diff --git a/addons/mrp_subcontracting/__manifest__.py b/addons/mrp_subcontracting/__manifest__.py index 5af0dfc8794..baf4fb86f08 100644 --- a/addons/mrp_subcontracting/__manifest__.py +++ b/addons/mrp_subcontracting/__manifest__.py @@ -107,7 +107,6 @@ 'web/static/src/legacy/js/core/ajax.js', 'web/static/src/legacy/js/core/bus.js', 'web/static/src/legacy/js/core/class.js', - 'web/static/src/legacy/js/core/concurrency.js', 'web/static/src/legacy/js/core/dialog.js', 'web/static/src/legacy/xml/dialog.xml', 'web/static/src/legacy/js/core/dom.js', diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py index dc257942f68..45c3be7829d 100644 --- a/addons/point_of_sale/__manifest__.py +++ b/addons/point_of_sale/__manifest__.py @@ -142,7 +142,6 @@ # FIXME POSREF legacy dependencies to remove 'web/static/lib/moment/moment.js', 'web/static/src/legacy/js/promise_extension.js', - 'web/static/src/legacy/js/core/concurrency.js', 'web/static/src/legacy/legacy_component.js', ], # Bundle that starts the pos, loaded on /pos/ui diff --git a/addons/pos_loyalty/static/src/overrides/models/loyalty.js b/addons/pos_loyalty/static/src/overrides/models/loyalty.js index 98d843405be..6d60692bb8b 100644 --- a/addons/pos_loyalty/static/src/overrides/models/loyalty.js +++ b/addons/pos_loyalty/static/src/overrides/models/loyalty.js @@ -2,17 +2,13 @@ import { Order, Orderline } from "@point_of_sale/app/store/models"; import { Mutex } from "@web/core/utils/concurrency"; -import concurrency from "@web/legacy/js/core/concurrency"; import { roundDecimals, roundPrecision } from "@web/core/utils/numbers"; import { _t } from "@web/core/l10n/translation"; import { patch } from "@web/core/utils/patch"; import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup"; -// FIXME: Perhaps MutexedDropPrevious can be replaced by the new KeepLast. -// > This might require thorough investigation on how _updateRewards work. -// > If successful, we can fully get rid of the legacy concurrency module. -const dropPrevious = new concurrency.MutexedDropPrevious(); // Used for queuing reward updates const mutex = new Mutex(); // Used for sequential cache updates +const updateRewardsMutex = new Mutex(); function _newRandomRewardCode() { return (Math.random() + 1).toString(36).substring(3); @@ -150,9 +146,9 @@ patch(Order.prototype, { /** @override */ getEmailItems() { - return super.getEmailItems(...arguments).concat( - this.has_pdf_gift_card ? [_t("the gift cards")] : [] - ); + return super + .getEmailItems(...arguments) + .concat(this.has_pdf_gift_card ? [_t("the gift cards")] : []); }, export_as_JSON() { @@ -300,15 +296,18 @@ patch(Order.prototype, { return orderLines[orderLines.length - 1]; }, set_pricelist(pricelist) { - const oldPricelist = this.pricelist + const oldPricelist = this.pricelist; super.set_pricelist(...arguments); if (this.couponPointChanges && oldPricelist !== pricelist) { // Remove couponPointChanges for cards in no longer available programs. // This makes sure that counting of points on loyalty and ewallet programs is updated after pricelist changes. const loyaltyProgramIds = new Set( this.pos.programs - .filter((program) => program.pricelist_ids.length > 0 - && (!pricelist || !program.pricelist_ids.includes(pricelist.id))) + .filter( + (program) => + program.pricelist_ids.length > 0 && + (!pricelist || !program.pricelist_ids.includes(pricelist.id)) + ) .map((program) => program.id) ); for (const [key, pointChange] of Object.entries(this.couponPointChanges)) { @@ -367,33 +366,30 @@ patch(Order.prototype, { if (this.pos.programs.length === 0) { return; } - dropPrevious - .exec(() => { - return this._updateLoyaltyPrograms().then(async () => { - // Try auto claiming rewards - const claimableRewards = this.getClaimableRewards(false, false, true); - let changed = false; - for (const { coupon_id, reward } of claimableRewards) { - if ( - reward.program_id.rewards.length === 1 && - !reward.program_id.is_nominative && - (reward.reward_type !== "product" || - (reward.reward_type == "product" && !reward.multi_product)) - ) { - this._applyReward(reward, coupon_id); - changed = true; - } + + updateRewardsMutex.exec(() => { + return this._updateLoyaltyPrograms().then(async () => { + // Try auto claiming rewards + const claimableRewards = this.getClaimableRewards(false, false, true); + let changed = false; + for (const { coupon_id, reward } of claimableRewards) { + if ( + reward.program_id.rewards.length === 1 && + !reward.program_id.is_nominative && + (reward.reward_type !== "product" || + (reward.reward_type == "product" && !reward.multi_product)) + ) { + this._applyReward(reward, coupon_id); + changed = true; } - // Rewards may impact the number of points gained - if (changed) { - await this._updateLoyaltyPrograms(); - } - this._updateRewardLines(); - }); - }) - .catch(() => { - /* catch the reject of dp when calling `add` to avoid unhandledrejection */ + } + // Rewards may impact the number of points gained + if (changed) { + await this._updateLoyaltyPrograms(); + } + this._updateRewardLines(); }); + }); }, async _updateLoyaltyPrograms() { await this._checkMissingCoupons(); @@ -716,7 +712,10 @@ patch(Order.prototype, { if (program.limit_usage && program.total_order_count >= program.max_usage) { return false; } - if (program.pricelist_ids.length > 0 && (!this.pricelist || !program.pricelist_ids.includes(this.pricelist.id))) { + if ( + program.pricelist_ids.length > 0 && + (!this.pricelist || !program.pricelist_ids.includes(this.pricelist.id)) + ) { return false; } return true; @@ -949,8 +948,10 @@ patch(Order.prototype, { : 0; for (const couponProgram of allCouponPrograms) { const program = this.pos.program_by_id[couponProgram.program_id]; - if (program.pricelist_ids.length > 0 - && (!this.pricelist || !program.pricelist_ids.includes(this.pricelist.id))) { + if ( + program.pricelist_ids.length > 0 && + (!this.pricelist || !program.pricelist_ids.includes(this.pricelist.id)) + ) { continue; } if (program.trigger == "with_code") { @@ -1165,7 +1166,7 @@ patch(Order.prototype, { let cheapestLine = false; for (const lines of Object.values(discountLinesPerReward)) { const lineReward = this.pos.reward_by_id[lines[0].reward_id]; - if (lineReward.reward_type !== 'discount') { + if (lineReward.reward_type !== "discount") { continue; } let discountedLines = orderLines; @@ -1496,9 +1497,11 @@ patch(Order.prototype, { let claimableRewards = null; let coupon = null; if (rule) { - let program_pricelists = rule.program_id.pricelist_ids; - if (program_pricelists.length > 0 - && (!this.pricelist || !program_pricelists.includes(this.pricelist.id))) { + const program_pricelists = rule.program_id.pricelist_ids; + if ( + program_pricelists.length > 0 && + (!this.pricelist || !program_pricelists.includes(this.pricelist.id)) + ) { return _t("That promo code program requires a specific pricelist."); } if (this.codeActivatedProgramRules.includes(rule.id)) { @@ -1515,7 +1518,13 @@ patch(Order.prototype, { const { successful, payload } = await this.env.services.orm.call( "pos.config", "use_coupon_code", - [[this.pos.config.id], code, this.creation_date, customerId, this.pricelist ? this.pricelist.id : false] + [ + [this.pos.config.id], + code, + this.creation_date, + customerId, + this.pricelist ? this.pricelist.id : false, + ] ); if (successful) { // Allow rejecting a gift card that is not yet paid. diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index 4d0d5e7638e..c8bc23d2ceb 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -158,7 +158,6 @@ 'web/static/src/legacy/js/core/ajax.js', 'web/static/src/legacy/js/core/bus.js', 'web/static/src/legacy/js/core/class.js', - 'web/static/src/legacy/js/core/concurrency.js', 'web/static/src/legacy/js/core/dialog.js', 'web/static/src/legacy/xml/dialog.xml', 'web/static/src/legacy/js/core/dom.js', diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index cc459f304f2..e83395003d7 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -107,7 +107,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/js/core/ajax.js', 'web/static/src/legacy/js/core/bus.js', 'web/static/src/legacy/js/core/class.js', - 'web/static/src/legacy/js/core/concurrency.js', 'web/static/src/legacy/js/core/dialog.js', 'web/static/src/legacy/xml/dialog.xml', 'web/static/src/legacy/js/core/dom.js', @@ -265,7 +264,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/js/core/ajax.js', 'web/static/src/legacy/js/core/bus.js', 'web/static/src/legacy/js/core/class.js', - 'web/static/src/legacy/js/core/concurrency.js', 'web/static/src/legacy/js/core/dialog.js', 'web/static/src/legacy/xml/dialog.xml', 'web/static/src/legacy/js/core/dom.js', diff --git a/addons/web/static/src/legacy/js/core/concurrency.js b/addons/web/static/src/legacy/js/core/concurrency.js deleted file mode 100644 index 7626dfe2524..00000000000 --- a/addons/web/static/src/legacy/js/core/concurrency.js +++ /dev/null @@ -1,109 +0,0 @@ -/** @odoo-module **/ - -/** - * Concurrency Utils - * - * This file contains a short collection of useful helpers designed to help with - * everything concurrency related in Odoo. - * - * The basic concurrency primitives in Odoo JS are the callback, and the - * promises. Promises (promise) are more composable, so we usually use them - * whenever possible. We use the jQuery implementation. - * - * Those functions are really nothing special, but are simply the result of how - * we solved some concurrency issues, when we noticed that a pattern emerged. - */ - -import Class from "@web/legacy/js/core/class"; - -const concurrency = { - /** - * A MutexedDropPrevious is a primitive for serializing computations while - * skipping the ones that where executed between a current one and before - * the execution of a new one. This is useful to avoid useless RPCs. - * - * You can read the Mutex description to understand its role ; for the - * DropPrevious part of this abstraction, imagine the following situation: - * you have a code that call the server with a fixed argument and a list of - * operations that only grows after each call and you only care about the - * RPC result (the server code doesn't do anything). If this code is called - * three times (A B C) and C is executed before B has started, it's useless - * to make an extra RPC (B) if you know that it won't have an impact and you - * won't use its result. - * - * Note that the promise returned by the exec call won't be resolved if - * exec is called before the first exec call resolution ; only the promise - * returned by the last exec call will be resolved (the other are rejected); - * - * A MutexedDropPrevious has to be a class, and not a function, because we - * have to keep track of some internal state. The exec function takes as - * argument an action (and not a promise as DropPrevious for example) - * because it's the MutexedDropPrevious role to trigger the RPC call that - * returns a promise when it's time. - */ - MutexedDropPrevious: Class.extend({ - init: function () { - this.locked = false; - this.currentProm = null; - this.pendingAction = null; - this.pendingProm = null; - }, - /** - * @param {function} action a function which may return a promise - * @returns {Promise} - */ - exec: function (action) { - var self = this; - var resolution; - var rejection; - if (this.locked) { - this.pendingAction = action; - var oldPendingDef = this.pendingProm; - - this.pendingProm = new Promise(function (resolve, reject) { - resolution = resolve; - rejection = reject; - if (oldPendingDef) { - oldPendingDef.reject(); - } - self.currentProm.reject(); - }); - this.pendingProm.resolve = resolution; - this.pendingProm.reject = rejection; - return this.pendingProm; - } else { - this.locked = true; - this.currentProm = new Promise(function (resolve, reject) { - resolution = resolve; - rejection = reject; - function unlock() { - self.locked = false; - if (self.pendingAction) { - var action = self.pendingAction; - var prom = self.pendingProm; - self.pendingAction = null; - self.pendingProm = null; - self.exec(action) - .then(prom.resolve) - .guardedCatch(prom.reject); - } - } - Promise.resolve(action()) - .then(function (result) { - resolve(result); - unlock(); - }) - .guardedCatch(function (reason) { - reject(reason); - unlock(); - }); - }); - this.currentProm.resolve = resolution; - this.currentProm.reject = rejection; - return this.currentProm; - } - } - }), -}; - -export default concurrency; diff --git a/addons/web/static/tests/legacy/core/concurrency_tests.js b/addons/web/static/tests/legacy/core/concurrency_tests.js deleted file mode 100644 index 3d900643644..00000000000 --- a/addons/web/static/tests/legacy/core/concurrency_tests.js +++ /dev/null @@ -1,379 +0,0 @@ -/** @odoo-module **/ - -import concurrency from "@web/legacy/js/core/concurrency"; -import { Mutex } from "@web/core/utils/concurrency"; -import testUtils from "@web/../tests/legacy/helpers/test_utils"; - -var makeTestPromise = testUtils.makeTestPromise; -var makeTestPromiseWithAssert = testUtils.makeTestPromiseWithAssert; - -QUnit.module('core', {}, function () { - - QUnit.module('concurrency'); - - QUnit.test('mutex: simple scheduling', async function (assert) { - assert.expect(5); - var mutex = new Mutex(); - - var prom1 = makeTestPromiseWithAssert(assert, 'prom1'); - var prom2 = makeTestPromiseWithAssert(assert, 'prom2'); - - mutex.exec(function () { return prom1; }); - mutex.exec(function () { return prom2; }); - - assert.verifySteps([]); - - await prom1.resolve(); - - assert.verifySteps(['ok prom1']); - - await prom2.resolve(); - - assert.verifySteps(['ok prom2']); - }); - - QUnit.test('mutex: simpleScheduling2', async function (assert) { - assert.expect(5); - var mutex = new Mutex(); - - var prom1 = makeTestPromiseWithAssert(assert, 'prom1'); - var prom2 = makeTestPromiseWithAssert(assert, 'prom2'); - - mutex.exec(function () { return prom1; }); - mutex.exec(function () { return prom2; }); - - assert.verifySteps([]); - - await prom2.resolve(); - - assert.verifySteps(['ok prom2']); - - await prom1.resolve(); - - assert.verifySteps(['ok prom1']); - }); - - QUnit.test('mutex: reject', async function (assert) { - assert.expect(7); - var mutex = new Mutex(); - - var prom1 = makeTestPromiseWithAssert(assert, 'prom1'); - var prom2 = makeTestPromiseWithAssert(assert, 'prom2'); - var prom3 = makeTestPromiseWithAssert(assert, 'prom3'); - - mutex.exec(function () { return prom1; }).catch(function () {}); - mutex.exec(function () { return prom2; }).catch(function () {}); - mutex.exec(function () { return prom3; }).catch(function () {}); - - assert.verifySteps([]); - - prom1.resolve(); - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(['ok prom1']); - - prom2.catch(function () { - assert.verifySteps(['ko prom2']); - }); - prom2.reject({name: "sdkjfmqsjdfmsjkdfkljsdq"}); - await testUtils.nextMicrotaskTick(); - - prom3.resolve(); - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(['ok prom3']); - }); - - QUnit.test('mutex: getUnlockedDef checks', async function (assert) { - assert.expect(9); - - var mutex = new Mutex(); - - var prom1 = makeTestPromiseWithAssert(assert, 'prom1'); - var prom2 = makeTestPromiseWithAssert(assert, 'prom2'); - - mutex.getUnlockedDef().then(function () { - assert.step('mutex unlocked (1)'); - }); - - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(['mutex unlocked (1)']); - - mutex.exec(function () { return prom1; }); - await testUtils.nextMicrotaskTick(); - - mutex.getUnlockedDef().then(function () { - assert.step('mutex unlocked (2)'); - }); - - assert.verifySteps([]); - - mutex.exec(function () { return prom2; }); - await testUtils.nextMicrotaskTick(); - - assert.verifySteps([]); - - await prom1.resolve(); - - assert.verifySteps(['ok prom1']); - - prom2.resolve(); - await testUtils.nextTick(); - - assert.verifySteps(['ok prom2', 'mutex unlocked (2)']); - }); - - QUnit.test('mutex: error and getUnlockedDef', async function (assert) { - const mutex = new Mutex(); - mutex.exec(async () => { - await Promise.resolve(); - throw new Error("boom"); - }).catch(() => assert.step("prom rejected")); - await testUtils.nextTick(); - assert.verifySteps(['prom rejected']); - - mutex.getUnlockedDef().then(function () { - assert.step('mutex unlocked'); - }); - await testUtils.nextMicrotaskTick(); - assert.verifySteps(['mutex unlocked']); - }); - - QUnit.test('MutexedDropPrevious: simple', async function (assert) { - assert.expect(5); - - var m = new concurrency.MutexedDropPrevious(); - var d1 = makeTestPromise(); - - d1.then(function () { - assert.step("d1 resolved"); - }); - m.exec(function () { return d1; }).then(function (result) { - assert.step("p1 done"); - assert.strictEqual(result, 'd1'); - }); - - assert.verifySteps([]); - d1.resolve('d1'); - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(["d1 resolved","p1 done"]); - }); - - QUnit.test('MutexedDropPrevious: d2 arrives after d1 resolution', async function (assert) { - assert.expect(8); - - var m = new concurrency.MutexedDropPrevious(); - var d1 = makeTestPromiseWithAssert(assert, 'd1'); - - m.exec(function () { return d1; }).then(function () { - assert.step("p1 resolved"); - }); - - assert.verifySteps([]); - d1.resolve('d1'); - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(['ok d1','p1 resolved']); - - var d2 = makeTestPromiseWithAssert(assert, 'd2'); - m.exec(function () { return d2; }).then(function () { - assert.step("p2 resolved"); - }); - - assert.verifySteps([]); - d2.resolve('d2'); - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(['ok d2','p2 resolved']); - }); - - QUnit.test('MutexedDropPrevious: p1 does not return a deferred', async function (assert) { - assert.expect(7); - - var m = new concurrency.MutexedDropPrevious(); - - m.exec(function () { return 42; }).then(function () { - assert.step("p1 resolved"); - }); - - assert.verifySteps([]); - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(['p1 resolved']); - - var d2 = makeTestPromiseWithAssert(assert, 'd2'); - m.exec(function () { return d2; }).then(function () { - assert.step("p2 resolved"); - }); - - assert.verifySteps([]); - d2.resolve('d2'); - await testUtils.nextMicrotaskTick(); - assert.verifySteps(['ok d2','p2 resolved']); - }); - - QUnit.test('MutexedDropPrevious: p2 arrives before p1 resolution', async function (assert) { - assert.expect(8); - - var m = new concurrency.MutexedDropPrevious(); - var d1 = makeTestPromiseWithAssert(assert, 'd1'); - - m.exec(function () { return d1; }).catch(function () { - assert.step("p1 rejected"); - }); - assert.verifySteps([]); - - var d2 = makeTestPromiseWithAssert(assert, 'd2'); - m.exec(function () { return d2; }).then(function () { - assert.step("p2 resolved"); - }); - - assert.verifySteps([]); - d1.resolve('d1'); - await testUtils.nextMicrotaskTick(); - assert.verifySteps(['p1 rejected', 'ok d1']); - - d2.resolve('d2'); - await testUtils.nextMicrotaskTick(); - assert.verifySteps(['ok d2', 'p2 resolved']); - }); - - QUnit.test('MutexedDropPrevious: 3 arrives before 2 initialization', async function (assert) { - assert.expect(10); - var m = new concurrency.MutexedDropPrevious(); - - var d1 = makeTestPromiseWithAssert(assert, 'd1'); - var d3 = makeTestPromiseWithAssert(assert, 'd3'); - - m.exec(function () { return d1; }).catch(function () { - assert.step('p1 rejected'); - }); - - m.exec(function () { - assert.ok(false, "should not execute this function"); - }).catch(function () { - assert.step('p2 rejected'); - }); - - m.exec(function () { return d3; }).then(function (result) { - assert.strictEqual(result, 'd3'); - assert.step('p3 resolved'); - }); - - assert.verifySteps([]); - - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(['p1 rejected', 'p2 rejected']); - - d1.resolve('d1'); - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(['ok d1']); - - d3.resolve('d3'); - await testUtils.nextTick(); - - - assert.verifySteps(['ok d3','p3 resolved']); - }); - - QUnit.test('MutexedDropPrevious: 3 arrives after 2 initialization', async function (assert) { - assert.expect(15); - var m = new concurrency.MutexedDropPrevious(); - - var d1 = makeTestPromiseWithAssert(assert, 'd1'); - var d2 = makeTestPromiseWithAssert(assert, 'd2'); - var d3 = makeTestPromiseWithAssert(assert, 'd3'); - - m.exec(function () { - assert.step('execute d1'); - return d1; - }).catch(function () { - assert.step('p1 rejected'); - }); - - m.exec(function () { - assert.step('execute d2'); - return d2; - }).catch(function () { - assert.step('p2 rejected'); - }); - - assert.verifySteps(['execute d1']); - - await testUtils.nextMicrotaskTick(); - assert.verifySteps(['p1 rejected']); - - d1.resolve('d1'); - await testUtils.nextMicrotaskTick(); - - assert.verifySteps(['ok d1', 'execute d2']); - - m.exec(function () { - assert.step('execute d3'); - return d3; - }).then(function () { - assert.step('p3 resolved'); - }); - await testUtils.nextMicrotaskTick(); - assert.verifySteps(['p2 rejected']); - - d2.resolve(); - await testUtils.nextMicrotaskTick(); - assert.verifySteps(['ok d2', 'execute d3']); - - d3.resolve(); - await testUtils.nextTick(); - assert.verifySteps(['ok d3', 'p3 resolved']); - - }); - - QUnit.test('MutexedDropPrevious: 2 in then of 1 with 3', async function (assert) { - assert.expect(9); - - var m = new concurrency.MutexedDropPrevious(); - - var d1 = makeTestPromiseWithAssert(assert, 'd1'); - var d2 = makeTestPromiseWithAssert(assert, 'd2'); - var d3 = makeTestPromiseWithAssert(assert, 'd3'); - var p3; - - m.exec(function () { return d1; }) - .catch(function () { - assert.step('p1 rejected'); - p3 = m.exec(function () { - return d3; - }).then(function () { - assert.step('p3 resolved'); - }); - return p3; - }); - - await testUtils.nextTick(); - assert.verifySteps([]); - - m.exec(function () { - assert.ok(false, 'should not execute this function'); - return d2; - }).catch(function () { - assert.step('p2 rejected'); - }); - - await testUtils.nextTick(); - assert.verifySteps(['p1 rejected', 'p2 rejected']); - - d1.resolve('d1'); - await testUtils.nextTick(); - - assert.verifySteps(['ok d1']); - - d3.resolve('d3'); - await testUtils.nextTick(); - - assert.verifySteps(['ok d3', 'p3 resolved']); - }); - -}); diff --git a/addons/web_editor/__manifest__.py b/addons/web_editor/__manifest__.py index 628a596d692..cc138b307fa 100644 --- a/addons/web_editor/__manifest__.py +++ b/addons/web_editor/__manifest__.py @@ -97,7 +97,6 @@ Odoo Web Editor widget. 'web/static/src/legacy/js/core/ajax.js', 'web/static/src/legacy/js/core/bus.js', 'web/static/src/legacy/js/core/class.js', - 'web/static/src/legacy/js/core/concurrency.js', 'web/static/src/legacy/js/core/dialog.js', 'web/static/src/legacy/xml/dialog.xml', 'web/static/src/legacy/js/core/dom.js', diff --git a/addons/website_slides/__manifest__.py b/addons/website_slides/__manifest__.py index 2ba6cde8aba..c787dca21da 100644 --- a/addons/website_slides/__manifest__.py +++ b/addons/website_slides/__manifest__.py @@ -200,7 +200,6 @@ Featuring 'web/static/src/legacy/js/core/ajax.js', 'web/static/src/legacy/js/core/bus.js', 'web/static/src/legacy/js/core/class.js', - 'web/static/src/legacy/js/core/concurrency.js', 'web/static/src/legacy/js/core/dialog.js', 'web/static/src/legacy/xml/dialog.xml', 'web/static/src/legacy/js/core/dom.js',