diff --git a/product_configurator_mrp/__manifest__.py b/product_configurator_mrp/__manifest__.py index bb3f1c2bb5..dad6ad348e 100644 --- a/product_configurator_mrp/__manifest__.py +++ b/product_configurator_mrp/__manifest__.py @@ -18,14 +18,18 @@ ], "assets": { "web.assets_backend": [ - "/product_configurator_mrp/static/src/js/list_controller.js", - "/product_configurator_mrp/static/src/js/kanban_controller.js", - "/product_configurator_mrp/static/src/js/form_controller.js", + "/product_configurator_mrp/static/src/js/list_controller.esm.js", + "/product_configurator_mrp/static/src/js/kanban_controller.esm.js", + "/product_configurator_mrp/static/src/js/form_controller.esm.js", "/product_configurator_mrp/static/src/scss/mrp_config.scss", "/product_configurator_mrp/static/src/xml/mrp_production_views.xml", ], }, - "demo": ["demo/product_template.xml"], + "demo": [ + "demo/product_template.xml", + "demo/configuration_set.xml", + "demo/mrp_bom.xml", + ], "qweb": ["static/src/xml/mrp_production_views.xml"], "installable": True, "auto_install": False, diff --git a/product_configurator_mrp/demo/configuration_set.xml b/product_configurator_mrp/demo/configuration_set.xml new file mode 100644 index 0000000000..16e29b7906 --- /dev/null +++ b/product_configurator_mrp/demo/configuration_set.xml @@ -0,0 +1,370 @@ + + + + + + + Line / Sport Line + + + Line / Luxury Line + + + Line / Model Sport Line + + + Line / Model Luxury Line + + + Line / Model M Sport + + + Line / Model Advantage + + + Transmission / Automatic Transmission Steptronic + + + Transmission / Sport Automatic Transmission Steptronic + + + Options / Sunroof + + + Options / Armrest + + + Options / Towhook + + + Options / Smoker Package + + + Engine / 218i Coupé + + + Engine / 220i Coupé + + + Engine / 228i Coupé + + + Engine / M235i Coupé + + + Engine / M235i xDrive Coupe + + + Engine / 218d Coupé + + + Engine / 220d Coupé + + + Engine / 220d xDrive Coupé + + + Engine / 225d Coupé + + + Paint Color / Silver Paint + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/product_configurator_mrp/demo/mrp_bom.xml b/product_configurator_mrp/demo/mrp_bom.xml new file mode 100644 index 0000000000..374d4982f9 --- /dev/null +++ b/product_configurator_mrp/demo/mrp_bom.xml @@ -0,0 +1,263 @@ + + + + + + + + + 1 + BMW 2 Series + + + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + + + + 5 + + + + diff --git a/product_configurator_mrp/models/mrp.py b/product_configurator_mrp/models/mrp.py index 760e185693..4a261f28c7 100644 --- a/product_configurator_mrp/models/mrp.py +++ b/product_configurator_mrp/models/mrp.py @@ -1,7 +1,7 @@ # Copyright (C) 2021 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import api, fields, models class MrpProduction(models.Model): @@ -23,6 +23,7 @@ class MrpProduction(models.Model): string="Custom Values", ) + @api.model def action_config_start(self): """Return action to start configuration wizard""" configurator_obj = self.env["product.configurator.mrp"] diff --git a/product_configurator_mrp/models/product_config.py b/product_configurator_mrp/models/product_config.py index 471a90651e..cad58ad7b6 100644 --- a/product_configurator_mrp/models/product_config.py +++ b/product_configurator_mrp/models/product_config.py @@ -94,10 +94,9 @@ def create_get_bom(self, variant, product_tmpl_id=None, values=None): "product_qty": parent_bom_line.product_qty, } specs = self.get_onchange_specifications(model="mrp.bom.line") - updates = mrpBomLine.onchange( + mrpBomLine.onchange( parent_bom_line_vals, ["product_id", "product_qty"], specs ) - values2 = updates.get("value", {}) values2 = self.get_vals_to_write( values=values, model="mrp.bom.line" ) diff --git a/product_configurator_mrp/static/src/js/form_controller.esm.js b/product_configurator_mrp/static/src/js/form_controller.esm.js new file mode 100644 index 0000000000..eb39cd3a03 --- /dev/null +++ b/product_configurator_mrp/static/src/js/form_controller.esm.js @@ -0,0 +1,33 @@ +/** @odoo-module **/ + +import {FormController} from "@web/views/form/form_controller"; +import {formView} from "@web/views/form/form_view"; +import {registry} from "@web/core/registry"; +import {useService} from "@web/core/utils/hooks"; + +export class ProductConfiguratorFormController extends FormController { + setup() { + super.setup(); + this.action = useService("action"); + this.rpc = useService("rpc"); + this.orm = useService("orm"); + } + + async _onConfigure() { + const action = await this.orm.call("mrp.production", "action_config_start", []); + this.action.doAction(action); + } +} +ProductConfiguratorFormController.components = { + ...FormController.components, +}; + +export const ProductConfiguratorFormView = { + ...formView, + Controller: ProductConfiguratorFormController, + buttonTemplate: "product_configurator_mrp.FormButtons", +}; + +registry + .category("views") + .add("product_configurator_mrp_form", ProductConfiguratorFormView); diff --git a/product_configurator_mrp/static/src/js/form_controller.js b/product_configurator_mrp/static/src/js/form_controller.js deleted file mode 100644 index ef8dedaa05..0000000000 --- a/product_configurator_mrp/static/src/js/form_controller.js +++ /dev/null @@ -1,60 +0,0 @@ -odoo.define("product_configurator_mrp.FormController", function (require) { - "use strict"; - - var core = require("web.core"); - var FormController = require("web.FormController"); - var FormView = require("web.FormView"); - var viewRegistry = require("web.view_registry"); - - var qweb = core.qweb; - - var ConfigFormController = FormController.extend({ - buttons_template: "ConfigFormView.buttons", - events: _.extend({}, FormController.prototype.events, { - "click .o_form_button_create_config": "_onConfigure", - }), - - renderButtons: function () { - var self = this; - var $footer = this.footerToButtons ? this.renderer.$("footer") : null; - var mustRenderFooterButtons = $footer && $footer.length; - self._super.apply(this, arguments); - if (mustRenderFooterButtons); - else if ( - this.$buttons && - self.modelName === "mrp.production" && - self.initialState.context.custom_create_variant - ) { - var button_create = this.$buttons.find(".o_form_button_create"); - button_create.after( - qweb.render("ConfigFormView.buttons", { - widget: this, - }) - ); - this.$buttons - .find(".o_form_button_create_config") - .css("display", "inline"); - } - }, - - _onConfigure: function () { - var self = this; - return this._rpc({ - model: "mrp.production", - method: "action_config_start", - args: [""], - context: this.initialState.context, - }).then(function (result) { - self.do_action(result); - }); - }, - }); - - var ConfigFormView = FormView.extend({ - config: _.extend({}, FormView.prototype.config, { - Controller: ConfigFormController, - }), - }); - - viewRegistry.add("product_configurator_mrp_form", ConfigFormView); -}); diff --git a/product_configurator_mrp/static/src/js/kanban_controller.esm.js b/product_configurator_mrp/static/src/js/kanban_controller.esm.js new file mode 100644 index 0000000000..e0aa2bf4b9 --- /dev/null +++ b/product_configurator_mrp/static/src/js/kanban_controller.esm.js @@ -0,0 +1,32 @@ +/** @odoo-module **/ + +import {KanbanController} from "@web/views/kanban/kanban_controller"; +import {kanbanView} from "@web/views/kanban/kanban_view"; +import {registry} from "@web/core/registry"; +import {useService} from "@web/core/utils/hooks"; + +export class ProductConfiguratorKanbanController extends KanbanController { + setup() { + super.setup(); + this.action = useService("action"); + this.rpc = useService("rpc"); + this.orm = useService("orm"); + } + + async _onConfigure() { + const action = await this.orm.call("mrp.production", "action_config_start", []); + this.action.doAction(action); + } +} +ProductConfiguratorKanbanController.components = { + ...KanbanController.components, +}; + +export const ProductConfiguratorKanbanView = { + ...kanbanView, + Controller: ProductConfiguratorKanbanController, + buttonTemplate: "product_configurator_mrp.KanbanButtons", +}; +registry + .category("views") + .add("product_configurator_mrp_kanban", ProductConfiguratorKanbanView); diff --git a/product_configurator_mrp/static/src/js/kanban_controller.js b/product_configurator_mrp/static/src/js/kanban_controller.js deleted file mode 100644 index 9c78d42df9..0000000000 --- a/product_configurator_mrp/static/src/js/kanban_controller.js +++ /dev/null @@ -1,48 +0,0 @@ -odoo.define("product_configurator_mrp.KanbanController", function (require) { - "use strict"; - - var KanbanController = require("web.KanbanController"); - var KanbanView = require("web.KanbanView"); - var viewRegistry = require("web.view_registry"); - - var ConfigKanbanController = KanbanController.extend({ - buttons_template: "ConfigKanbanView.buttons", - events: _.extend({}, KanbanController.prototype.events, { - "click .o-kanban-button-new_config": "_onConfigure", - }), - - renderButtons: function () { - var self = this; - self._super.apply(this, arguments); - if ( - this.$buttons && - self.modelName === "mrp.production" && - self.initialState.context.custom_create_variant - ) { - this.$buttons - .find(".o-kanban-button-new_config") - .css("display", "inline"); - } - }, - - _onConfigure: function () { - var self = this; - return this._rpc({ - model: "mrp.production", - method: "action_config_start", - args: [""], - context: this.initialState.context, - }).then(function (result) { - self.do_action(result); - }); - }, - }); - - var ConfigKanbanView = KanbanView.extend({ - config: _.extend({}, KanbanView.prototype.config, { - Controller: ConfigKanbanController, - }), - }); - - viewRegistry.add("product_configurator_mrp_kanban", ConfigKanbanView); -}); diff --git a/product_configurator_mrp/static/src/js/list_controller.esm.js b/product_configurator_mrp/static/src/js/list_controller.esm.js new file mode 100644 index 0000000000..d9e11ce2cb --- /dev/null +++ b/product_configurator_mrp/static/src/js/list_controller.esm.js @@ -0,0 +1,34 @@ +/** @odoo-module **/ + +import {ListController} from "@web/views/list/list_controller"; +import {listView} from "@web/views/list/list_view"; +import {registry} from "@web/core/registry"; +import {useService} from "@web/core/utils/hooks"; + +export class ProductConfiguratorController extends ListController { + setup() { + super.setup(); + this.action = useService("action"); + this.rpc = useService("rpc"); + this.orm = useService("orm"); + } + + async _onConfigure() { + const action = await this.orm.call("mrp.production", "action_config_start", []); + this.action.doAction(action); + } +} + +ProductConfiguratorController.components = { + ...ListController.components, +}; + +export const ProductConfiguratorListView = { + ...listView, + Controller: ProductConfiguratorController, + buttonTemplate: "product_configurator_mrp.ListButtons", +}; + +registry + .category("views") + .add("product_configurator_mrp_tree", ProductConfiguratorListView); diff --git a/product_configurator_mrp/static/src/js/list_controller.js b/product_configurator_mrp/static/src/js/list_controller.js deleted file mode 100644 index 25087fccbc..0000000000 --- a/product_configurator_mrp/static/src/js/list_controller.js +++ /dev/null @@ -1,47 +0,0 @@ -odoo.define("product_configurator_mrp.ListController", function (require) { - "use strict"; - var ListController = require("web.ListController"); - var ListView = require("web.ListView"); - var viewRegistry = require("web.view_registry"); - - var ConfigListController = ListController.extend({ - buttons_template: "ConfigListView.buttons", - events: _.extend({}, ListController.prototype.events, { - "click .o_list_button_add_config": "_onConfigure", - }), - - renderButtons: function () { - var self = this; - self._super.apply(this, arguments); - if ( - this.$buttons && - self.modelName === "mrp.production" && - self.initialState.context.custom_create_variant - ) { - this.$buttons - .find(".o_list_button_add_config") - .css("display", "inline"); - } - }, - - _onConfigure: function () { - var self = this; - return this._rpc({ - model: "mrp.production", - method: "action_config_start", - args: [""], - context: this.initialState.context, - }).then(function (result) { - self.do_action(result); - }); - }, - }); - - var ConfigListView = ListView.extend({ - config: _.extend({}, ListView.prototype.config, { - Controller: ConfigListController, - }), - }); - - viewRegistry.add("product_configurator_mrp_tree", ConfigListView); -}); diff --git a/product_configurator_mrp/static/src/xml/mrp_production_views.xml b/product_configurator_mrp/static/src/xml/mrp_production_views.xml index c917792a0f..22aa204a20 100644 --- a/product_configurator_mrp/static/src/xml/mrp_production_views.xml +++ b/product_configurator_mrp/static/src/xml/mrp_production_views.xml @@ -1,38 +1,39 @@ - - - - - + + - - - - + - - + + + + diff --git a/product_configurator_mrp/tests/__init__.py b/product_configurator_mrp/tests/__init__.py index 56a9087930..b8d0e003b5 100644 --- a/product_configurator_mrp/tests/__init__.py +++ b/product_configurator_mrp/tests/__init__.py @@ -1,4 +1,4 @@ # Copyright (C) 2021 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -# from . import test_mrp +from . import test_mrp diff --git a/product_configurator_mrp/tests/test_mrp.py b/product_configurator_mrp/tests/test_mrp.py index 18688b9ef7..b012b69c97 100644 --- a/product_configurator_mrp/tests/test_mrp.py +++ b/product_configurator_mrp/tests/test_mrp.py @@ -3,124 +3,177 @@ from datetime import datetime -from odoo.addons.product_configurator.tests.test_product_configurator_test_cases import ( - ProductConfiguratorTestCases, -) +from odoo.addons.product_configurator.tests.common import ProductConfiguratorTestCases class TestMrp(ProductConfiguratorTestCases): def setUp(self): super(TestMrp, self).setUp() - self.mrpBomConfigSet = self.env["mrp.bom.line.configuration.set"] - self.mrpBomConfig = self.env["mrp.bom.line.configuration"] self.mrpBom = self.env["mrp.bom"] self.mrpBomLine = self.env["mrp.bom.line"] - self.mrpRoutingWorkcenter = self.env["mrp.routing.workcenter"] self.productProduct = self.env["product.product"] - self.productTemplate = self.env["product.template"] self.mrpProduction = self.env["mrp.production"] self.product_id = self.env.ref("product.product_product_3") - self.workcenter_id = self.env.ref("mrp.mrp_workcenter_3") + self.company = self.env.ref("base.main_company") - # create bom - self.bom_id = self.mrpBom.create( + self.selected_products = self.productProduct + self.selected_products |= self.env.ref( + "product_configurator.product_bmw_model_sport_line" + ) + self.selected_products |= self.env.ref( + "product_configurator.product_2_series_transmission_steptronic" + ) + self.selected_products |= self.env.ref( + "product_configurator.product_2_series_sunroof" + ) + self.selected_products |= self.env.ref( + "product_configurator.product_engine_220i_coupe" + ) + + self.selected_alt_products = self.productProduct + self.selected_alt_products |= self.env.ref( + "product_configurator.product_bmw_sport_line" + ) + self.selected_alt_products |= self.env.ref( + "product_configurator.product_2_series_transmission_steptronic" + ) + self.selected_alt_products |= self.env.ref( + "product_configurator.product_2_series_sunroof" + ) + self.selected_alt_products |= self.env.ref( + "product_configurator.product_engine_218i_coupe" + ) + + def _configure_alt_variant(self): + product_config_wizard = self.ProductConfWizard.create( { - "product_tmpl_id": self.product_id.product_tmpl_id.id, - "product_qty": 1.00, - "type": "normal", - "ready_to_produce": "all_available", + "product_tmpl_id": self.config_product.id, } ) - # create bom line - self.bom_line_id = self.mrpBomLine.create( + product_config_wizard.action_next_step() + product_config_wizard.write( { - "bom_id": self.bom_id.id, - "product_id": self.product_id.id, - "product_qty": 1.00, + "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, + "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, } ) - # create BOM operations line - self.mrpRoutingWorkcenter.create( + product_config_wizard.action_next_step() + product_config_wizard.write( { - "bom_id": self.bom_id.id, - "name": "Operation 1", - "workcenter_id": self.workcenter_id.id, + "__attribute_{}".format(self.attr_color.id): self.value_red.id, + "__attribute_{}".format(self.attr_rims.id): self.value_rims_378.id, } ) - - def test_00_skip_bom_line(self): - checkVal = self.mrpBomLine._skip_bom_line(product=self.product_id) - self.assertFalse( - checkVal, - "Error: If value exists\ - Method: _skip_bom_line()", - ) - self.bom_line_id.bom_id.config_ok = True - self.mrp_config_step = self.mrpBomConfigSet.create( + product_config_wizard.action_next_step() + product_config_wizard.write( { - "name": "TestConfigSet", + "__attribute_{}".format( + self.attr_model_line.id + ): self.value_sport_line.id, } ) - self.bom_line_id.write({"config_set_id": self.mrp_config_step.id}) - # create bom_line_config - self.mrp_bom_config = self.mrpBomConfig.create( + product_config_wizard.action_next_step() + product_config_wizard.write( { - "config_set_id": self.mrp_config_step.id, - "value_ids": [ - ( - 6, - 0, - [ - self.value_gasoline.id, - self.value_218i.id, - self.value_220i.id, - self.value_red.id, - ], - ) - ], + "__attribute_{}".format(self.attr_tapistry.id): self.value_tapistry.id, } ) - self.product_id.write( - {"attribute_value_ids": [(6, 0, self.mrp_bom_config.value_ids.ids)]} - ) - self.mrpProduction.create( + product_config_wizard.action_next_step() + product_config_wizard.write( { - "product_id": self.product_id.id, - "product_qty": 1.00, - "product_uom_id": 1.00, - "bom_id": self.bom_id.id, - "date_planned_start": datetime.now(), + "__attribute_{}".format( + self.attr_transmission.id + ): self.value_transmission.id, + "__attribute_{}".format(self.attr_options.id): [ + [6, 0, [self.value_options_2.id]] + ], } ) - self.mrpBomLine._skip_bom_line(product=self.product_id) - self.assertFalse( - checkVal, - "Error: If value exists\ - Method: _skip_bom_line()", + return product_config_wizard.with_context( + allowed_company_ids=[self.company.id] + ).action_next_step() + + def _get_product_id(self): + self._configure_product_nxt_step() + return self.config_product.product_variant_ids[-1] + + def test_00_generate_bom_from_parent(self): + variant = self._get_product_id() + bom_products = variant.variant_bom_ids.bom_line_ids.mapped("product_id") + + self.assertEqual( + bom_products, + self.selected_products, + "BOM was not generated correctly", ) def test_01_action_config_start(self): - mrpProduction = self.mrpProduction.create( + context = dict( + self.env.context, + wizard_model="product.configurator.mrp", + allowed_company_ids=[self.company.id], + ) + wizard_action = self.mrpProduction.with_context(**context).action_config_start() + self.assertEqual( + wizard_action.get("res_model"), + "product.configurator.mrp", + "Config wizard got the wrong model", + ) + + def test_02_generate_bom_from_values(self): + self.env.ref("product_configurator_mrp.bom_2_series").active = False + + variant = self._get_product_id() + bom_products = variant.variant_bom_ids.bom_line_ids.mapped("product_id") + + self.assertEqual( + bom_products, + self.selected_products, + "BOM was not generated correctly", + ) + + def test_03_reconfigure_product(self): + variant = self._get_product_id() + production = self.mrpProduction.create( { - "product_id": self.product_id.id, + "product_id": variant.id, "product_qty": 1.00, - "product_uom_id": 1.00, - "bom_id": self.bom_id.id, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + "bom_id": variant.variant_bom_ids.id, "date_planned_start": datetime.now(), } ) - context = dict( - self.env.context, - default_order_id=mrpProduction.id, - wizard_model="product.configurator.mrp", + + action = production.reconfigure_product() + wiz = self.env["product.configurator.mrp"].browse(action["res_id"]) + ctx = action["context"] + self.ProductConfWizard = wiz.with_context(**ctx) + self._configure_alt_variant() + + move_products = production.move_raw_ids.mapped("product_id") + self.assertEqual( + move_products, + self.selected_alt_products, + "Production BOM not generated correctly", ) - mrpProduction.action_config_start() - self.ProductConfWizard = self.env["product.configurator.mrp"].with_context( - **context + + def test_04_bom_missing_config_set(self): + # If the user fails to (or chooses not to) specify a config set for a BOM line, + # that BOM line will be included in all variant BOMs, even if not selected in + # the wizard + bom_line = self.env.ref("product_configurator_mrp.bom_line_engine_218i_coupe") + bom_line.config_set_id = False + + variant = self._get_product_id() + bom_products = variant.variant_bom_ids.bom_line_ids.mapped("product_id") + + selected_products = self.selected_products + selected_products |= self.env.ref( + "product_configurator.product_engine_218i_coupe" + ) + + self.assertEqual( + bom_products, + selected_products, + "BOM was not generated correctly", ) - self._configure_product_nxt_step() - # self.assertEqual( - # vals['res_id'], - # mrpProduction.product_id.id, - # 'Not Equal' - # )