diff --git a/addons/web/static/src/webclient/company_service.js b/addons/web/static/src/webclient/company_service.js index 1f5f7729436..c4bf26769a9 100644 --- a/addons/web/static/src/webclient/company_service.js +++ b/addons/web/static/src/webclient/company_service.js @@ -2,7 +2,6 @@ import { browser } from "@web/core/browser/browser"; import { registry } from "@web/core/registry"; -import { symmetricalDifference } from "../core/utils/arrays"; import { session } from "@web/session"; import { UPDATE_METHODS } from "@web/core/orm_service"; @@ -39,15 +38,29 @@ export const companyService = { } else if ("cids" in cookie.current) { cids = parseCompanyIds(cookie.current.cids); } + + const availableCompanies = session.user_companies.allowed_companies; const allowedCompanyIds = computeAllowedCompanyIds(cids); + const nextAvailableCompanies = allowedCompanyIds.slice(); // not using a Set because order is important + nextAvailableCompanies.add = (companyId) => { + if (!nextAvailableCompanies.includes(companyId)) { + nextAvailableCompanies.push(companyId); + availableCompanies[companyId].child_ids.map(nextAvailableCompanies.add); + } + } + nextAvailableCompanies.remove = (companyId) => { + if (nextAvailableCompanies.includes(companyId)) { + nextAvailableCompanies.splice(nextAvailableCompanies.indexOf(companyId), 1); + availableCompanies[companyId].child_ids.map(nextAvailableCompanies.remove); + } + } const stringCIds = allowedCompanyIds.join(","); router.replaceState({ cids: stringCIds }, { lock: true }); cookie.setCookie("cids", stringCIds); - user.updateContext({ allowed_company_ids: allowedCompanyIds }); - const availableCompanies = session.user_companies.allowed_companies; + // reload the page if changes are being done to `res.company` env.bus.addEventListener("RPC:RESPONSE", (ev) => { const { data, error } = ev.detail; const { model, method } = data.params; @@ -57,8 +70,10 @@ export const companyService = { } } }); + return { availableCompanies, + nextAvailableCompanies, get allowedCompanyIds() { return allowedCompanyIds.slice(); }, @@ -68,47 +83,24 @@ export const companyService = { getCompany(companyId) { return availableCompanies[companyId]; }, - getAllChildren(companyId) { - return [ - ...availableCompanies[companyId].child_ids, - ...availableCompanies[companyId].child_ids.map((child) => this.getAllChildren(child)) - ].flat() - }, - getChildrenToToggle(companyId) { - const children = this.getAllChildren(companyId) - if ( allowedCompanyIds.includes(companyId) ) { - return children.filter((id) => allowedCompanyIds.includes(id)) - } else { - return children.filter((id) => !allowedCompanyIds.includes(id)) - } - }, - setCompanies(mode, ...companyIds) { - // compute next company ids - let nextCompanyIds; + setCompanies(mode, companyId) { if (mode === "toggle") { - nextCompanyIds = symmetricalDifference(allowedCompanyIds, companyIds); - } else if (mode === "loginto") { - const companyId = companyIds[0]; - const children = this.getAllChildren(companyId); - if (allowedCompanyIds.length === 1) { - // 1 enabled company: stay in single company mode - nextCompanyIds = [companyId, ...children]; + if (nextAvailableCompanies.includes(companyId)) { + nextAvailableCompanies.remove(companyId); } else { - // multi company mode - nextCompanyIds = [ - companyId, - ...children, - ...allowedCompanyIds.filter((id) => id !== companyId && !children.includes(id)), - ]; + nextAvailableCompanies.add(companyId); } + } else if (mode === "loginto") { + nextAvailableCompanies.splice(0, nextAvailableCompanies.length); + nextAvailableCompanies.add(companyId) } - nextCompanyIds = nextCompanyIds.length ? nextCompanyIds : [companyIds[0]]; - - // apply them - router.pushState({ cids: nextCompanyIds }, { lock: true }); - cookie.setCookie("cids", nextCompanyIds); - browser.setTimeout(() => browser.location.reload()); // history.pushState is a little async }, + logNextCompanies() { + const next = nextAvailableCompanies.length ? nextAvailableCompanies : [allowedCompanyIds[0]] + router.pushState({ cids: next }, { lock: true }); + cookie.setCookie("cids", next); + browser.setTimeout(() => browser.location.reload()); // history.pushState is a little async + } }; }, }; diff --git a/addons/web/static/src/webclient/switch_company_menu/switch_company_menu.js b/addons/web/static/src/webclient/switch_company_menu/switch_company_menu.js index 68755c83336..66aecd88304 100644 --- a/addons/web/static/src/webclient/switch_company_menu/switch_company_menu.js +++ b/addons/web/static/src/webclient/switch_company_menu/switch_company_menu.js @@ -5,13 +5,12 @@ import { DropdownItem } from "@web/core/dropdown/dropdown_item"; import { useService } from "@web/core/utils/hooks"; import { registry } from "@web/core/registry"; import { browser } from "@web/core/browser/browser"; -import { symmetricalDifference } from "@web/core/utils/arrays"; import { Component, useState, reactive } from "@odoo/owl"; const store = reactive({ - companiesToToggle: [], toggleTimer: null, + nextAvailableCompanies: [], }); @@ -21,25 +20,25 @@ export class SwitchCompanyItem extends Component { this.store = useState(store); } - logIntoCompany(companyId) { - browser.clearTimeout(this.store.toggleTimer); - this.companyService.setCompanies("loginto", companyId); + get selectedCompanies() { + this.store.nextAvailableCompanies; // check if the state changed + return this.companyService.nextAvailableCompanies; } - get selectedCompanies() { - return symmetricalDifference( - this.companyService.allowedCompanyIds, - this.store.companiesToToggle, - ); + logIntoCompany(companyId) { + this.companyService.setCompanies("loginto", companyId); + browser.clearTimeout(this.store.toggleTimer); + this.companyService.logNextCompanies(); } toggleCompany(companyId) { - this.store.companiesToToggle = symmetricalDifference(this.store.companiesToToggle, [ - companyId, ...this.companyService.getChildrenToToggle(companyId) - ]); + this.companyService.setCompanies("toggle", companyId); + // trigger state change + this.store.nextAvailableCompanies = this.companyService.nextAvailableCompanies.slice(); + browser.clearTimeout(this.store.toggleTimer); this.store.toggleTimer = browser.setTimeout(() => { - this.companyService.setCompanies("toggle", ...this.store.companiesToToggle); + this.companyService.logNextCompanies(); }, this.constructor.toggleDelay); } } @@ -56,7 +55,7 @@ export class SwitchCompanyMenu extends Component { setup() { this.companyService = useService("company"); this.store = useState(store); - this.store.companiesToToggle = []; + this.store.nextAvailableCompanies = []; } } SwitchCompanyMenu.template = "web.SwitchCompanyMenu"; diff --git a/addons/web/static/tests/webclient/mobile/mobile_switch_company_menu_tests.js b/addons/web/static/tests/webclient/mobile/mobile_switch_company_menu_tests.js index 89cf88df4b9..3b62823612b 100644 --- a/addons/web/static/tests/webclient/mobile/mobile_switch_company_menu_tests.js +++ b/addons/web/static/tests/webclient/mobile/mobile_switch_company_menu_tests.js @@ -252,7 +252,7 @@ QUnit.module("MobileSwitchCompanyMenu", (hooks) => { * [x] **Company 3** */ await click(scMenuEl.querySelectorAll(".log_into")[1]); - assert.verifySteps(["cids=2%2C3%2C1"]); + assert.verifySteps(["cids=2"]); }); QUnit.test("multi company mode: log into an already selected company", async (assert) => { @@ -282,7 +282,7 @@ QUnit.module("MobileSwitchCompanyMenu", (hooks) => { * [x] Company 3 -> log into */ await click(scMenuEl.querySelectorAll(".log_into")[2]); - assert.verifySteps(["cids=3%2C2"]); + assert.verifySteps(["cids=3"]); }); QUnit.test("companies can be logged in even if some toggled within delay", async (assert) => { diff --git a/addons/web/static/tests/webclient/switch_company_menu_tests.js b/addons/web/static/tests/webclient/switch_company_menu_tests.js index 839fd9fe0be..f06128b3ab7 100644 --- a/addons/web/static/tests/webclient/switch_company_menu_tests.js +++ b/addons/web/static/tests/webclient/switch_company_menu_tests.js @@ -40,7 +40,9 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { allowed_companies: { 3: { id: 3, name: "Hermit", sequence: 1, parent_id: false, child_ids: [] }, 2: { id: 2, name: "Herman's", sequence: 2, parent_id: false, child_ids: [] }, - 1: { id: 1, name: "Heroes TM", sequence: 3, parent_id: false, child_ids: [] }, + 1: { id: 1, name: "Heroes TM", sequence: 3, parent_id: false, child_ids: [4, 5] }, + 4: { id: 4, name: "Hercules", sequence: 4, parent_id: 1, child_ids: [] }, + 5: { id: 5, name: "Hulk", sequence: 5, parent_id: 1, child_ids: [] }, }, current_company: 3, }); @@ -59,10 +61,10 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { assert.strictEqual(target.querySelector("div.o_switch_company_menu").textContent, "Hermit"); await click(target.querySelector(".dropdown-toggle")); - assert.containsN(target, ".toggle_company", 3); - assert.containsN(target, ".log_into", 3); + assert.containsN(target, ".toggle_company", 5); + assert.containsN(target, ".log_into", 5); assert.containsOnce(target, ".fa-check-square"); - assert.containsN(target, ".fa-square-o", 2); + assert.containsN(target, ".fa-square-o", 4); assert.strictEqual( target.querySelector(".fa-check-square").closest(".dropdown-item").textContent, "Hermit" @@ -73,7 +75,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { ); assert.strictEqual( target.querySelector(".dropdown-menu").textContent, - "HermitHerman'sHeroes TM" + "HermitHerman'sHeroes TMHerculesHulk" ); }); @@ -89,42 +91,46 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { * [x] **Hermit** * [ ] Herman's * [ ] Heroes TM + * [ ] Hercules + * [ ] Hulk */ assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [3]); assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3); await click(target.querySelector(".dropdown-toggle")); - assert.containsN(target, "[data-company-id]", 3); + assert.containsN(target, "[data-company-id]", 5); assert.containsN(target, "[data-company-id] .fa-check-square", 1); - assert.containsN(target, "[data-company-id] .fa-square-o", 2); + assert.containsN(target, "[data-company-id] .fa-square-o", 4); assert.deepEqual( [...target.querySelectorAll("[data-company-id] .toggle_company")].map( (el) => el.getAttribute('aria-checked') ), - ["true", "false", "false"] + ["true", "false", "false", "false", "false"] ); assert.deepEqual( [...target.querySelectorAll("[data-company-id] .log_into")].map((el) => el.getAttribute('aria-pressed')), - ["true", "false", "false"] + ["true", "false", "false", "false", "false"] ); /** * [x] **Hermit** * [x] Herman's -> toggle * [ ] Heroes TM + * [ ] Hercules + * [ ] Hulk */ await click(target.querySelectorAll(".toggle_company")[1]); assert.containsOnce(target, ".dropdown-menu", "dropdown is still opened"); assert.containsN(target, "[data-company-id] .fa-check-square", 2); - assert.containsN(target, "[data-company-id] .fa-square-o", 1); + assert.containsN(target, "[data-company-id] .fa-square-o", 3); assert.deepEqual( [...target.querySelectorAll("[data-company-id] .toggle_company")].map( (el) => el.getAttribute('aria-checked') ), - ["true", "true", "false"] + ["true", "true", "false", "false", "false"] ); assert.deepEqual( [...target.querySelectorAll("[data-company-id] .log_into")].map((el) => el.getAttribute('aria-pressed')), - ["true", "false", "false"] + ["true", "false", "false", "false", "false"] ); await prom; assert.verifySteps(["cids=3%2C2"]); @@ -144,29 +150,33 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { * [x] **Hermit** * [ ] Herman's * [ ] Heroes TM + * [ ] Hercules + * [ ] Hulk */ assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [3]); assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3); await click(target.querySelector(".dropdown-toggle")); - assert.containsN(target, "[data-company-id]", 3); + assert.containsN(target, "[data-company-id]", 5); assert.containsN(target, "[data-company-id] .fa-check-square", 1); - assert.containsN(target, "[data-company-id] .fa-square-o", 2); + assert.containsN(target, "[data-company-id] .fa-square-o", 4); /** - * [ ] **Hermit** -> toggle all - * [x] Herman's -> toggle all - * [x] Heroes TM -> toggle all + * [ ] Hermit -> toggle all + * [x] **Herman's** -> toggle all + * [x] Heroes TM -> toggle all + * [ ] Hercules + * [ ] Hulk */ await click(target.querySelectorAll(".toggle_company")[0]); await click(target.querySelectorAll(".toggle_company")[1]); await click(target.querySelectorAll(".toggle_company")[2]); assert.containsOnce(target, ".dropdown-menu", "dropdown is still opened"); - assert.containsN(target, "[data-company-id] .fa-check-square", 2); + assert.containsN(target, "[data-company-id] .fa-check-square", 4); assert.containsN(target, "[data-company-id] .fa-square-o", 1); assert.verifySteps([]); await prom; // await toggle promise - assert.verifySteps(["cids=2%2C1"]); + assert.verifySteps(["cids=2%2C1%2C4%2C5"]); }); QUnit.test("single company selected: toggling it off will keep it", async (assert) => { @@ -183,19 +193,23 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { * [x] **Hermit** * [ ] Herman's * [ ] Heroes TM + * [ ] Hercules + * [ ] Hulk */ assert.deepEqual(scMenu.env.services.router.current.hash, { cids: 3 }); assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [3]); assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3); await click(target.querySelector(".dropdown-toggle")); - assert.containsN(target, "[data-company-id]", 3); + assert.containsN(target, "[data-company-id]", 5); assert.containsN(target, "[data-company-id] .fa-check-square", 1); - assert.containsN(target, "[data-company-id] .fa-square-o", 2); + assert.containsN(target, "[data-company-id] .fa-square-o", 4); /** - * [ ] **Hermit** -> toggle off + * [x] **Hermit** -> toggle off * [ ] Herman's * [ ] Heroes TM + * [ ] Hercules + * [ ] Hulk */ await click(target.querySelectorAll(".toggle_company")[0]); assert.deepEqual(scMenu.env.services.router.current.hash, { cids: 3 }); @@ -203,7 +217,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3); assert.containsOnce(target, ".dropdown-menu", "dropdown is still opened"); assert.containsN(target, "[data-company-id] .fa-check-square", 0); - assert.containsN(target, "[data-company-id] .fa-square-o", 3); + assert.containsN(target, "[data-company-id] .fa-square-o", 5); }); QUnit.test("single company mode: companies can be logged in", async (assert) => { @@ -218,18 +232,22 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { * [x] **Hermit** * [ ] Herman's * [ ] Heroes TM + * [ ] Hercules + * [ ] Hulk */ assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [3]); assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3); await click(target.querySelector(".dropdown-toggle")); - assert.containsN(target, "[data-company-id]", 3); + assert.containsN(target, "[data-company-id]", 5); assert.containsN(target, "[data-company-id] .fa-check-square", 1); - assert.containsN(target, "[data-company-id] .fa-square-o", 2); + assert.containsN(target, "[data-company-id] .fa-square-o", 4); /** - * [x] **Hermit** - * [ ] Herman's -> log into + * [ ] Hermit + * [x] **Herman's** -> log into * [ ] Heroes TM + * [ ] Hercules + * [ ] Hulk */ await click(target.querySelectorAll(".log_into")[1]); assert.containsNone(target, ".dropdown-menu", "dropdown is directly closed"); @@ -249,22 +267,26 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { * [x] Hermit * [ ] Herman's * [x] **Heroes TM** + * [ ] Hercules + * [ ] Hulk */ assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [3, 1]); assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3); await click(target.querySelector(".dropdown-toggle")); - assert.containsN(target, "[data-company-id]", 3); + assert.containsN(target, "[data-company-id]", 5); assert.containsN(target, "[data-company-id] .fa-check-square", 2); - assert.containsN(target, "[data-company-id] .fa-square-o", 1); + assert.containsN(target, "[data-company-id] .fa-square-o", 3); /** - * [x] Hermit - * [ ] Herman's -> log into - * [x] **Heroes TM** + * [ ] Hermit + * [x] **Herman's** -> log into + * [ ] Heroes TM + * [ ] Hercules + * [ ] Hulk */ await click(target.querySelectorAll(".log_into")[1]); assert.containsNone(target, ".dropdown-menu", "dropdown is directly closed"); - assert.verifySteps(["cids=2%2C3%2C1"]); + assert.verifySteps(["cids=2"]); }); QUnit.test("multi company mode: log into an already selected company", async (assert) => { @@ -280,22 +302,26 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { * [ ] Hermit * [x] **Herman's** * [x] Heroes TM + * [ ] Hercules + * [ ] Hulk */ assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [2, 1]); assert.strictEqual(scMenu.env.services.company.currentCompany.id, 2); await click(target.querySelector(".dropdown-toggle")); - assert.containsN(target, "[data-company-id]", 3); + assert.containsN(target, "[data-company-id]", 5); assert.containsN(target, "[data-company-id] .fa-check-square", 2); - assert.containsN(target, "[data-company-id] .fa-square-o", 1); + assert.containsN(target, "[data-company-id] .fa-square-o", 3); /** * [ ] Hermit - * [x] **Herman's** - * [x] Heroes TM -> log into + * [ ] Herman's + * [x] **Heroes TM** -> log into + * [x] Hercules + * [x] Hulk */ await click(target.querySelectorAll(".log_into")[2]); assert.containsNone(target, ".dropdown-menu", "dropdown is directly closed"); - assert.verifySteps(["cids=1%2C2"]); + assert.verifySteps(["cids=1%2C4%2C5"]); }); QUnit.test("companies can be logged in even if some toggled within delay", async (assert) => { @@ -310,18 +336,22 @@ QUnit.module("SwitchCompanyMenu", (hooks) => { * [x] **Hermit** * [ ] Herman's * [ ] Heroes TM + * [ ] Hercules + * [ ] Hulk */ assert.deepEqual(scMenu.env.services.company.allowedCompanyIds, [3]); assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3); await click(target.querySelector(".dropdown-toggle")); - assert.containsN(target, "[data-company-id]", 3); + assert.containsN(target, "[data-company-id]", 5); assert.containsN(target, "[data-company-id] .fa-check-square", 1); - assert.containsN(target, "[data-company-id] .fa-square-o", 2); + assert.containsN(target, "[data-company-id] .fa-square-o", 4); /** - * [ ] **Hermit** -> toggled - * [ ] Herman's -> logged in + * [ ] Hermit -> toggled + * [x] **Herman's** -> logged in * [ ] Heroes TM -> toggled + * [ ] Hercules + * [ ] Hulk */ await click(target.querySelectorAll(".toggle_company")[2]); await click(target.querySelectorAll(".toggle_company")[0]);