From 614dd2349ccc1d0b1ed5d0efd5242ddea7716f02 Mon Sep 17 00:00:00 2001 From: i1 Date: Fri, 6 May 2022 13:19:06 +0200 Subject: [PATCH] [14.0][ADD] Subcontractor Partner Management --- .../README.rst | 35 +++ .../__init__.py | 1 + .../__manifest__.py | 18 ++ .../models/__init__.py | 1 + .../models/res_partner.py | 267 ++++++++++++++++++ .../readme/CONFIGURE.rst | 1 + .../readme/CONTRIBUTORS.rst | 2 + .../readme/DESCRIPTION.rst | 8 + .../readme/HISTORY.rst | 4 + .../readme/USAGE.rst | 4 + .../static/description/icon.png | Bin 0 -> 28872 bytes .../tests/__init__.py | 3 + ...t_create_sybcontractor_partner_location.py | 170 +++++++++++ .../views/res_partner.xml | 20 ++ .../mrp_subcontracting_partner_management | 1 + .../setup.py | 6 + 16 files changed, 541 insertions(+) create mode 100644 mrp_subcontracting_partner_management/README.rst create mode 100644 mrp_subcontracting_partner_management/__init__.py create mode 100644 mrp_subcontracting_partner_management/__manifest__.py create mode 100644 mrp_subcontracting_partner_management/models/__init__.py create mode 100644 mrp_subcontracting_partner_management/models/res_partner.py create mode 100644 mrp_subcontracting_partner_management/readme/CONFIGURE.rst create mode 100644 mrp_subcontracting_partner_management/readme/CONTRIBUTORS.rst create mode 100644 mrp_subcontracting_partner_management/readme/DESCRIPTION.rst create mode 100644 mrp_subcontracting_partner_management/readme/HISTORY.rst create mode 100644 mrp_subcontracting_partner_management/readme/USAGE.rst create mode 100644 mrp_subcontracting_partner_management/static/description/icon.png create mode 100644 mrp_subcontracting_partner_management/tests/__init__.py create mode 100644 mrp_subcontracting_partner_management/tests/test_create_sybcontractor_partner_location.py create mode 100644 mrp_subcontracting_partner_management/views/res_partner.xml create mode 120000 setup/mrp_subcontracting_partner_management/odoo/addons/mrp_subcontracting_partner_management create mode 100644 setup/mrp_subcontracting_partner_management/setup.py diff --git a/mrp_subcontracting_partner_management/README.rst b/mrp_subcontracting_partner_management/README.rst new file mode 100644 index 000000000..38929e877 --- /dev/null +++ b/mrp_subcontracting_partner_management/README.rst @@ -0,0 +1,35 @@ +**This file is going to be generated by oca-gen-addon-readme.** + +*Manual changes will be overwritten.* + +Please provide content in the ``readme`` directory: + +* **DESCRIPTION.rst** (required) +* INSTALL.rst (optional) +* CONFIGURE.rst (optional) +* **USAGE.rst** (optional, highly recommended) +* DEVELOP.rst (optional) +* ROADMAP.rst (optional) +* HISTORY.rst (optional, recommended) +* **CONTRIBUTORS.rst** (optional, highly recommended) +* CREDITS.rst (optional) + +Content of this README will also be drawn from the addon manifest, +from keys such as name, authors, maintainers, development_status, +and license. + +A good, one sentence summary in the manifest is also highly recommended. + + +Automatic changelog generation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`HISTORY.rst` can be auto generated using `towncrier `_. + +Just put towncrier compatible changelog fragments into `readme/newsfragments` +and the changelog file will be automatically generated and updated when a new fragment is added. + +Please refer to `towncrier` documentation to know more. + +NOTE: the changelog will be automatically generated when using `/ocabot merge $option`. +If you need to run it manually, refer to `OCA/maintainer-tools README `_. diff --git a/mrp_subcontracting_partner_management/__init__.py b/mrp_subcontracting_partner_management/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/mrp_subcontracting_partner_management/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mrp_subcontracting_partner_management/__manifest__.py b/mrp_subcontracting_partner_management/__manifest__.py new file mode 100644 index 000000000..e6a86df1f --- /dev/null +++ b/mrp_subcontracting_partner_management/__manifest__.py @@ -0,0 +1,18 @@ +{ + "name": "Subcontracting Partner Management", + "version": "14.0.1.0.0", + "summary": "Subcontracting Partner Management", + "author": "Ooops404, Cetmix, Odoo Community Association (OCA)", + "license": "LGPL-3", + "category": "Inventory", + "website": "https://github.com/OCA/manufacture", + "depends": ["purchase_stock", "mrp_subcontracting", "sale_stock"], + "external_dependencies": {}, + "demo": [], + "data": [ + "views/res_partner.xml", + ], + "qweb": [], + "installable": True, + "application": False, +} diff --git a/mrp_subcontracting_partner_management/models/__init__.py b/mrp_subcontracting_partner_management/models/__init__.py new file mode 100644 index 000000000..91fed54d4 --- /dev/null +++ b/mrp_subcontracting_partner_management/models/__init__.py @@ -0,0 +1 @@ +from . import res_partner diff --git a/mrp_subcontracting_partner_management/models/res_partner.py b/mrp_subcontracting_partner_management/models/res_partner.py new file mode 100644 index 000000000..ec3393403 --- /dev/null +++ b/mrp_subcontracting_partner_management/models/res_partner.py @@ -0,0 +1,267 @@ +from odoo import api, fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + is_subcontractor_partner = fields.Boolean(string="Subcontractor") + subcontracted_created_location_id = fields.Many2one("stock.location") + partner_picking_type_id = fields.Many2one("stock.picking.type") + partner_buy_rule_id = fields.Many2one("stock.rule") + partner_resupply_rule_id = fields.Many2one("stock.rule") + + def _set_subcontracting_values_active(self, active): + self.ensure_one() + if self.subcontracted_created_location_id: + self.subcontracted_created_location_id.active = active + if self.partner_picking_type_id: + self.partner_picking_type_id.active = active + if self.partner_buy_rule_id: + self.partner_buy_rule_id.active = active + if self.partner_resupply_rule_id: + self.partner_resupply_rule_id.active = active + + def unlink(self): + """ + This Method is override to archive all subcotracting field + """ + for record in self: + record._set_subcontracting_values_active(False) + result = super(ResPartner, self).unlink() + return result + + def write(self, values): + for record in self: + is_subcontractor_partner = values.get("is_subcontractor_partner") + active = values.get("active") + if is_subcontractor_partner is not None: + values.update(record._update_subcontractor_entities_for_record(values)) + if active is not None: + record._set_subcontracting_values_active(active) + super(ResPartner, self).write(values) + + @api.model + def create(self, values): + partner = super(ResPartner, self).create(values) + if values.get("is_subcontractor_partner", False): + partner._create_subcontractor_entities() + return partner + + def _create_location(self, parent_location, company): + """Creating Subcontracting Location starts here""" + + name = "Subcontractor {}".format(self.name) + location_vals = { + "name": name, + "usage": "internal", + "location_id": parent_location or False, + "company_id": company.id, + "active": True, + } + location_rec = self.subcontracted_created_location_id + if not location_rec: + location_rec = self.env["stock.location"].create(location_vals) + return location_rec + + def _create_subcontracted_operation_type(self, warehouse, location): + """Creating Operation Type for Subcontracting""" + first_name = self.name.split(" ")[0] or "" + operation_type_name = "Subcontractor {} {}".format(str(first_name), " IN") + sequence_code = "" + for code in list(filter(None, operation_type_name.split(" "))): + sequence_code += code[0] + operation_type_rec = self.partner_picking_type_id + if not operation_type_rec: + operation_type_vals = { + "name": operation_type_name, + "code": "incoming", + "sequence_code": sequence_code, + } + if warehouse: + operation_type_vals.update({"warehouse_id": warehouse.id}) + if location: + operation_type_vals.update({"default_location_dest_id": location.id}) + operation_type_rec = self.env["stock.picking.type"].create( + operation_type_vals + ) + return operation_type_rec + + def _create_subcontracted_buy_rule(self, operation_type_rec, location): + """Creating Route Rule for Subcontracting starts here""" + first_name = self.name.split(" ")[0] or "" + buy_route = self.env.ref( + "purchase_stock.route_warehouse0_buy", raise_if_not_found=False + ) + rule_vals = { + "name": "Subcontractor {}".format(first_name), + "action": "buy", + } + rule = self.partner_buy_rule_id + if operation_type_rec: + rule_vals.update({"picking_type_id": operation_type_rec.id}) + if location: + rule_vals.update({"location_id": location.id}) + if buy_route: + rule_vals.update({"route_id": buy_route.id}) + if not rule and rule_vals: + rule = self.env["stock.rule"].create(rule_vals) + return rule + + def _create_subcontracted_resupply_rule(self, location): + """# Creating Route Rule for Subcontracting resupply on order starts here""" + first_name = self.name.split(" ")[0] or "" + resupply_on_order_route = self.env.ref( + "mrp_subcontracting.route_resupply_subcontractor_mto", + raise_if_not_found=False, + ) + delivery_type = self.env.ref("stock.picking_type_out", raise_if_not_found=False) + production = self.env["ir.property"]._get( + "property_stock_production", "product.template" + ) + resupply_rule_vals = { + "name": "Subcontractor {}".format(first_name), + "action": "pull", + "partner_address_id": self._origin.id, + } + pull_rule = self.partner_resupply_rule_id + if delivery_type: + resupply_rule_vals.update( + { + "picking_type_id": delivery_type.id, + } + ) + if location: + resupply_rule_vals.update( + { + "location_id": location.id, + } + ) + if production: + resupply_rule_vals.update( + { + "location_src_id": production.id, + } + ) + if resupply_on_order_route: + resupply_rule_vals.update( + { + "route_id": resupply_on_order_route.id, + } + ) + + if not pull_rule and resupply_rule_vals: + pull_rule = self.env["stock.rule"].create(resupply_rule_vals) + return pull_rule + + def _create_subcontractor_entities(self): + """ + Create entities for the subcontractor + - Stock location + - Stock operation type + - "Buy" stock rule + """ + for rec in self.filtered(lambda p: p.company_type == "company"): + partner_update_vals = rec._create_subcontractor_entities_for_record() + rec.write(partner_update_vals) + + def _update_subcontractor_entities_for_record(self, values): + self.ensure_one() + is_subcontractor_partner = values.get("is_subcontractor_partner") + + check_data = { + # Updating Subcontracting Location + "subcontracted_created_location_id": self._create_subcontracting_location_data, + # Updating Subcontracting operation type + "partner_picking_type_id": self._create_operation_type_for_subcontracting, + # Updating Route Rule for Subcontracting buy + "partner_buy_rule_id": self._create_route_rule_for_subcontracting, + # Updating Route Rule for Subcontracting resupply + "partner_resupply_rule_id": self._create_route_rule_for_subcontracting_resupply, + } + for field_name in check_data: + if is_subcontractor_partner is True and getattr(self, field_name): + getattr(self, field_name).active = True + elif is_subcontractor_partner is True and not getattr(self, field_name): + values.update(check_data[field_name]()) + elif is_subcontractor_partner is False and getattr(self, field_name): + getattr(self, field_name).active = False + + return values + + def _create_subcontractor_entities_for_record(self): + self.ensure_one() + partner_update_vals = {"is_subcontractor_partner": True} + # Creating Subcontracting Location ends here + partner_update_vals.update(self._create_subcontracting_location_data()) + partner_update_vals.update(self._create_operation_type_for_subcontracting()) + # Creating Route Rule for Subcontracting starts here + partner_update_vals.update(self._create_route_rule_for_subcontracting()) + # Creating Route Rule for Subcontracting resupply on order starts here + partner_update_vals.update( + self._create_route_rule_for_subcontracting_resupply() + ) + return partner_update_vals + + def _get_location_for_record(self): + self.ensure_one() + location = self.subcontracted_created_location_id + if not location: + default_company = self.env.company + company = self.company_id or default_company + parent_location = ( + company.subcontracting_location_id + and company.subcontracting_location_id.id + ) + location = self._create_location(parent_location, company) + self.subcontracted_created_location_id = location + return location + + def _get_warehouse_for_record(self): + self.ensure_one() + default_company = self.env.company + default_warehouse = self.env["stock.warehouse"].search( + [("company_id", "=", default_company.id)] + )[0] + company = self.company_id or default_company + warehouse = ( + self.env["stock.warehouse"].search([("company_id", "=", company.id)])[0] + if self.company_id + else default_warehouse + ) # noqa + return warehouse + + def _create_subcontracting_location_data(self): + self.ensure_one() + location = self._get_location_for_record() + return { + "property_stock_subcontractor": location.id, + "subcontracted_created_location_id": location.id, + } + + def _create_operation_type_for_subcontracting(self): + self.ensure_one() + operation_type_rec = self.partner_picking_type_id + if not operation_type_rec: + # Creating Operation Type for Subcontracting starts here + location = self._get_location_for_record() + warehouse = self._get_warehouse_for_record() + operation_type_rec = self._create_subcontracted_operation_type( + warehouse, location + ) + self.partner_picking_type_id = operation_type_rec + return {"partner_picking_type_id": operation_type_rec.id} + + def _create_route_rule_for_subcontracting(self): + location = self._get_location_for_record() + warehouse = self._get_warehouse_for_record() + operation_type_rec = self._create_subcontracted_operation_type( + warehouse, location + ) + buy_rule = self._create_subcontracted_buy_rule(operation_type_rec, location) + + return {"partner_buy_rule_id": buy_rule.id} + + def _create_route_rule_for_subcontracting_resupply(self): + location = self._get_location_for_record() + resupply_rule = self._create_subcontracted_resupply_rule(location) + return {"partner_resupply_rule_id": resupply_rule.id} diff --git a/mrp_subcontracting_partner_management/readme/CONFIGURE.rst b/mrp_subcontracting_partner_management/readme/CONFIGURE.rst new file mode 100644 index 000000000..0862077f4 --- /dev/null +++ b/mrp_subcontracting_partner_management/readme/CONFIGURE.rst @@ -0,0 +1 @@ +* No configuration is required diff --git a/mrp_subcontracting_partner_management/readme/CONTRIBUTORS.rst b/mrp_subcontracting_partner_management/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..a3385a623 --- /dev/null +++ b/mrp_subcontracting_partner_management/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Ooops404 +* Cetmix diff --git a/mrp_subcontracting_partner_management/readme/DESCRIPTION.rst b/mrp_subcontracting_partner_management/readme/DESCRIPTION.rst new file mode 100644 index 000000000..fe086ffb2 --- /dev/null +++ b/mrp_subcontracting_partner_management/readme/DESCRIPTION.rst @@ -0,0 +1,8 @@ +The goal of this module is to simplify the management of the partner properties used in MRP Subcontracting. + +It adds a new checkbox "Subcontractor" which when enabled creates the following entities: + +* A child location in the "Subcontracting" location +* A Stock Operation Type of type 'receipt' for this location +* A new 'Buy' stock rule +* A new 'Resupply Subcontractor on Order' rule diff --git a/mrp_subcontracting_partner_management/readme/HISTORY.rst b/mrp_subcontracting_partner_management/readme/HISTORY.rst new file mode 100644 index 000000000..a7312928a --- /dev/null +++ b/mrp_subcontracting_partner_management/readme/HISTORY.rst @@ -0,0 +1,4 @@ +14.0.1.0.0 +~~~~~~~~~~ + +* Initial release diff --git a/mrp_subcontracting_partner_management/readme/USAGE.rst b/mrp_subcontracting_partner_management/readme/USAGE.rst new file mode 100644 index 000000000..b5502316a --- /dev/null +++ b/mrp_subcontracting_partner_management/readme/USAGE.rst @@ -0,0 +1,4 @@ +* Select a partner of type "Company" +* Enable the "Subcontractor" checkbox +* New entities are created or existing are used if were created previously +* When disabled all associated enties will be archived diff --git a/mrp_subcontracting_partner_management/static/description/icon.png b/mrp_subcontracting_partner_management/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ea708b0a29ee106a8799f18ff661aa6641f6478c GIT binary patch literal 28872 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 literal 0 HcmV?d00001 diff --git a/mrp_subcontracting_partner_management/tests/__init__.py b/mrp_subcontracting_partner_management/tests/__init__.py new file mode 100644 index 000000000..c7819b815 --- /dev/null +++ b/mrp_subcontracting_partner_management/tests/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_create_sybcontractor_partner_location diff --git a/mrp_subcontracting_partner_management/tests/test_create_sybcontractor_partner_location.py b/mrp_subcontracting_partner_management/tests/test_create_sybcontractor_partner_location.py new file mode 100644 index 000000000..edf060089 --- /dev/null +++ b/mrp_subcontracting_partner_management/tests/test_create_sybcontractor_partner_location.py @@ -0,0 +1,170 @@ +from odoo.tests import common, tagged + + +@tagged("post_install", "-at_install") +class TestSubcontractedPartner(common.SavepointCase): + @classmethod + def setUpClass(cls): + """ + - Create a Partner record “Wood Corner” + - Type will be Company and new boolean is_subcontractor_partner is Set True + """ + super().setUpClass() + cls.partner_id = cls.env.ref("base.res_partner_12") + cls.partner_obj = cls.env["res.partner"] + + def _get_partner(self): + return self.partner_obj.create( + { + "name": "Test partner", + "is_company": True, + "is_subcontractor_partner": True, + } + ) + + def test_is_subcontractor_partner_first_time(self): + self.partner_id.update( + { + "is_subcontractor_partner": True, + } + ) + + location = self.partner_id.subcontracted_created_location_id + self.assertTrue(location, "Location is not created") + self.assertTrue(location.active, "Location must be active") + + partner_picking_type = self.partner_id.partner_picking_type_id + self.assertTrue(partner_picking_type, "Picking type is not created") + self.assertTrue(partner_picking_type.active, "Picking type must be active") + + partner_buy_rule = self.partner_id.partner_buy_rule_id + self.assertTrue(partner_buy_rule, "Partner Buy rule is not created") + self.assertTrue(partner_buy_rule.active, "Partner Buy rule must be active") + + partner_resupply_rule = self.partner_id.partner_resupply_rule_id + self.assertTrue(partner_resupply_rule, "Partner Resupply rule is not created") + self.assertTrue( + partner_resupply_rule.active, "Partner Resupply rule must be active" + ) + + def test_is_subcontractor_partner_switch_off(self): + self.partner_id.write( + { + "is_subcontractor_partner": True, + } + ) + self.partner_id.update( + { + "is_subcontractor_partner": False, + } + ) + + location = self.partner_id.subcontracted_created_location_id + self.assertFalse(location.active, "Location must be not active") + + partner_picking_type = self.partner_id.partner_picking_type_id + self.assertFalse(partner_picking_type.active, "Picking type must be not active") + + partner_buy_rule = self.partner_id.partner_buy_rule_id + self.assertFalse(partner_buy_rule.active, "Partner Buy rule must be not active") + + partner_resupply_rule = self.partner_id.partner_resupply_rule_id + self.assertFalse( + partner_resupply_rule.active, "Partner Resupply rule must be not active" + ) + + def test_is_subcontractor_partner_switch_on(self): + self.partner_id.update( + { + "is_subcontractor_partner": True, + } + ) + + location = self.partner_id.subcontracted_created_location_id + self.assertTrue(location.active, "Location must be active") + + partner_picking_type = self.partner_id.partner_picking_type_id + self.assertTrue(partner_picking_type.active, "Picking type must be active") + + partner_buy_rule = self.partner_id.partner_buy_rule_id + self.assertTrue(partner_buy_rule.active, "Partner Buy rule must be active") + + partner_resupply_rule = self.partner_id.partner_resupply_rule_id + self.assertTrue( + partner_resupply_rule.active, "Partner Resupply rule must be active" + ) + + def test_is_subcontractor_partner_aсtive_switch_off(self): + self.partner_id.write( + { + "is_subcontractor_partner": True, + } + ) + self.partner_id.update( + { + "active": False, + } + ) + + location = self.partner_id.subcontracted_created_location_id + self.assertFalse(location.active, "Location must be not active") + + partner_picking_type = self.partner_id.partner_picking_type_id + self.assertFalse(partner_picking_type.active, "Picking type must be not active") + + partner_buy_rule = self.partner_id.partner_buy_rule_id + self.assertFalse(partner_buy_rule.active, "Partner Buy rule must be not active") + + partner_resupply_rule = self.partner_id.partner_resupply_rule_id + self.assertFalse( + partner_resupply_rule.active, "Partner Resupply rule must be not active" + ) + + def test_is_subcontractor_partner_aсtive_switch_on(self): + self.partner_id.write( + { + "is_subcontractor_partner": True, + } + ) + self.partner_id.write( + { + "active": True, + } + ) + + location = self.partner_id.subcontracted_created_location_id + self.assertTrue(location.active, "Location must be active") + + partner_picking_type = self.partner_id.partner_picking_type_id + self.assertTrue(partner_picking_type.active, "Picking type must be active") + + partner_buy_rule = self.partner_id.partner_buy_rule_id + self.assertTrue(partner_buy_rule.active, "Partner Buy rule must be active") + + partner_resupply_rule = self.partner_id.partner_resupply_rule_id + self.assertTrue( + partner_resupply_rule.active, "Partner Resupply rule must be active" + ) + + def test_is_subcontractor_partner_delete(self): + partner_id = self.partner_obj.create( + { + "name": "Test partner", + "is_company": True, + "is_subcontractor_partner": True, + } + ) + + location = partner_id.subcontracted_created_location_id + partner_picking_type = partner_id.partner_picking_type_id + partner_buy_rule = partner_id.partner_buy_rule_id + partner_resupply_rule = partner_id.partner_resupply_rule_id + + partner_id.unlink() + + self.assertFalse(location.active, "Location must be not active") + self.assertFalse(partner_picking_type.active, "Picking type must be not active") + self.assertFalse(partner_buy_rule.active, "Partner Buy rule must be not active") + self.assertFalse( + partner_resupply_rule.active, "Partner Resupply rule must be not active" + ) diff --git a/mrp_subcontracting_partner_management/views/res_partner.xml b/mrp_subcontracting_partner_management/views/res_partner.xml new file mode 100644 index 000000000..77f89331e --- /dev/null +++ b/mrp_subcontracting_partner_management/views/res_partner.xml @@ -0,0 +1,20 @@ + + + + + res.partner.form.inherit.subcontractor + res.partner + + + + + + + + + + diff --git a/setup/mrp_subcontracting_partner_management/odoo/addons/mrp_subcontracting_partner_management b/setup/mrp_subcontracting_partner_management/odoo/addons/mrp_subcontracting_partner_management new file mode 120000 index 000000000..cb8463957 --- /dev/null +++ b/setup/mrp_subcontracting_partner_management/odoo/addons/mrp_subcontracting_partner_management @@ -0,0 +1 @@ +../../../../mrp_subcontracting_partner_management \ No newline at end of file diff --git a/setup/mrp_subcontracting_partner_management/setup.py b/setup/mrp_subcontracting_partner_management/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/mrp_subcontracting_partner_management/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)