diff --git a/product_profile_example/README.rst b/product_profile_example/README.rst index 2c862f3ce24..9d8803627ef 100644 --- a/product_profile_example/README.rst +++ b/product_profile_example/README.rst @@ -14,7 +14,7 @@ See its description. Configuration ============= -Optionnaly: +Optionally: * Settings > Configuration > General Settings: check 'Display fields in Profile' and apply (only if you want display hidden fields set by profile git ) @@ -39,6 +39,7 @@ Contributors ------------ * David BEAL +* Kevin KHAO Maintainer ---------- diff --git a/product_profile_example/__manifest__.py b/product_profile_example/__manifest__.py index 576ce054781..c6a5eb6a6e1 100644 --- a/product_profile_example/__manifest__.py +++ b/product_profile_example/__manifest__.py @@ -2,14 +2,14 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { + "license": "AGPL-3", "name": "Product Profile Example", "summary": "Product Profile Use Case", - "version": "10.0.1.0.1", + "version": "12.0.1.0.0", "author": "Akretion, Odoo Community Association (OCA)", "category": "product", - "depends": ["product_profile", "purchase", "point_of_sale", "mrp",], - "website": "http://www.akretion.com/", - "demo": ["demo/product.profile.csv", "demo/product_product_demo.xml",], + "depends": ["product_profile", "purchase_stock", "point_of_sale", "mrp"], + "website": "https://github.com/oca/product-attribute", + "demo": ["demo/product_profile.xml", "demo/product_product.xml"], "installable": True, - "license": "AGPL-3", } diff --git a/product_profile_example/demo/product.profile.csv b/product_profile_example/demo/product.profile.csv deleted file mode 100644 index 0a7c0330c08..00000000000 --- a/product_profile_example/demo/product.profile.csv +++ /dev/null @@ -1,20 +0,0 @@ -id,name,type,sale_ok,purchase_ok,profile_default_categ_id/id,available_in_pos,profile_default_route_ids/id,explanation -own,My Own Type Saleable,product,1,0,product.product_category_all,1,purchase.route_warehouse0_buy,"- My own type -- Saleable product -- Not purchasable" -consu_prof,Consumable,consu,1,0,product.product_category_all,0,purchase.route_warehouse0_buy,"- Consumable" -pos_pu_prof,POS & purchase,product,1,1,product.product_category_all,1,purchase.route_warehouse0_buy,"- Product in POS -- Product purchasable" -manuf_prof,Manufacturing TO,product,1,0,product.product_category_5,1,"mrp.route_warehouse0_manufacture,stock.route_warehouse0_mto","- Manufacturing Product -- Make to order product -- Not purchaseable -- Display on POS" -any_prof,Weird profile,product,0,1,product.product_category_all,1,,"- Purchasable -- Display on POS -- Not Saleable" -complete_prof,Complete profile,product,1,1,product.product_category_all,1,"mrp.route_warehouse0_manufacture,stock.route_warehouse0_mto,purchase.route_warehouse0_buy","- Stockable product -- Saleable -- Purchasable -- Available in POS -- MTO -- Manufacturable" diff --git a/product_profile_example/demo/product_product.xml b/product_profile_example/demo/product_product.xml new file mode 100644 index 00000000000..ee5f9098183 --- /dev/null +++ b/product_profile_example/demo/product_product.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/product_profile_example/demo/product_product_demo.xml b/product_profile_example/demo/product_product_demo.xml deleted file mode 100644 index 5fe546c52ce..00000000000 --- a/product_profile_example/demo/product_product_demo.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/product_profile_example/demo/product_profile.xml b/product_profile_example/demo/product_profile.xml new file mode 100644 index 00000000000..8921582698f --- /dev/null +++ b/product_profile_example/demo/product_profile.xml @@ -0,0 +1,57 @@ + + + + + My Own Type Saleable + product + True + False + + True + + - My own type +- Saleable product +- Not purchasable + + + + Complete Profile + product + True + True + + True + + - Stockable product +- Saleable +- Purchasable +- Available in POS +- MTO +- Manufacturable + + + + Manufacturing TO + product + True + False + + True + + - Manufacturing Product +- Make to order product +- Display on POS + + + + Consumable + consu + True + False + + False + Consumable + + + + diff --git a/product_profile_example/models/__init__.py b/product_profile_example/models/__init__.py index 37f3cd0267b..e923f538423 100644 --- a/product_profile_example/models/__init__.py +++ b/product_profile_example/models/__init__.py @@ -1,4 +1,4 @@ # © 2015 David BEAL @ Akretion # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import profile +from . import product_profile diff --git a/product_profile_example/models/profile.py b/product_profile_example/models/product_profile.py similarity index 93% rename from product_profile_example/models/profile.py rename to product_profile_example/models/product_profile.py index e735e59b369..c303565dd35 100644 --- a/product_profile_example/models/profile.py +++ b/product_profile_example/models/product_profile.py @@ -24,5 +24,5 @@ class ProductProfile(models.Model): "whether it will be bought, manufactured, MTO/MTS,...", ) profile_default_categ_id = fields.Many2one( - "product.category", string="Default category" + "product.category", string="Default category", required=True ) diff --git a/product_profile_example/static/description/icon.png b/product_profile_example/static/description/icon.png index 7c8b3dc5563..3a0328b516c 100644 Binary files a/product_profile_example/static/description/icon.png and b/product_profile_example/static/description/icon.png differ diff --git a/product_profile_example/static/description/product_profile_icon.png b/product_profile_example/static/description/product_profile_icon.png new file mode 100644 index 00000000000..7c8b3dc5563 Binary files /dev/null and b/product_profile_example/static/description/product_profile_icon.png differ diff --git a/product_profile_example/tests/test_profile.py b/product_profile_example/tests/test_profile.py index 6983a568930..d4f28fe9bfd 100644 --- a/product_profile_example/tests/test_profile.py +++ b/product_profile_example/tests/test_profile.py @@ -7,23 +7,57 @@ class TestProductProfile(TransactionCase): def setUp(self): super(TestProductProfile, self).setUp() + self._setup_obj_data() + self._setup_profile_data() + + def _setup_obj_data(self): + self.tmpl_m = self.env["product.template"] self.prd_m = self.env["product.product"] - # product 'HDD SH-2' in demo data - self.hard_disc_prd = self.env.ref("product.product_product_17") - self.my_own_profile = self.env.ref("product_profile_example.own") - self.manufacturing_prof = self.env.ref( - "product_profile_example.manuf_prof" - ) + # misc + self.desk_combination_prd = self.env.ref("product.product_product_3") self.analysis_tmpl = self.env.ref( - "point_of_sale.partner_product_7_product_template" + "product.expense_hotel_product_template" ) - self.analysis_prd = self.env.ref("point_of_sale.partner_product_7") + self.analysis_prd = self.env.ref("product.expense_hotel") self.theoritical_categ_id = self.env.ref("product.product_category_5") self.categ = self.env.ref("product.product_category_3") - def test_check_hard_disc_product(self): + def _setup_profile_data(self): + self.profile_own = self.env.ref("product_profile_example.profile_own") + self.profile_own_nondefaults = { + "type": "product", + "sale_ok": True, + "purchase_ok": False, + "available_in_pos": True, + } + self.profile_own_defaults = { + "categ_id": self.env.ref("product.product_category_all"), + "route_ids": self.env.ref("purchase_stock.route_warehouse0_buy"), + } + self.profile_complete = self.env.ref( + "product_profile_example.profile_complete" + ) + self.profile_complete_nondefaults = { + "type": "product", + "sale_ok": True, + "purchase_ok": True, + "available_in_pos": True, + } + self.profile_complete_defaults = { + "categ_id": self.env.ref("product.product_category_all"), + "route_ids": [ + self.env.ref("mrp.route_warehouse0_manufacture") + + self.env.ref("stock.route_warehouse0_mto") + + self.env.ref("purchase_stock.route_warehouse0_buy") + ], + } + self.profile_manuf = self.env.ref( + "product_profile_example.profile_manuf" + ) + + def test_check_desk_combination_product(self): # check route_ids - real_routes = [x.id for x in self.hard_disc_prd.route_ids] + real_routes = [x.id for x in self.desk_combination_prd.route_ids] theoritical_routes = [ self.env.ref("mrp.route_warehouse0_manufacture").id, self.env.ref("stock.route_warehouse0_mto").id, @@ -34,56 +68,148 @@ def test_check_hard_disc_product(self): # check categ_id theoritical_categ_id = self.theoritical_categ_id self.assertEqual( - self.hard_disc_prd.categ_id.id, theoritical_categ_id.id + self.desk_combination_prd.categ_id.id, theoritical_categ_id.id ) - def test_create_product(self): - name = "only name is specified" - vals = {"profile_id": self.my_own_profile.id, "name": name} - new_product = self.prd_m.create(vals) - new_product._onchange_from_profile() - count_prd = self.prd_m.search([("name", "=", name)]) + def test_on_create_template_with_profile(self): + """Creating a product with a profile applies all the profile's values""" + name = "template with 'own' profile" + vals = {"profile_id": self.profile_own.id, "name": name} + new_tmpl = self.tmpl_m.create(vals) + count_tmpl = self.tmpl_m.search([("name", "=", name)]).ids + self.assertEqual(len(count_tmpl), 1) + # test all values from profile are applied + for key in self.profile_own_nondefaults: + self.assertEqual( + self.profile_own_nondefaults[key], getattr(new_tmpl, key) + ) + for key in self.profile_own_defaults: + self.assertEqual( + self.profile_own_defaults[key], getattr(new_tmpl, key) + ) + + def test_on_create_product_with_profile(self): + """Creating a product with a profile applies all the profile's values""" + name = "product with 'own' profile" + vals = {"profile_id": self.profile_own.id, "name": name} + new_prd = self.prd_m.create(vals) + count_prd = self.prd_m.search([("name", "=", name)]).ids self.assertEqual(len(count_prd), 1) + # test all values from profile are applied + for key in self.profile_own_nondefaults: + self.assertEqual( + self.profile_own_nondefaults[key], getattr(new_prd, key) + ) + for key in self.profile_own_defaults: + self.assertEqual( + self.profile_own_defaults[key], getattr(new_prd, key) + ) + + def test_on_set_tmpl_profile(self): + """Test that setting a profile impacts: + - nondefaults in every case + - defaults only if there was no profile previously""" + + # set profile to "own" + vals = {"profile_id": self.profile_own.id} + self.analysis_tmpl.write(vals) + self.assertEqual(self.analysis_tmpl.profile_id, self.profile_own) + for key in self.profile_own_nondefaults: + self.assertEqual( + self.profile_own_nondefaults[key], + getattr(self.analysis_tmpl, key), + ) + for key in self.profile_own_defaults: + self.assertEqual( + self.profile_own_defaults[key], + getattr(self.analysis_tmpl, key), + ) + + # get vals on the template + defaults_keys = self.profile_own_defaults.keys() + defaults_tmpl_vals = { + key: self.analysis_tmpl[key] for key in defaults_keys + } - def test_write_template(self): - vals = {"profile_id": self.my_own_profile.id} + # set profile to "complete_prof" + vals = {"profile_id": self.profile_complete.id} self.analysis_tmpl.write(vals) - self.assertEqual(self.analysis_tmpl.profile_id, self.my_own_profile) + self.assertEqual(self.analysis_tmpl.profile_id, self.profile_complete) + for key in self.profile_complete_nondefaults: + self.assertEqual( + self.profile_complete_nondefaults[key], + getattr(self.analysis_tmpl, key), + ) + for key in self.profile_own_defaults: + self.assertEqual( + defaults_tmpl_vals[key], getattr(self.analysis_tmpl, key) + ) - def test_write_product(self): - vals = {"profile_id": self.my_own_profile.id} + def test_on_set_prd_profile(self): + """Test that setting a profile impacts: + - nondefaults in every case + - defaults only if there was no profile previously""" + + # set profile to "own" + vals = {"profile_id": self.profile_own.id} + self.analysis_prd.write(vals) + self.assertEqual(self.analysis_prd.profile_id, self.profile_own) + for key in self.profile_own_nondefaults: + self.assertEqual( + self.profile_own_nondefaults[key], + getattr(self.analysis_prd, key), + ) + for key in self.profile_own_defaults: + self.assertEqual( + self.profile_own_defaults[key], getattr(self.analysis_prd, key) + ) + + # get vals on the products + defaults_keys = self.profile_own_defaults.keys() + defaults_prd_vals = { + key: self.analysis_tmpl[key] for key in defaults_keys + } + + # set profile to "complete_prof" + vals = {"profile_id": self.profile_own.id} self.analysis_prd.write(vals) - self.assertEqual(self.analysis_prd.profile_id, self.my_own_profile) + self.assertEqual(self.analysis_prd.profile_id, self.profile_own) + for key in self.profile_complete_nondefaults: + self.assertEqual( + self.profile_own_nondefaults[key], + getattr(self.analysis_prd, key), + ) + for key in self.profile_complete_defaults: + self.assertEqual( + defaults_prd_vals[key], getattr(self.analysis_prd, key) + ) + + def test_on_write_profile_nondefaults(self): + """Writing on non-default profile fields should propagate + changes on products""" + product = self.env["product.product"].search( + [("profile_id", "=", self.profile_manuf.id)] + )[0] + self.profile_manuf.write({"purchase_ok": False}) + self.assertEqual(product.purchase_ok, False) + + def test_on_write_profile_defaults(self): + """Writing on default profile fields should not propagate + changes on products""" + product = self.env["product.product"].search( + [("profile_id", "=", self.profile_manuf.id)] + )[0] + self.profile_manuf.write({"profile_default_categ_id": self.categ.id}) + self.assertNotEqual(product.categ_id, self.categ) def test_product_tmpl_fields_view_get(self): + # test search filters loaded view_id = self.env.ref("product.product_template_search_view").id - res = self.hard_disc_prd.fields_view_get( + res = self.desk_combination_prd.fields_view_get( view_id=view_id, view_type="search" ) self.assertTrue( - 'string="My Own Type Saleable"' in res["arch"], + b'string="My Own Type Saleable"' in res["arch"], 'string="My Own Type Saleable" must be in ' "fields_view_get() output", ) - - def test_impact_write_profile_model(self): - """If profile is updated, products must be written too - on profile depends fields""" - self.manufacturing_prof.write({"type": "consu"}) - product = self.env["product.product"].search( - [("profile_id", "=", self.manufacturing_prof.id)] - )[0] - self.assertEqual(product.type, "consu") - - def test_default_behavior(self): - """Check if field prefixed with default_profile - have a default behavior on field values""" - categ = self.categ - consu_profile = self.env.ref("product_profile_example.consu_prof") - vals = { - "profile_id": consu_profile.id, - "categ_id": categ.id, - "name": "Product with modified category", - } - new_product = self.prd_m.create(vals) - self.assertEqual(new_product.categ_id, categ)