From 39a8e09d46518e8f775f693914635c2c9c2451d2 Mon Sep 17 00:00:00 2001 From: i1 Date: Fri, 6 May 2022 13:19:06 +0200 Subject: [PATCH 01/12] [14.0][ADD] Subcontractor Partner Management [UPD] Update mrp_subcontracting_partner_management.pot [UPD] README.rst --- .../README.rst | 103 ++++ .../__init__.py | 1 + .../__manifest__.py | 18 + .../mrp_subcontracting_partner_management.pot | 65 +++ .../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 .../static/description/index.html | 458 ++++++++++++++++++ .../tests/__init__.py | 3 + ...t_create_sybcontractor_partner_location.py | 170 +++++++ .../views/res_partner.xml | 20 + 16 files changed, 1125 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/i18n/mrp_subcontracting_partner_management.pot 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/static/description/index.html 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 diff --git a/mrp_subcontracting_partner_management/README.rst b/mrp_subcontracting_partner_management/README.rst new file mode 100644 index 000000000..fa0923840 --- /dev/null +++ b/mrp_subcontracting_partner_management/README.rst @@ -0,0 +1,103 @@ +================================= +Subcontracting Partner Management +================================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/14.0/mrp_subcontracting_partner_management + :alt: OCA/manufacture +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_subcontracting_partner_management + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/129/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +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 + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +* No configuration is required + +Usage +===== + +* 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 + +Changelog +========= + +14.0.1.0.0 +~~~~~~~~~~ + +* Initial release + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Ooops404 +* Cetmix + +Contributors +~~~~~~~~~~~~ + +* Ooops404 +* Cetmix + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/manufacture `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. 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/i18n/mrp_subcontracting_partner_management.pot b/mrp_subcontracting_partner_management/i18n/mrp_subcontracting_partner_management.pot new file mode 100644 index 000000000..77fb1b451 --- /dev/null +++ b/mrp_subcontracting_partner_management/i18n/mrp_subcontracting_partner_management.pot @@ -0,0 +1,65 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_subcontracting_partner_management +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model,name:mrp_subcontracting_partner_management.model_res_partner +msgid "Contact" +msgstr "" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__display_name +msgid "Display Name" +msgstr "" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__id +msgid "ID" +msgstr "" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner____last_update +msgid "Last Modified on" +msgstr "" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_buy_rule_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_buy_rule_id +msgid "Partner Buy Rule" +msgstr "" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_picking_type_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_picking_type_id +msgid "Partner Picking Type" +msgstr "" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_resupply_rule_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_resupply_rule_id +msgid "Partner Resupply Rule" +msgstr "" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__subcontracted_created_location_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__subcontracted_created_location_id +msgid "Subcontracted Created Location" +msgstr "" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__is_subcontractor_partner +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__is_subcontractor_partner +#: model_terms:ir.ui.view,arch_db:mrp_subcontracting_partner_management.view_partner_form_inherit_subcontractor +msgid "Subcontractor" +msgstr "" 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/static/description/index.html b/mrp_subcontracting_partner_management/static/description/index.html new file mode 100644 index 000000000..5a1fa28e7 --- /dev/null +++ b/mrp_subcontracting_partner_management/static/description/index.html @@ -0,0 +1,458 @@ + + + + + + +Subcontracting Partner Management + + + +
+

Subcontracting Partner Management

+ + +

Beta License: LGPL-3 OCA/manufacture Translate me on Weblate Try me on Runbot

+

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
  • +
+

Table of contents

+ +
+

Configuration

+
    +
  • No configuration is required
  • +
+
+
+

Usage

+
    +
  • 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
  • +
+
+
+

Changelog

+
+

14.0.1.0.0

+
    +
  • Initial release
  • +
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ooops404
  • +
  • Cetmix
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/manufacture project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + 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 + + + + + + + + + + From fd1b6e6cf704be108fe4cfc79fc16ae7fc91e519 Mon Sep 17 00:00:00 2001 From: i1 Date: Fri, 1 Jul 2022 12:40:24 +0200 Subject: [PATCH 02/12] [FIX] set correct values when duplicating partner [IMP] res.partner: Use function to compose entiry name stock.picking.type: Boolean field "is_subcontractor" for UI filters [UPD] Update mrp_subcontracting_partner_management.pot [UPD] README.rst mrp_subcontracting_partner_management 14.0.1.0.1 --- .../README.rst | 6 + .../__manifest__.py | 3 +- .../mrp_subcontracting_partner_management.pot | 14 +++ .../models/__init__.py | 1 + .../models/res_partner.py | 106 +++++++++--------- .../models/stock_picking_type.py | 7 ++ .../readme/HISTORY.rst | 6 + .../static/description/index.html | 42 ++++--- ...t_create_sybcontractor_partner_location.py | 13 +++ .../views/stock_picking_type.xml | 19 ++++ 10 files changed, 143 insertions(+), 74 deletions(-) create mode 100644 mrp_subcontracting_partner_management/models/stock_picking_type.py create mode 100644 mrp_subcontracting_partner_management/views/stock_picking_type.xml diff --git a/mrp_subcontracting_partner_management/README.rst b/mrp_subcontracting_partner_management/README.rst index fa0923840..db05b59b3 100644 --- a/mrp_subcontracting_partner_management/README.rst +++ b/mrp_subcontracting_partner_management/README.rst @@ -55,6 +55,12 @@ Usage Changelog ========= +14.0.1.0.1 +~~~~~~~~~~ + +**Bugfixes** +- Fixed duplicate rules when creating a subcontractor partner + 14.0.1.0.0 ~~~~~~~~~~ diff --git a/mrp_subcontracting_partner_management/__manifest__.py b/mrp_subcontracting_partner_management/__manifest__.py index e6a86df1f..593232e97 100644 --- a/mrp_subcontracting_partner_management/__manifest__.py +++ b/mrp_subcontracting_partner_management/__manifest__.py @@ -1,6 +1,6 @@ { "name": "Subcontracting Partner Management", - "version": "14.0.1.0.0", + "version": "14.0.1.0.1", "summary": "Subcontracting Partner Management", "author": "Ooops404, Cetmix, Odoo Community Association (OCA)", "license": "LGPL-3", @@ -11,6 +11,7 @@ "demo": [], "data": [ "views/res_partner.xml", + "views/stock_picking_type.xml", ], "qweb": [], "installable": True, diff --git a/mrp_subcontracting_partner_management/i18n/mrp_subcontracting_partner_management.pot b/mrp_subcontracting_partner_management/i18n/mrp_subcontracting_partner_management.pot index 77fb1b451..f5c4451d0 100644 --- a/mrp_subcontracting_partner_management/i18n/mrp_subcontracting_partner_management.pot +++ b/mrp_subcontracting_partner_management/i18n/mrp_subcontracting_partner_management.pot @@ -20,16 +20,24 @@ msgstr "" #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__display_name +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__display_name msgid "Display Name" msgstr "" #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__id msgid "ID" msgstr "" +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__is_subcontractor +msgid "Is Subcontractor" +msgstr "" + #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner____last_update +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type____last_update msgid "Last Modified on" msgstr "" @@ -51,6 +59,11 @@ msgstr "" msgid "Partner Resupply Rule" msgstr "" +#. module: mrp_subcontracting_partner_management +#: model:ir.model,name:mrp_subcontracting_partner_management.model_stock_picking_type +msgid "Picking Type" +msgstr "" + #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__subcontracted_created_location_id #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__subcontracted_created_location_id @@ -61,5 +74,6 @@ msgstr "" #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__is_subcontractor_partner #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__is_subcontractor_partner #: model_terms:ir.ui.view,arch_db:mrp_subcontracting_partner_management.view_partner_form_inherit_subcontractor +#: model_terms:ir.ui.view,arch_db:mrp_subcontracting_partner_management.view_pickingtype_filter msgid "Subcontractor" msgstr "" diff --git a/mrp_subcontracting_partner_management/models/__init__.py b/mrp_subcontracting_partner_management/models/__init__.py index 91fed54d4..2bfe5d3f3 100644 --- a/mrp_subcontracting_partner_management/models/__init__.py +++ b/mrp_subcontracting_partner_management/models/__init__.py @@ -1 +1,2 @@ from . import res_partner +from . import stock_picking_type diff --git a/mrp_subcontracting_partner_management/models/res_partner.py b/mrp_subcontracting_partner_management/models/res_partner.py index ec3393403..585ba2aa5 100644 --- a/mrp_subcontracting_partner_management/models/res_partner.py +++ b/mrp_subcontracting_partner_management/models/res_partner.py @@ -5,10 +5,20 @@ 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") + subcontracted_created_location_id = fields.Many2one( + copy=False, comodel_name="stock.location" + ) + partner_picking_type_id = fields.Many2one( + copy=False, comodel_name="stock.picking.type" + ) + partner_buy_rule_id = fields.Many2one( + copy=False, + comodel_name="stock.rule", + ) + partner_resupply_rule_id = fields.Many2one( + copy=False, + comodel_name="stock.rule", + ) def _set_subcontracting_values_active(self, active): self.ensure_one() @@ -23,7 +33,7 @@ class ResPartner(models.Model): def unlink(self): """ - This Method is override to archive all subcotracting field + This Method is override to archive all subcontracting field """ for record in self: record._set_subcontracting_values_active(False) @@ -47,12 +57,19 @@ class ResPartner(models.Model): partner._create_subcontractor_entities() return partner + def _compose_entity_name(self): + """Compose entity name. + Override this function to implement onw logic + Returns: + name (char) composed name + """ + return self.display_name + def _create_location(self, parent_location, company): """Creating Subcontracting Location starts here""" - name = "Subcontractor {}".format(self.name) location_vals = { - "name": name, + "name": self._compose_entity_name(), "usage": "internal", "location_id": parent_location or False, "company_id": company.id, @@ -65,8 +82,8 @@ class ResPartner(models.Model): 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") + name = self._compose_entity_name() + operation_type_name = "{}: {}".format(name, " IN") sequence_code = "" for code in list(filter(None, operation_type_name.split(" "))): sequence_code += code[0] @@ -76,6 +93,7 @@ class ResPartner(models.Model): "name": operation_type_name, "code": "incoming", "sequence_code": sequence_code, + "is_subcontractor": True, } if warehouse: operation_type_vals.update({"warehouse_id": warehouse.id}) @@ -88,28 +106,25 @@ class ResPartner(models.Model): 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) + if not rule: + buy_route = self.env.ref( + "purchase_stock.route_warehouse0_buy", raise_if_not_found=False + ) + rule = self.env["stock.rule"].create( + { + "name": self._compose_entity_name(), + "action": "buy", + "picking_type_id": operation_type_rec.id, + "location_id": location.id, + "route_id": buy_route.id, + } + ) + self.partner_buy_rule_id = rule 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, @@ -118,44 +133,25 @@ class ResPartner(models.Model): 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( + if not pull_rule: + pull_rule = self.env["stock.rule"].create( { + "name": self._compose_entity_name(), + "action": "pull", + "partner_address_id": self._origin.id, "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( - { + "location_id": production.id, + "location_src_id": location.id, "route_id": resupply_on_order_route.id, + "procure_method": "mts_else_mto", } ) - - if not pull_rule and resupply_rule_vals: - pull_rule = self.env["stock.rule"].create(resupply_rule_vals) + self.partner_resupply_rule_id = pull_rule return pull_rule def _create_subcontractor_entities(self): - """ - Create entities for the subcontractor + """Create entities for the subcontractor - Stock location - Stock operation type - "Buy" stock rule diff --git a/mrp_subcontracting_partner_management/models/stock_picking_type.py b/mrp_subcontracting_partner_management/models/stock_picking_type.py new file mode 100644 index 000000000..a63d15df4 --- /dev/null +++ b/mrp_subcontracting_partner_management/models/stock_picking_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class StockPickingType(models.Model): + _inherit = "stock.picking.type" + + is_subcontractor = fields.Boolean() diff --git a/mrp_subcontracting_partner_management/readme/HISTORY.rst b/mrp_subcontracting_partner_management/readme/HISTORY.rst index a7312928a..8e3a62052 100644 --- a/mrp_subcontracting_partner_management/readme/HISTORY.rst +++ b/mrp_subcontracting_partner_management/readme/HISTORY.rst @@ -1,3 +1,9 @@ +14.0.1.0.1 +~~~~~~~~~~ + +**Bugfixes** +- Fixed duplicate rules when creating a subcontractor partner + 14.0.1.0.0 ~~~~~~~~~~ diff --git a/mrp_subcontracting_partner_management/static/description/index.html b/mrp_subcontracting_partner_management/static/description/index.html index 5a1fa28e7..da45b9343 100644 --- a/mrp_subcontracting_partner_management/static/description/index.html +++ b/mrp_subcontracting_partner_management/static/description/index.html @@ -379,29 +379,30 @@ ul.auto-toc {

Table of contents

-

Configuration

+

Configuration

  • No configuration is required
-

Usage

+

Usage

  • Select a partner of type “Company”
  • Enable the “Subcontractor” checkbox
  • @@ -410,16 +411,21 @@ ul.auto-toc {
-

Changelog

+

Changelog

-

14.0.1.0.0

+

14.0.1.0.1

+

Bugfixes +- Fixed duplicate rules when creating a subcontractor partner

+
+
+

14.0.1.0.0

  • Initial release
-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed @@ -427,23 +433,23 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Ooops404
  • Cetmix
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose 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 index edf060089..2624fb4dd 100644 --- a/mrp_subcontracting_partner_management/tests/test_create_sybcontractor_partner_location.py +++ b/mrp_subcontracting_partner_management/tests/test_create_sybcontractor_partner_location.py @@ -168,3 +168,16 @@ class TestSubcontractedPartner(common.SavepointCase): self.assertFalse( partner_resupply_rule.active, "Partner Resupply rule must be not active" ) + + def test_check_countof_rules(self): + partner_id = self.partner_obj.create( + { + "name": "Test partner", + "is_company": True, + "is_subcontractor_partner": True, + } + ) + rules = self.env["stock.rule"].search( + [("name", "=", partner_id.partner_buy_rule_id.name)] + ) + self.assertTrue(len(rules) == 2, "There are must be 2 subcontractor rules") diff --git a/mrp_subcontracting_partner_management/views/stock_picking_type.xml b/mrp_subcontracting_partner_management/views/stock_picking_type.xml new file mode 100644 index 000000000..25b6d9a92 --- /dev/null +++ b/mrp_subcontracting_partner_management/views/stock_picking_type.xml @@ -0,0 +1,19 @@ + + + + + stock.picking.type.filter.subcontractor + stock.picking.type + + + + + + + + + From 475b2e8371cacabe1fcae950f8a0a1fe47bab70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Wed, 17 Aug 2022 08:57:35 +0000 Subject: [PATCH 03/12] Added translation using Weblate (Spanish) Translated using Weblate (Spanish) Currently translated at 100.0% (11 of 11 strings) Translation: manufacture-14.0/manufacture-14.0-mrp_subcontracting_partner_management Translate-URL: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_subcontracting_partner_management/es/ --- .../i18n/es.po | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 mrp_subcontracting_partner_management/i18n/es.po diff --git a/mrp_subcontracting_partner_management/i18n/es.po b/mrp_subcontracting_partner_management/i18n/es.po new file mode 100644 index 000000000..429d8c1b8 --- /dev/null +++ b/mrp_subcontracting_partner_management/i18n/es.po @@ -0,0 +1,82 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_subcontracting_partner_management +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-08-17 11:07+0000\n" +"Last-Translator: Víctor Martínez \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model,name:mrp_subcontracting_partner_management.model_res_partner +msgid "Contact" +msgstr "Contacto" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__display_name +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__id +msgid "ID" +msgstr "ID" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__is_subcontractor +msgid "Is Subcontractor" +msgstr "Es subcontratista" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner____last_update +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_buy_rule_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_buy_rule_id +msgid "Partner Buy Rule" +msgstr "Regla de compra de contacto" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_picking_type_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_picking_type_id +msgid "Partner Picking Type" +msgstr "Tipo de operación de contacto" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_resupply_rule_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_resupply_rule_id +msgid "Partner Resupply Rule" +msgstr "Regla de reabastecimiento de contacto" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model,name:mrp_subcontracting_partner_management.model_stock_picking_type +msgid "Picking Type" +msgstr "Tipo de operación" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__subcontracted_created_location_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__subcontracted_created_location_id +msgid "Subcontracted Created Location" +msgstr "Ubicación de subcontratista creada" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__is_subcontractor_partner +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__is_subcontractor_partner +#: model_terms:ir.ui.view,arch_db:mrp_subcontracting_partner_management.view_partner_form_inherit_subcontractor +#: model_terms:ir.ui.view,arch_db:mrp_subcontracting_partner_management.view_pickingtype_filter +msgid "Subcontractor" +msgstr "Subcontratista" From b14c153fe8ef27f2ea7e93d003011e483a097299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Thu, 18 Aug 2022 08:40:52 +0200 Subject: [PATCH 04/12] [FIX] mrp_subcontracting_partner_management: Fix view in contacts TT38583 mrp_subcontracting_partner_management 14.0.1.1.0 --- mrp_subcontracting_partner_management/__manifest__.py | 2 +- .../views/res_partner.xml | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/mrp_subcontracting_partner_management/__manifest__.py b/mrp_subcontracting_partner_management/__manifest__.py index 593232e97..f6f9df02f 100644 --- a/mrp_subcontracting_partner_management/__manifest__.py +++ b/mrp_subcontracting_partner_management/__manifest__.py @@ -1,6 +1,6 @@ { "name": "Subcontracting Partner Management", - "version": "14.0.1.0.1", + "version": "14.0.1.1.0", "summary": "Subcontracting Partner Management", "author": "Ooops404, Cetmix, Odoo Community Association (OCA)", "license": "LGPL-3", diff --git a/mrp_subcontracting_partner_management/views/res_partner.xml b/mrp_subcontracting_partner_management/views/res_partner.xml index 77f89331e..1802199a0 100644 --- a/mrp_subcontracting_partner_management/views/res_partner.xml +++ b/mrp_subcontracting_partner_management/views/res_partner.xml @@ -7,12 +7,11 @@ - - + + From 46ae903207c8e2215f0ad11d9d7caa178abc1e37 Mon Sep 17 00:00:00 2001 From: Samuele Mariani Date: Wed, 21 Sep 2022 16:47:07 +0000 Subject: [PATCH 05/12] Added translation using Weblate (Italian) Translated using Weblate (Italian) Currently translated at 100.0% (11 of 11 strings) Translation: manufacture-14.0/manufacture-14.0-mrp_subcontracting_partner_management Translate-URL: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_subcontracting_partner_management/it/ --- .../i18n/it.po | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 mrp_subcontracting_partner_management/i18n/it.po diff --git a/mrp_subcontracting_partner_management/i18n/it.po b/mrp_subcontracting_partner_management/i18n/it.po new file mode 100644 index 000000000..13dd1254b --- /dev/null +++ b/mrp_subcontracting_partner_management/i18n/it.po @@ -0,0 +1,82 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_subcontracting_partner_management +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-09-21 19:07+0000\n" +"Last-Translator: Samuele Mariani \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model,name:mrp_subcontracting_partner_management.model_res_partner +msgid "Contact" +msgstr "Contatto" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__display_name +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__id +msgid "ID" +msgstr "ID" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__is_subcontractor +msgid "Is Subcontractor" +msgstr "è un Terzista" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner____last_update +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type____last_update +msgid "Last Modified on" +msgstr "Ultima Modifica il" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_buy_rule_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_buy_rule_id +msgid "Partner Buy Rule" +msgstr "Regola di acquisto del partner" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_picking_type_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_picking_type_id +msgid "Partner Picking Type" +msgstr "Tipo Trasferimento del partner" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_resupply_rule_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_resupply_rule_id +msgid "Partner Resupply Rule" +msgstr "Rifornimento Regola Partner" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model,name:mrp_subcontracting_partner_management.model_stock_picking_type +msgid "Picking Type" +msgstr "Tipo trasferimento" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__subcontracted_created_location_id +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__subcontracted_created_location_id +msgid "Subcontracted Created Location" +msgstr "Ubicazione per terzista" + +#. module: mrp_subcontracting_partner_management +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__is_subcontractor_partner +#: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__is_subcontractor_partner +#: model_terms:ir.ui.view,arch_db:mrp_subcontracting_partner_management.view_partner_form_inherit_subcontractor +#: model_terms:ir.ui.view,arch_db:mrp_subcontracting_partner_management.view_pickingtype_filter +msgid "Subcontractor" +msgstr "Terzista" From cd20343a80c91255edd41ab3dbe813e6c1e75f46 Mon Sep 17 00:00:00 2001 From: geomer198 Date: Wed, 5 Oct 2022 18:42:11 +0300 Subject: [PATCH 06/12] [IMP] mrp_subcontracting_partner_management : Changed 'is_subcontractor_partner' field position. When subcontractor name changed then changes subcontractor related records name. --- .../__manifest__.py | 4 +- .../models/res_partner.py | 442 +++++++++--------- .../readme/CONFIGURE.rst | 5 +- .../readme/USAGE.rst | 3 + .../static/description/icon.png | Bin 28872 -> 9455 bytes .../tests/__init__.py | 2 +- ..._create_subcontractor_partner_location.py} | 76 ++- .../views/res_partner.xml | 19 - .../views/res_partner_views.xml | 36 ++ ..._type.xml => stock_picking_type_views.xml} | 0 10 files changed, 319 insertions(+), 268 deletions(-) rename mrp_subcontracting_partner_management/tests/{test_create_sybcontractor_partner_location.py => test_create_subcontractor_partner_location.py} (77%) delete mode 100644 mrp_subcontracting_partner_management/views/res_partner.xml create mode 100644 mrp_subcontracting_partner_management/views/res_partner_views.xml rename mrp_subcontracting_partner_management/views/{stock_picking_type.xml => stock_picking_type_views.xml} (100%) diff --git a/mrp_subcontracting_partner_management/__manifest__.py b/mrp_subcontracting_partner_management/__manifest__.py index f6f9df02f..d93c6adcc 100644 --- a/mrp_subcontracting_partner_management/__manifest__.py +++ b/mrp_subcontracting_partner_management/__manifest__.py @@ -10,8 +10,8 @@ "external_dependencies": {}, "demo": [], "data": [ - "views/res_partner.xml", - "views/stock_picking_type.xml", + "views/res_partner_views.xml", + "views/stock_picking_type_views.xml", ], "qweb": [], "installable": True, diff --git a/mrp_subcontracting_partner_management/models/res_partner.py b/mrp_subcontracting_partner_management/models/res_partner.py index 585ba2aa5..a4decbfea 100644 --- a/mrp_subcontracting_partner_management/models/res_partner.py +++ b/mrp_subcontracting_partner_management/models/res_partner.py @@ -1,3 +1,5 @@ +import re + from odoo import api, fields, models @@ -6,258 +8,234 @@ class ResPartner(models.Model): is_subcontractor_partner = fields.Boolean(string="Subcontractor") subcontracted_created_location_id = fields.Many2one( - copy=False, comodel_name="stock.location" + comodel_name="stock.location", copy=False ) partner_picking_type_id = fields.Many2one( - copy=False, comodel_name="stock.picking.type" - ) - partner_buy_rule_id = fields.Many2one( - copy=False, - comodel_name="stock.rule", - ) - partner_resupply_rule_id = fields.Many2one( - copy=False, - comodel_name="stock.rule", + comodel_name="stock.picking.type", copy=False ) + partner_buy_rule_id = fields.Many2one(comodel_name="stock.rule", copy=False) + partner_resupply_rule_id = fields.Many2one(comodel_name="stock.rule", copy=False) - def _set_subcontracting_values_active(self, active): + def action_subcontractor_location_stock(self): + """Open subcontractor location stock list""" 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 subcontracting 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) + action = self.env["ir.actions.actions"]._for_xml_id( + "stock.location_open_quants" + ) + active_ids = self.property_stock_subcontractor.ids + action.update(domain=[("location_id", "child_of", active_ids)]) + return action @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 get_data_struct(self): + return { + # Updating Subcontracting Location + "subcontracted_created_location_id": "_create_subcontracting_location_data", + # Updating Subcontracting operation type + "partner_picking_type_id": "_create_operation_type_for_subcontracting", + # Updating Route Rule for Subcontracting buy + "partner_buy_rule_id": "_create_route_rule_for_subcontracting", + # Updating Route Rule for Subcontracting resupply + "partner_resupply_rule_id": "_create_route_rule_for_subcontracting_resupply", + } + + def _set_subcontracting_values_active(self, active): + """Set subcontracting values active/inactive by argument key""" + for key in self.get_data_struct(): + self.mapped(key).write({"active": active}) + + @api.model + def _update_name_translation(self, records, name): + """Update name field translation for records""" + self.env["ir.translation"].search( + [ + ("name", "=", "{},name".format(records._name)), + ("res_id", "in", records.ids), + ("value", "!=", name), + ] + ).write({"value": name}) + + def _update_subcontractor_values_name(self, name): + """ + Update subcontractor related records: + - Location; + - Operation type; + - Route Rule for Subcontracting buy; + - Route Rule for Subcontracting resupply. + """ + partners = self.filtered(lambda p: p.is_subcontractor_partner) + field_names = [*self.get_data_struct(), "property_stock_subcontractor"] + field_names.remove("partner_picking_type_id") + for field in field_names: + records = partners.mapped(field) + records.write({"name": name}) + self._update_name_translation(records, name) + type_name = "%s: IN" % name + code = "".join(re.findall(r"\b\w", type_name)) + picks = partners.mapped("partner_picking_type_id") + picks.write({"name": type_name, "sequence_code": code}) + self._update_name_translation(picks, type_name) + + def unlink(self): + """This Method is override to archive all subcontracting field""" + self._set_subcontracting_values_active(False) + return super(ResPartner, self).unlink() + + def write(self, vals): + if "is_subcontractor_partner" in vals: + self._update_subcontractor_entities_for_record( + vals.get("is_subcontractor_partner") + ) + if "active" in vals: + self._set_subcontracting_values_active(vals.get("active")) + result = super(ResPartner, self).write(vals) + if vals.get("name"): + self._update_subcontractor_values_name(vals.get("name")) + return result + + @api.model_create_multi + def create(self, vals_list): + records = super(ResPartner, self).create(vals_list) + check_data = self.get_data_struct().items() + for record in records.filtered( + lambda r: r.is_subcontractor_partner and r.is_company + ): + values = {} + for key, func in check_data: + if not getattr(record, key) or not values.get(key): + values.update(**getattr(record, func)(values) or {}) + if values: + record.write(values) + return records + + def _update_subcontractor_entities_for_record(self, is_subcontractor_partner): + if not is_subcontractor_partner: + return self._set_subcontracting_values_active(False) + data_items = self.get_data_struct().items() + for rec in self: + vals = {} + for key, record, func in map( + lambda f: (f[0], getattr(rec, f[0]), f[1]), data_items + ): + if record: + record.active = True + else: + if not getattr(rec, key) or not vals.get(key): + vals.update(**getattr(rec, func)(vals) or {}) + if vals: + rec.write(vals) def _compose_entity_name(self): - """Compose entity name. - Override this function to implement onw logic - Returns: - name (char) composed name + """ + Compose entity name. Override this function to implement onw logic + :return: name (char) composed name """ return self.display_name - def _create_location(self, parent_location, company): - """Creating Subcontracting Location starts here""" - - location_vals = { - "name": self._compose_entity_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): + def _create_subcontracted_operation_type(self, vals): """Creating Operation Type for Subcontracting""" - name = self._compose_entity_name() - operation_type_name = "{}: {}".format(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, - "is_subcontractor": True, - } - 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 + location_id = self._get_location_id_for_record(vals) + if "partner_picking_type_id" in vals: + return vals.get("partner_picking_type_id"), location_id + if self.partner_picking_type_id: + return self.partner_picking_type_id.id, location_id + operation_type_name = "%s: IN" % self._compose_entity_name() + operation_type_vals = { + "name": operation_type_name, + "code": "incoming", + "sequence_code": "".join(re.findall(r"\b\w", operation_type_name)), + "is_subcontractor": True, + } + company = self.company_id or self.env.company + warehouse = self.env["stock.warehouse"].search( + [("company_id", "=", company.id)], limit=1 + ) + if warehouse: + operation_type_vals.update({"warehouse_id": warehouse.id}) + if location_id: + operation_type_vals.update({"default_location_dest_id": location_id}) + return ( + self.env["stock.picking.type"].create(operation_type_vals).id, + location_id, + ) - def _create_subcontracted_buy_rule(self, operation_type_rec, location): - """Creating Route Rule for Subcontracting starts here""" - rule = self.partner_buy_rule_id - if not rule: - buy_route = self.env.ref( - "purchase_stock.route_warehouse0_buy", raise_if_not_found=False - ) - rule = self.env["stock.rule"].create( + def _get_location_id_for_record(self, vals): + self.ensure_one() + if "subcontracted_created_location_id" in vals: + return vals.get("subcontracted_created_location_id") + if self.subcontracted_created_location_id: + return self.subcontracted_created_location_id.id + company = self.company_id or self.env.company + parent_location = ( + company.subcontracting_location_id and company.subcontracting_location_id.id + ) + return ( + self.env["stock.location"] + .create( { "name": self._compose_entity_name(), - "action": "buy", - "picking_type_id": operation_type_rec.id, - "location_id": location.id, - "route_id": buy_route.id, + "usage": "internal", + "location_id": parent_location or False, + "company_id": company.id, + "active": True, } ) - self.partner_buy_rule_id = rule - return rule + .id + ) - def _create_subcontracted_resupply_rule(self, location): - """# Creating Route Rule for Subcontracting resupply on order starts here""" - resupply_on_order_route = self.env.ref( + def _create_subcontracting_location_data(self, vals): + self.ensure_one() + location_id = self._get_location_id_for_record(vals) + return { + "property_stock_subcontractor": location_id, + "subcontracted_created_location_id": location_id, + } + + def _create_operation_type_for_subcontracting(self, vals): + self.ensure_one() + # Creating Operation Type for Subcontracting starts here + operation_type_rec_id, _ = self._create_subcontracted_operation_type(vals) + return {"partner_picking_type_id": operation_type_rec_id} + + def _create_route_rule_for_subcontracting(self, vals): + self.ensure_one() + operation_type_rec_id, location_id = self._create_subcontracted_operation_type( + vals + ) + route = self.env.ref( + "purchase_stock.route_warehouse0_buy", raise_if_not_found=False + ) + buy_rule = self.env["stock.rule"].create( + { + "name": self._compose_entity_name(), + "action": "buy", + "picking_type_id": operation_type_rec_id, + "location_id": location_id, + "route_id": route.id, + } + ) + return {"partner_buy_rule_id": buy_rule.id} + + def _create_route_rule_for_subcontracting_resupply(self, vals): + self.ensure_one() + prop = self.env["ir.property"]._get( + "property_stock_production", "product.template" + ) + picking_type = self.env.ref("stock.picking_type_out", raise_if_not_found=False) + 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" + rule = self.env["stock.rule"].create( + { + "name": self._compose_entity_name(), + "action": "pull", + "partner_address_id": self._origin.id, + "picking_type_id": picking_type.id, + "location_id": prop.id, + "location_src_id": self._get_location_id_for_record(vals), + "route_id": route.id, + "procure_method": "mts_else_mto", + } ) - pull_rule = self.partner_resupply_rule_id - if not pull_rule: - pull_rule = self.env["stock.rule"].create( - { - "name": self._compose_entity_name(), - "action": "pull", - "partner_address_id": self._origin.id, - "picking_type_id": delivery_type.id, - "location_id": production.id, - "location_src_id": location.id, - "route_id": resupply_on_order_route.id, - "procure_method": "mts_else_mto", - } - ) - self.partner_resupply_rule_id = pull_rule - 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} + return {"partner_resupply_rule_id": rule.id} diff --git a/mrp_subcontracting_partner_management/readme/CONFIGURE.rst b/mrp_subcontracting_partner_management/readme/CONFIGURE.rst index 0862077f4..01d08757f 100644 --- a/mrp_subcontracting_partner_management/readme/CONFIGURE.rst +++ b/mrp_subcontracting_partner_management/readme/CONFIGURE.rst @@ -1 +1,4 @@ -* No configuration is required +To see newly created rules, go to **Settings** > **Inventory** and activate "Multi step routes". + +It is also possible to allow a user to check inventory locations in **Sales & Purchases** tab of **Vendor** without activating dev mode. +To do so, enable technical extra right **Display Inventory section on Vendor** in user. diff --git a/mrp_subcontracting_partner_management/readme/USAGE.rst b/mrp_subcontracting_partner_management/readme/USAGE.rst index b5502316a..9f7e8bc18 100644 --- a/mrp_subcontracting_partner_management/readme/USAGE.rst +++ b/mrp_subcontracting_partner_management/readme/USAGE.rst @@ -2,3 +2,6 @@ * Enable the "Subcontractor" checkbox * New entities are created or existing are used if were created previously * When disabled all associated enties will be archived +* When name of subcontractor is updated, names of entities are updated automatically. +* It is also possible to check inventory locations using **Subcontractor Location Stock** smart button on partner. +* When name of subcontractor is updated, names of entities (subcontracting location, operation type, rules) are updated automatically. diff --git a/mrp_subcontracting_partner_management/static/description/icon.png b/mrp_subcontracting_partner_management/static/description/icon.png index ea708b0a29ee106a8799f18ff661aa6641f6478c..3a0328b516c4980e8e44cdb63fd945757ddd132d 100644 GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 28872 zcmeENQ+Fj@v^}wHoUmitw(X8{I<{@wHaqUvb~+s=oup&i*3Esm|KL7;V^ob@FIA)V z+^cG@HRoKB%8F7*@c8fm002owT3i(X0ROrK2f#vq4R;<3FJA+kgS3t_0Dyr0zbDw- zy5AcBKmw2v7g6^xILn0zz?4`TrMk2Mq*2QvQx1ufR8nt`RN zDGAKOK`Xc4{&C2G0~^i>z(}n&L;cHr{o1=>*MpXMoVNle(D>P7ZT|PTw)SQ5v{I|i zpu0iaBLM*S-|-&;{~_=n0{nmpT zqF37T?(wk`@7mw$r8Mm9so0=)`oI9@U*9{|*dV)>=zIO1#)<9t+kcRE~%Qyz*6MiatC?+U9hPT!?_WX=RARYPNgQX$Ku&xz@y1;eNH$*0Hg=JvJ~rTg>4 zYCxVa@ctcJctnK?TMnUW*bDZFs}529Q2PzcJdb=?Wxwhg#ui&CPoSLB0`5W-XuGC$ zxH9`~vibFNUC91(@Z>dRBveWu@Fps1$-;guVAq&?4Qj?s_#k`R8`%%r6J_geCyVxt z^5z4xSsI9g;#$7 zb`wke=4bBq-))0^s^56Mdbji;MY6dEAa#lyW;`D#fc!HnTgh z_55I%O5!^4BP-bE2su1Qm$^rM1rqO{r2p9e3?NA{d~~DuZw6kPs61mIru*ez%608o zOU2`Bq>dvtHz+VMwXVE<{83Cl#idJ8gUz@)i@HV4QE>dRc3vbJY4D<#m%xKSuP%(8 zqG*UOl{iWac%}34+rIYFda-S}-<66>kxY?Ffn2)Rn?ZyKyJ$Q2sABLaqOcHv-dx+o zgd$n~(G23Lc8k`><0>c@^$t2+yabEpIvU*sC+ecoc&chb5GuCoSvjwIYxpT}h@jWk zP_|nh%+}G(jrtrD6@{EIZ;-H(e6@mNPUT}Cdzxhw0Psodq_vcqQG^Vkxp;;;#LoGI zSgyN%MmpK@PvzjqW@bjCV?;?6E>;FmVqp`_R_io^p(e?ZN{<#_+mP+!6efTjYP<@1fSJS~kp}{20HS;k(ord8 z4xzMas&UGG(uOA%OdoIL1{V}<=4jl0>`p@w=Rc{Pi}G|-4f3KH-yxe^4tMYlxB2l2 zSA{jPh@z^af+}gtVJ5@BW2`EN(U$AtR~9v6J=P7~UvAx=2TVU=VRGxjlBr*;ZZHwy zBd#h1yBGq}%1t6>hAmz9&MyBls0x9-;b@e+J$}Mq;(IH!q$=%;^MO(HV;5mBLv^X5 z5cr6EO1VX!_f}jE1BHm1SX7+SCtM~cCf9@y{9y{;$#HXIx|&+~2wy?p?NQz8yTTJa zE%zkg3}V^Siv)CHI8P^y#loYKW-8)iGyoHmlg`zgYb&S`1SKL8qYJ9|O2=2taa6M$ zbsS^5~G%JcpBE}stx&KEQ)P;1V+3)c_4uI!Y&KuYc)&AZZT)?M@NNe z7neu76dtOZMW_+`fZSdv*#LfPHm{*lB729xDSV&S+w$NNa=EqQM?7_N#o~@O^-7`8!$5W26J%hyv^^$h+lHb62woO z>*z;@MJ+&<#TxKv#)UuZuG~wOei>|Dy3w8a^x8TqusD0*glKaz)(u_!Y+#o?S?Mxj zRnA2IXa+2TjS$@moJ6~W(6*(Y9-O-xST)prbNFM$f>}BZ7H#Z4wzTfy{u$Cz(!9h- z)d=|&_2-th5k(d-;MrG??$^jYs(N~_{UO)8?v!jwKYcWBzVY;zhVaN7h))Lw#17HK zRw@uA5?SCYIA|hL5YHXehLJDl6ihw*jxZjS=Smuu^UmpN;;!pEY62a}Wz*IcPGiN9 z!Y`UW3F5$~X0$1|r~Jv-9-+P=tM8K1P&{9i-bBnyRY|%EK_~fuxGXfz3GVIIQC3zq z4SS>DMcE{NeMG*FHvY*uUe7R>+Aq32(Tm{J$;&5`GN20i(6p#B+R0`k&x)S+*S?2rz3z2=?!KPt)E=W6 zgQVZnavAhPQ*R48eAq93{(_$$7Nb{5@|EV~?w++q2dWF~lQMtvbD7qE-BdvEGnu9w znwdGdr5(3HNIILAonRRP26KS;@^G3}A^Kr&e05@iX;djIC2=0YvIg+ob5SF%$M@9$ zVLK}Ty2|?KXrUVT8OY>*v;YXHg$H@~9Mk6X}9wExL`>u1ly&vRr_&smtEaoB~n@vaK&i{5vM1$9N!66wNbWoRP-Oxt?w4=TY zcH6GCsLC6f#fYswQt_KlPB;zzoaP({Ip@$9+uEaEphOXwZIr1r!bgyiR3V)NZP=gk z2mS=x?9oE`Jee-jdC}C>bnBSh_d;~?Z=!tsae)7FVXX!^_Gs6{!1M|c*c(GA_%`X$ zit30duxN|FH$nW-he}N(L*$OOCtRr0dnSB4zdHF$3m>bo1 zTk$^elb&FaE+o=#*-T9XvPX=E@=qq4`A0{y^}LsOQ_r+q_+Q`y3-jYj<4T(KdKn=^!%(EPWZn?lrx z6JP2s`&v)60{$P9GGrOaJ4~S`rOWvVF66o8N>@8~i2~I8!_B}*70ZHwc#R*jrczcY zX`>x$9NZyzH3T2djL^4r?7J?MoMl}?db zf4E>+S%Fnd-wec7fv2xMF3=~SmbJV^Imx%3MJ5L0E_F8BKL$VZAr`(JXNji6k3i&= zSRm*!=y0Z&j$A%7qTaTN*(vg<44E4Pu(eS>_j8X7A@pNS%hh(aLE! zUb;W^z(?AEO|7Gz;lmA4+OlwWTyyEnt)nkX_Z3_`oyAPoK6zX73$+$PWJ180C%n>7 z>uo2zvXVQf>CylOYda?9THG%QW>Hg%(RC%Nbi&{p=e8#s8oZ`d52iIm3VOl-sh!b7uGq3 zSidKrcfzi5c^jCZQl+2jipMS8E&Was7r>zR`^gUuxr7?#UeW&-ks>6cgf1)}ZpO#R zpHeovSK~6oA4zV%b2zH%s>eiLeKHdN7$(!WqSj;QN0S_W2F3XHSk9MC)&G;&cUR>G zMF}u5)nSEn)8Q9xP(S^Yu0r{v=z;V-3&Mn5HnI<4+?<2oY??^W<}Z~@a8@78Fry{h z?}h#(B384AZqsr_@|kwbqML>i$JiEh3Vj`fs0fugA^&(?UmU}Yu+c**3vn*v6}8`| z`Qiqi7}2Zqc)n&n zIX)>?&7`N+Wd>VpbsXXyKhWKMOgmac3j) zFhG2MK{)Ahu6EAT6(e_|6|;3nh=`aQ_e0jvZ?|5qCC=X4x1DzDe60T7-Epy2*8~60 zV>dPs$mgbg78rmOI?&~ohTxPOpVo-v;TD`U1M!(Qa;4qH2JD+hv*wRczNO#jcU2y^ z3~RkRy-t}FFKMp5ob*KQ1mW}k^PcTr9qKeBBr3K9G&h(|2p?C^&Rzt6)Wfe2rc0kr z76@JCPVcT9O}L3ILA-7l_Ws#?tZxY8znreooXHXUj@9*=) zJlXRwgTArjfxmruM#qPBi_Ay?lki*M2ImOuFjxLg0C^48wvqua~@ z`=>8*D*4zBO2VuZ=_W#f@`&#XIJPMq_W}XX-G5I;GvX^Z`*rZwrinO_U+h<_(a$ZH z42)4KQN`rkAM7{zXI0AmktaS@S$ zC2#E1ls2?~68P&P9uKA5eAe@jlDDA6eo4G53f96!q|*&nSX8+feT|j~DBFTRzJLJnkN*dWm(M zTkd3g$L?cmmwj8faE+tN(Qq1lRl@bDfJ+H1^G zEG^T0zbCWmBV?C%zpV^2s?VTRBjZLZ)6+W(9Sny@=3bZ@PFwe)>w1pP#e8A<_n5=H zxOQJ^xTqpDS3imE)Hkp99xmgkt{SnRVmJfmDUOE?-xvN6Z(_>p})UWGE?z&X+R(lkh!Q6AGpGR`Zj2K z+^0^l#WsY9i%6Bifi$irAt7PjRah5%Jo`(&xMCvZH&z6m4bj9cQ&*p(iu^z-f^HYW zQk21w5r}72`05B3K}yCj)8Oj$aOYG&dzaC1&{S|lKFiEV&`c&;p|A0oUcNl+s9rBG2BYyTzYb_?ZTQ=*&`=?c*Q5 zV!cqTTyG(37ag!0H!3>qC4NKvOh3R~o;FRz1&E7pp)_8DHt)W>f_^WF3P4-Od(Zxu zY9O|4r6F~y6F2v(jyd`Du~|=sYE{$cE4NkXA~w1ojK5$fKJi!$Lv)1F_BrINpID}` z$7MNr)eZ)KtlUIUcXTbiaOnXPCoTvbe7%_957!Nxov6?O)Q>M!tB^|}%c=JyW!C=0 z3Xmf5T|maTcCpYeF0#yFCmj?Z${{miGurs1&on}Lpx0(a9v>fXheEns_kcr=b=#|{ zo%+BM%D2;3!_iFBrdyQrCr2fU4*}w550B%y(tJjr z`^AhKv{Qx0?U4r%$QLd6-HkmUxUbZ*=`za-?xqOQM8lJ0scY!9=bnmz8#DLNIbzSf zcJ7%V$56{mA9!s_MBF117|t^IX)+oQP38L;N$lM_Ghpc+0d;M+Cqo}4>riAtkIM=I zqu5eQ`J9NGgc;F?Yuk-=h(JJoiyg)LWNs$NOfd3uV+l$)vYbqeqok95q`{ZSdeF+G}`P zgJMFKof{(-e5a%?t}6BBg@rQJ2qW1LNUe0xXn2JvlM6>&3i%VH$u;Q$H*U-ft{vY66z&T!ewPI33iBF!2*H z7#J3lSWuP%^o%tkbrX3GbDYm`K0h9(+tx3HjxztTB_vFk1L4|eEH!bD&+N725_>? z0RmgY@oBx*xLh60r2-nB#+3CYc)$U@y}0?KmU`Gu`%kJPQv9r;@J|BXDj5e^7Lc^| z&$k;IHJhcJ90KN>Eck7!a?j0EILQcf=H=7te=&UmDy~CVZ02tLY-!t>wiGqe;Scmz z>FqZ>d@Z)wJlz%4$HgQ9lnJRD)YF(^VCeOV<@2+D+cj%)JlV=Gqy0ZGKwZ4R!|%r`t!zM@A9j?SY+UH^8L5kbxy1b6 zC^!P3G^xBw@_qizrI#0vEVMMSl$*-@x&y7d0>fEklT?G^@d(F+61!etNc$Oy#rcX4~JeKA6GvXSG%20P0FVP;R~G@ zM5;a-c=4X)n1TL%y-<^fBbPN*z8#0@%UQB4df_6?ZdU zje+P95Cn!!jt2@2S@Fyyf~ zAK)>A@#pNjJ+hmmY4I{da)$LODM!?XVf8Uj4#*^b_f*L4fuBB%9-Q(GbNNVBRvv}n z&*LFi;5z?!gN1gw_ZU|x>(e}a>KSr_ zjDWLZNnefWKKPVH>f(J22l3yU=QGKQJ}Uz361!|6iVLb<76v11XmfHI_O8+rwVO z*!cjLv$s$--Ydg2zpMTl>R^u~(M}$?(>7HaJLC%ePU+V3KX21nNrXpXydN&QagpZyOMJY-cb@fx(oZB;hyz&N zLN)|eFGG{5CSheYMW{6^Ax`Pd;6ZG)?Ye|}bERK5D`xWZ1n>*N8n>Z8Z+^oj6=+`| ztmx~3>6s1;PMxQo4l`(Nd5rExoA1Re#?Nk8nC_$X|89T%)O~AhUCAg@{>M<;^}}NZ z#T9H5s~K#15S>r>LyOp)`KEBJmUC>I2MC8Mdy*}zkgsz0;!sJgCa6M@$n({04GL^RvzS%3Kpk|Vb zey5ndy254oTviZy+6iKi&2OAg$>em0V_I1NucMAGtn)!|21E+#bXeT#$;op)`&bwi zyx4idxkTzatdF41yg#1D$E0!)Wf$zcynP`egmPMH)*LQP_JG=kF*6z`?|ZZNZJIY~z7Z>CKtPI&l1#EC zns&F7)AiaZhkM`HJNsHSI~7PQRl~&63||$L2iQGq=?X8_F}5| zNmTp{6fzDvJeP>R2p&)gFqiq~1ZM|s?PP>-@RA>>G%*aj+x5@F>qj@TY#&aBT?cqW z!$1XvFz`Kgkl%4rJbiM%AqzZ*KtpbMFC1|V{NzFviz_lcJs78 z?2n7V=d9D8p;Ob!Cy!w6Vkwl#Qm)N6Eq40WhZT(XJBv< zhT4mzVxB0oeEJF-Da_jFF2)C#ZOBgs7UKM7QxrO}bDvofT0@nUJ5Qe>L#R(K2O93Z zg1gJD8=mdXpwYkY;&|FR-;d&fp9g20Ils<&}P80Xm9t<=C|M@^eI?8#PGB{b`Q zD~h&mz~zSzOG?&Gs0R$rPFS`op;NV4zz2fL`AC!Vx4tKRJ*2+xZWg|)oGWnNjiX3U zTRckxiq!|Xwwnb%O|ov?VFKYV_XO*rrR%}*)T>-LHJBeN(IZIRNuU|BgoCWa5By$4nOvWd&9dpYrBhYa5fwPt_yav8a3)ll6~~| zev5G|;nc8FECa8PDhMA0_4?1#OiP)gjZC15-J|4}6-ic4@&gocEhbV$wy2SPUb zaP-(C6}_-L!lZIZ^oB&a|4&1M&T+j{yt!e>#P^-yEu-V6PYa{iX&l@ggqI$*2iAx_ zNfnP%E%|>zxqsqgSPdDwPg56V|0dy2&l)Bbr{ns^^9823VXBoYy6!Z0;I*vFiQ>-~+Qmkx^T{#S!taEC&7| z{yj=_6Ktt4GV|a4WEH`%Y7=8VvTpFc-|iMh;1)-sD`V!@UmF-6FbfoVh@5~gSVN|p zjA2Myn(4XO)wqeZ$0CV_sIoV>Odrg)EJf;xIPQS^9}H zh^OpME9}_e#Jexi=_x5A!%vj&XenHn6?+RIunAAYZKk#n&xdwhCo>WlAr@FqzDo2< zdHa3+CznF0F4*eMuhzcyhJGc(wbxKh+~y6&UFh!FUXQok&(Ev#D$kC76vbjl+S4}# zmi6mu4AKB{JZa3(<)WtkDla}cRArQ>gFR{Czl8AkUG32BM|W&R0(C; zndXNvGke*W?*}i7#hGt=MPd@_Y!9mg+K?~iUQakB=cJsHT55NwZ25m%`UjvO15;uH z6m~dhtQTe~3=o}NPo-5#9;V-6f)R)_`MAza-bNdVq+;?7QH~8 zI+?Y}iu|`3jF!f9raB}X2>h)QtoqhHfG2(eS&eBI8pDQLIR5|!qq_nF^MZXj`Kfed z+ytPtpuXnvI5XX2^3$UiOh?ltzrm;BeeLAlrV5kuujT&3hAGDqF*sy1XO~uP{rA`_ z;STFrqI2dsqJH-(o{eTvn{8X}iLylgtE@0I=}2(p@48VEl}nRm=@i`%ih8MetE;1K z)jGxQmLs1RKYw}x^R)7%bSKI(YiIj&BY4t?@K7j5z_Bqg-%wirs6GBd$&R8{gC$$Q zUYsYim=lsR&kaT~hX%4y{7| z;QFKgWvrQ35)z12w0`_(J~6JST$3OEFkcUvVLw*eAZL z75IC(`Jf$;Sh6pgUrig~Al*lN=VtwDwC?9)UVNws{UhXCpg-Bm_QVS)5Ju>mcs-6L zpbLfS6~)V1oisZ;W{k479hC*UNVDR*;^eqnc5_v4Pcbj`$V-I( z#LJ_<+nbAnYAZRXb+_A#*^DAP+%!UibZ)b@2D8S@#1H2j>w5Z6@mxBvLbB4T_c$dg zdT3C&p@;Ev$-n@A@bnf4jw}Hk3Q%_Je18yI(uG=`Z^ZdxA2gAdHg0mfDCu$@9mD;- zFhIao(LB6~-U6Lf!1iPTuy^I@a@He#Q^8pG8|<+$IiaS>cL)x)DcE3Z(`%kKN-bm9 z>D$3|d|XTY%1T;eV>RQM4$$vKne4;3NpJTSrZ~=?*w%0E_@!X;(<*nf6XV~Y+tLjY zNo^ASI-mdI_4KjVd1pgoudwfj7SlkP!)h-&Bx4{h0C{?HQV*7bWSc;uR1HYMS0|$d zh?12x%k?7eM*6m}ewAhfk(i%^G<-JQSd=L79RQl#atL?PeR@ajvT!koRxV5s(?P43 zteaQGoD<&qCuHc~ZC7mJ_AI0R@k0Iss$)MiLBTh4VtXtsCXYzLfb%Z*av} zJ7czYXrS+i7}<8S+Q9goEbET#ef31&TdO!m?ia{us3-~xfG>KPQ(e8}C4bR*$~gU{ zA`|uTshgT9m+O;@O#ZWb^n`8gh7|-G{$ga24Dc({wLU)dz`y1|LX8f0rYvyj{~bfv zbYS*Vqy+mct(a{Ud;F~IPiy;({6pbZI8}h_S?AR$pJdK*4zg5y-YAf&-^L?^6yW)a z+wd5?aRLu2v24u6M;a?&_=|S%UW|3?A3`7jcJ=aZe_uzGk$C-4Y>e$?8{gdeQjpVK z81fOz?4j(}y22|w&O0#H*x`QuLR1YCK=WKfL<+gKY7> z@SQ@s3{mUzGl91n!J}IT`E@O}8JR4V;7@>@5pabL*?$}Px-ft^03#b751;?L{~(eo zN~lM8Pn0CwOc|t^EUb41As9sguH2wdfP~S=mwRdhgHo}inW0}a#{~+o&!fw68P1S3 zhYAyH?NF=W@I24@Tq>6qaq#vluQ9bLsjZByh*5HOtc)TF8iYUhz{6XT`;~0tQ~jzC z7qDM1_+7d(HH<+e8;4V@5|4{5+ze<5R;x0eXX5=d{dJp&Vo3DIwqKSS zipaAb!r0!J^lyYUK4^I z9)vl4>0ZT}`z=QGNL`JfViY1{SflvJ*o5}Q#5VwDvg`Z%ceny_nWKf?Axw>-nxIl7 zVblEZRNVC#*n=?7m)XT_wO+Y5=N|)fL)FM_Jhqpg~$hY&CDu z)Uccxb(6V|1f8!c0he74PP=}gd5h*gXV#SJ9E>QTbJQOY#KxwAS6pg>oVC7wE@@uG zXig|QN+9-D$oE4L0t_Wi%ig_|&7tpp7_^K?7fMZaY#*CvE?e84u-(eIg5 z)k!F*9hf~bm{8Xl{RYEP3`5*3nI&&eX9GtO6*o9kAXBbp-Ml;L(@&L@aSg@aB0%-< zzX#d-U7ya}WH2t~vDnP+_btcXRo0^?PQ!7U+w;H!WYayniHl^r67_SEvpeyS3c2eg z1gn?+6-+HDUN($;@JTXO-R-DG{2O3(#a*f!B^+sCaoA{dzci@VE$5P;=-P&}v-l!+ z{kdLB#I8LZ)|zjat<^Fg z4t-e^L7qn6%t7Rq)|z0~?*Vgv!4i4uMh+X5nbUIxJa0~wA;SiE9eh+Ae;HecCLVHz zStnV61?V5*vKMruek_|^rT0Z%y=ON5z`{yT$q{I;SEd)`cY*z;1y--!A-4BDF+}*i zMpF~4{$T(kgWEW$x_JZZXJBe4RvxFGb3=?gAoo-AyGqq4#YAR$03es663K4h5+yxuzMuh;zpgjrEOl zy)uNuh;a2Lngk_!Z#^oVjKp(}5C&8!Tozh@co$rJflCc(f5>6mfteP#!tKvx`{F_i zv7K_DO~;k?S`V#}GfU_dOOFqI`Eb5gW#TDG-YKO5xa(R*Hr}LNC@LDxB5Jq&RITpZi}> zMyU0V0c6aOo0|jd^Dq%C%7K;f6N|Z< z&*eb)sAexdTH07L!ity$CDR`0gCVOikZp-d;)K)&9xZ@(V8^BXp@P(Z(BP-rUmfJX zc8)5!XwT72%v}a}$X=+_%td78dpwyw3N4ct!DS^w;IQS8XNIsAvGL6aOMsu7htMcQ zUG?tyg6Vze4s1k>YrKai5}~n`j0-eWGHMFT@Sp;!jmx}@xHcT*Z~6VSS18nEb#Xwp z1Md=`r`FF3qC7twDm^fVF~;1Jm%FQ`lk@`ilPH#NvKe|1)a2=u&3UnAV>ZjejKgt9 zi`jYLwvM8c9C{y9X~YgGintx-9$;8YxJZ366az(f)Cgg5bQ$|`^(w+C$Do~kzg`Z2 zchso#WS}LM7C&Cs-JD!1cYeOA=7Uq(q8IcVVr^i=*it}LaRV8~$SV8b%3K+ILM_Hb z-?+!f_>&;ywTgihng8fB+72%JL`9;^Ij%Aqkqs1mtmxiERWHfP2V5A`ggDMg)^!RF ze4I7qL?cgQkn5g>CvB>VB`!khTUcTU#OXggEs=6;Qs)uOC+^-;*1m*h`~yr@=e>za zZq-$!MHFeu5?~&;Qwenhdd;=1%h0;AF%J3L6(B;4DTg8DxP_Y*LN}jWlST&T(os*F z)Gp|=kK^gl-w4YC6FARD9)`qb7Rf55z{!sT_w%2_%D~4dM*whkUUwFJ_IbCX%_OLuxt?lyFS2@suzNjV4Gb&NUdE)3SklZyx&n=!lu*HQpcgc>asxjN~i*{aO4Ip4{^3CS{I!e^rN;i z@>z$<>c)e3^N%xBO#CHU$6hfzA5zFV_vo7M9zg?k0_OlUF&n+w@PkJKs`_X-r4zC}k?a z0roM(%FaaHVPMN`+k~Qx&5EYM>4kFYBqO^zx!#$UC1M7miO zy9fEB<_s8AyA9(~l5|{v6`yjW>l$a|`82r|ah?BZeXRUXhkyW&^Vq+K5Awd7kJ$D+3x}?7?Syf1(611#M z-;n3-?v={y*{*+mzbsNs`jI+*-B)hy6s#miOuEwH}S23A< zQvk`n*b0FBRW{d3)vm+$G(QqU)D7J`_6&Y-n%x)u>CshJZc^Yu`LP7!v;d9lrzw6H zGtOHuUTK1sa5boS2&XOg)Np$>h)|xI!q4n?chsh!mKj){7G6RNaL9Wh#0W<3uJ!%~ zd-T^v8_&#w$%?IC-p!z(*uKAvPOuBo_*6xsN!PfG^<9~)Fg5K=$LJ-bavCEXbhW2rt`lrM9dj!f6YRJ$$ zGAFpMtD*W!=E$AZ$&aF$y}fR2yh3)zG+);)gKv5?TuXqJm4I4$bE9WsB4#rgiil}c zr&xqTwHBcmvL9mbD+OQ3I1}jcIp=5`&--~3?5^d;uSQRc7H?D!m$v4xP^spvoz$XI z7o-7Iz*eSyRE&;{edqqRFrHw^iTk~7G^ckoUAT|<3|2^3Tmvz`79VBzs{sR@biQd+ zj;Y5Y5M#R|s8H+>3z5JLgC0O-tjrEO(U+Vcw6d@E`$gr@@HAXJjOCsg((*Sw4Z~F1 z6Y*?vB>uj=-IHS7pem3*&hK{3-V|AE<;tc^0)FiZS&DrY&iWdx&(lmdg*r8}Nq2by zdor=1fL>{!)@4Q&$<`!!VyEP6MbVrf!;J5AilwUelvbaHPAW_gR|LzO+ZI^|4z(qO zv-$%8l7UalZY5EZbcIEBlclJ@$XG@PU%t^q zb_-dYDTlken!Z>QwnP-{i8$b-Rv7FJ*{z&yM_nYWCqFD&quG8F8}>=E+g7Vz)dsQ~ ziRzy;(-3>4%`ZZhnCg@E4RG!ki3vfw@#`B6o6}rg8juAhcAfH{ZhVgoAa_5Nz@XjBae;e-(^cMqUt!H50 z2-9KM(8M5_fqpsMz37RtH%e_S#rXS&Iy1JHVOuYC9`gxNZv#AXF|3tps@Z_&Tle$F=4f;c4Cm4(}!J z6g7axH~nwbCFacQL>A4ATouaqeHL38E_4P(x38Ta@0}*(1sgjOz>5UZQmba&I3v0j zu~~8R4zyDHW}QFN4LrcaA`01RIc?Z_ld%_9%A!nmP!utCAe46M67j^z_r;xRE)oqU z2`AqF^8%P@#u9mG_)Vr!nSp1Q&;ew3ts1+dc`_rs!7Iv^mg-wS1TloEDiMv^;gL?D zA>dPgSG|E*TO@bfv8n7UYIpQ+sj|bP73cp%l5~!NWR%txoa5Lwx^lA~r|C8D^0U6W)H0m3p(XII%e5?w&q|KYva1_BT zjzVzt3zBweDwdUnb@uM+X_C4voUgnrX23~hC1|GP812YG3ySttv9{ zQqWV)1^5N!R&#QI)0a{2oNO?y5MsaP=kOl*TT-C{#$XWo{N)p`zk$HPPRa`5d`GU} z!RVb`JJv0G8)&~?rkX}geA0`jC{sC1yC{;Z0=Bl(`6LFfArLA)7&Sm?*hE|1Zz&rZ z^%g4wnU;>}QaAw(7Bcu#oT@AXau8So3jL2>>s=mIbya~Jh2OkEXf(AV&m^YU$*4KiQ> znF*9#%Us1Ux@Kkz@Y{Cw?%W?aa9?4EXVZd$s&FPHkXI?XNL)C8|L&x5RY&$1-KCka-i|G@T2%FXrDfy+*mwVAk4fF;Np+PznbJ%dv+eX0Pv; zIU~Mi-A0AR4|tNj)_;zNxlQ09#ymcM>(>*eLcOrDTwNtFn1NyBpi9+Cg5+{y&!MPX z!Eh2?T{Tx1Voo{Mj-Z?#~~ZOI#V9a0;HRWieEIbh&ZIM)~qgWnOwYPh*2!RD)0Kfm6nL znutR$vq_IA`AnhSrUFahQ^`rPr?$$ zM0h<)SG?2^vv+SvOnxNzYn$6lWNjEJqhmwAp;;kbcAPHAH<8bsvf8o+B>Q6idVU8S z-pAJaRTYp?l)mOXP*oNyfMKo0H#Y2HQ_`qdwZLIzM3IRB1iKV0y?*qPTS>&VqeTjj z#R>Cr8bC~T1dw(|z5*#1qgg~+JKJKj1UqHxIMq<~1B9lSOWwc<`gVCiL7ZUDw{MuCEy@kza` z0g=#>ScjwZtJ^gxiR1v>++?qwD#=ooRy0?LpMG*XLq}6{B=;{eWp0LQ|6Ha0y<`pT zC&m9zpyL#kwUWxnPF6&LKD31(R=h9v^ zRawn0;^|RTg%q=G@yu>riyI=pT{sx|I-pmsV#bfz z&WVeDATMV>rvHvojXD*qxb-mLk~m=F#}K;noCQn%5O+Bf=!$qHR*94-4xH&VID$oK zem6_7S=p#S5!bANNQ$TmV1-hiAo#dhX_Vk#z?nAcPD~TllF}>QLSbfBqb1GT@2hYn zlB_X2)&aw;f|sn-Q9SfrYqe>o&+{BhX}TchMBF|J!b;%LM@UV8a66{6cLM z$4qU^2L!PPf7)_Vst}tH%z4q7SaH6D4NJ9uVtbu*J2;8Qmg~Dv2I^;&qTKIV<48-D ztSg!-u!EG8)LcuuuJLJLj`l+zX+31D(~wMWLWI_UlbeJD6gebONkfyZuqug;x9EiwlBdtidD1k^m9m>RSXSP>1)X4#)8kuz9lSyyLouGUnpd#oA z6@OJJHcQV@-}<+Jpqhb7;=^o)sKn7V3{ia%n;BeTHgyC+-1GNmXCD{@)0wPwSnrp)SCkaSYiUI5jNoFC%*}=mvdniOc@s!o+DnK zdSSk#IlF%b zpr(!XXTQ>%G0X_Ahw`$2Di=6W$>(@rKV0?vYHGT}x7qf#8NWML8Gnx`6k8@^PO!l> zp%#vK(kl3{bKxN@O{SE00Krd3WwQM$Zt};RaEqso^P*E!H&^q4B*x`%GAq{CO{;#{ zoUs?-qF3}@0y(?g9#uc5^N-j1M!|ilGY|L5A$s9dSw{`iT){3Z&}Ag^>s=DDea3n2E_o_s@yETF zjTYJ1n)w+x*9L*lK>bg9UlmkWvvs?13GOZ-2^$Np8+UgPY$QN%fU1+91hT6} z`Im(6@U|>j%c*PgjK{+AK2@D>B}G*&mNRExo@&t6`uylBNm!m|a!z(+I(phqIL!4n z{JD~?dpyp<@{r`be_8-SRHFl^p}u2M2FS7w-$iBK>vmen0z{WwM0=ia3gIeCi9lS( z2l@GRSKqf@-P5&Xr99V2)UQ{KeJEV-x077ib%mAmmwWbt!82i&_`^8l+l#!syv!mw z2S!)CW~~_a9eCT`i`!12#(T4p<$IkMtOKs3&;eT3T+UWo$~1WQR*DJf*$EPwnv%X& zsSJ$3uWRWwDeP$}F>x{c_|&kw&AXXjU|rPC$9cnfp6S4^-mjWYEIp{Gs91E#Qn|=a zZr!bd(jG9P&o?FSI+@2vj9LtwSs;G^1O`-RjDIUL!?+B#x3{G>mx(zY=gT{~9v-MX zML&1E{3K;F9?(RqQ}heJ4JJYozNtk({gyI7klD2!o^GLu-E zRK?7_U>EdEAx50uOO9~4 z9R0Bb!sJnn3K8wsZSg91_Lfd#E-;I)OxqT2!cKsdGlNWJf)pa#%#`u!iVE^r#5KQ0q0QgO|>H9MG z+w7Qh9*4WLi80m0@Ar4>0Ui4hNeW*A&}o^7*2Y~r{G@#b=5sHlI97wxo`H%fca(|G zUb_0G=^r>OEL-y!ZcP3K;m|U%Jisu_dztE4g!@ z!kHwD!6oOTD_$;0B6MkktVQ;!VD6`Cih!N`fs*)?gzrgRg28)xq_LDgLg>3Tsnd9oMF)>n@OclH{o%y8a}E?|@q?~`Kg>i+XcTgA5zzRx zd|#v25fH+1OFGo`-!aG<@IO)y?#ryl&-iaEn1YtH)Yz@B?#@-5S=NVtpKl>77&Sv{ z&-jtU#QYWw6+IBR>UrxogWzrt?3cb@_|2@-asyt#`q9>$c!^Q4^PLPCNtn=5SG!Ul zgC#rf$R0CNU*nkoFAnnhF2igUReJxc5BXky3@UrVgq*w0_l)fk5)+e zO@8WfM2=5fs2?}*7JccKKb%OdfbO_&ghssBkG;DuFn+Pm7LiTpZr=KHC7ffuEsXaT>(oK zI4}%pR)+n#X_cY!QTPP6mbrCpDVR`zFQ9R99fSTegIqHW0bcB8Oi~e*`Sg9(=L|n^ z@VGK{=QZcq-Xj%3*PoTTeVXK2diD)N;!@?I5eVS}efn$P?C|n}o<~JVp;l)d9C$@% z@~`RQ*A8>Z(ppzdTK|rl5CjHLKGMwcHOr?bnvq@uXCTVHP*U#u;bX1xq5djtsRU2V}v zcj|+8-|slubuv6!T=kB-zcVCvvwU;%$k(3^{`O|(dTn35%!}@5Ty|MFU1$0LHMGp9k>6GOOmjP&(~ZP zqq!#X+iK-4&89j~aVk%6J$7Eui3FYS*iSbjm=ZCP)Wvt1^VaRkZiX)JXBNMMx25Fv zY&vnM^WP@?G@qBn9AqOatw`&>A<0no#!3GhTMXxWe3h!=kkv)BX5Cs^j3rQ-m zUyz@t!fLNR-bZp3SrJQ49E&y%Na%bNHNqeFbgKHZ+38zhFYqOsmM>N79YD9&X?&a%Q5uM~o`}BTo-I`bA#8_>QunXbTz&njaYjRq zO;)Z1gs_l2(;0&=xT{HPbjE$kNi}Cet%#dIxTINOv8)|t;Q4knYpz`^hZP{kJkH7sf9&KC?2M>^)_=a|x1T&z{`%247yEncQ^ z9@30Q58euEyX#61P)2CT4%2KytbHQKFGzS+JQuJs*H0pnV5@RgpGK^?9vOzCwU^0| zwu2hJE{3(&(ed!GOcgqnyM(70SvJl-v|0Igb*{qAul@b}=C&C-x9X8$ZWIw@_7!zt zzi-cRp%KQ3QHSJuc#jrh41ozkF!kB-#}sU`UoRtOg)>z43V(O< zlNQ!-nhJTQhv&=wv7pP^jHnd&Vk7T-)t_+F(Omd+u$VwDQ8S8+*TJ#8S$ri`c)F!q z;Wsl$uVeJi{@qbApU-}3NRNP)bMX(neSAX`{G2w?#?4N{#|2^_s3i9aO5#p)2JzV zDlO_frbx7#Tr0nqy}_U(w`$EK*K`rUpxm_KgABiuP(UrCQ7 zWND+={Xola_`Egp#)fVXD0LP^SlG21Dc9-PS zi{l~ypnsBte9NA4M-z2ZkH2i|D8)y4&PJR7 z`oW2xyIITQD*5U*TP9pzFBnk%bQpr39BG%+$5N_%&u=vZI<~L+Eqe^UA^{&?&o=v2 zdVWd>r1m*97p^U#phb#7zfmvOo-LwBMCsS&60yuZBPKz0Dx|=66atp&dSY3jxY%39 z7;btD?qQN8(`=Z!5PYQO8nQWh6P(u*AvG z)+>-mc-~`+-oUln_1?Y-n<1?pc5361fck;WdjS{cr}K9!C<|o1b-y<y`mc*$ZyIu2~S2Oq?k#u>-#!Bn$Kbz(#?FbNl;MyZcdY_y~Sa|5x@mHqZ&Ua z>KpQioumPyc18^dEOaNNJ$on4&EeAg7;3D-#r!U2WAr>oLO{yMi`N2~mO*+}#S{|B zz~VJPC;k#efPk8=VbVr|!cc?p)sIwGOz`1q$0ewMW;*t_2)Pc3D%8Mg3k6-F?Q2%U z-znun^$S_A4UYza5!LOoXYBiyisg$D36YS)w+J>G=*p0=3;7CzN8LO`nIRVnmL_e% z*)@K;`Mh;$l{NR<`)@F|J@J(=%y^GuuOARJ2#&nXj)>X+GwIpQr}ZRlfBuKE(JqQ% zM4gr(!n1(ySH}-;?AC`2mq(Ux6Un@;xw$RlAy4zPY_EmU{OV*mnr)Gp$ZZmi($`*I z*bE=l9ny2rthZ%a&NBlp8sCfreUzF06dpsxW>*+(X#7?9g@yk@1l&Y_OGz?Ag2oCI zS+{yzHqWfwM$p_v9YNt}N$IGpoJ@_!Le<|B7(R$y4xQNXLciPK1pP3)v&75Wyo$uN1N7?93T2A5m^ z!-DPOY5P35cz#$twUEJ!=Zt)(QHb7dwkLfk3VTb1I}kN?f`mB>JNBSOq3>o7rEA zIgD=}kyfAXyK{7h%y6!#b9KPh;-Ql6ch0(klEsE{TW679(uuwi63P_eXDNO9qqD2ZTVL z%Lui|7l$1<T{xsD*Z);0DB#EfVFJi7hkr)aqpp-{MpgoFEG(2-0A!s zaQ&IMth?UiJ2$l^xEbcVOKQ48ZcSo~GnnmUBCn4x5FhJ-C?qC1iOe;1SB`i{PV_ZH z!KTLU-DaUWxjDi+Ey*!Uq6gv#<0872J2cUG%25YCsa20vFO>mQ842>ze={7!%X=qh5jWNyz3N8^Ivb;hjW_di}I33zFbhaS`8uY>qzPq-I`lYKu`) z2Vu+wwD-|eQJtDo?Y-nPgUYEy#}V|pyDm4kX_SWR!-R6_Z{J;>bgjIVWiu^!Q6^w8 z72OlK8sjT4>-E<_NI}0r zBer|yeo3WQ?$W}-Cw*2BONZO)gD7^>yTaQM7W{$$il{kaC@{=TkFfM8_cGrz{nquSmpi<5BAP_;=pUVbXN5R|N+?b2=OE_;y`wO|>WB{dXR|xt5rPvUAlDN%6wp+Hlc!|yFEVFyYa}Eob=w@a52FO`^Q%K<%)vi_l?wT=REyeEhl{O+kd$%}VRltA2DNa@EtFq1h@gYib!n#PQMDa_5Y6e#cIt z=KFHgU)YF1DtHF9gC2uaqV#U~jE+9{2WfVh8IMP`bdCAFwHWUU5~ffgnrAh{?5SOH zxAMtQW?w*N1>&AcystPp^nx>7vN0F?o=kV3gUZmZD~`h`vpIx*#5>S(XSDK85KEK> z#^gtC_=!4Do&HNo95~XEB7J%V;@VN3rc?$;khrquhQGCmX$U3XcKFa@uMYBBZ@}37 za+aQX+bM?mb|U6pTf(0iR{$`Fz0%LxO*LCuCKSeQLPYIM3GQB~Sy2*d9)|p$t-?7T zAl$p#wOiI|tC|QsG`dmx1?R1+qj@w3<4y{>giSZrA61liq~20*PQnmI#PmU0{Zky)EFTOgItjgI%%y{*d5PM3dn-MC-V}2b4_Qe-r<)rG)F)4#V<`w_Su}x zjeu_9lDBfCw#1zOMCAbhur&XbBVh2;)1%{P+GpRQ^Pp9mm8a#n+HXZ=;ZL7dwOJ?O znSU|P6?(UG&Pe&0rCwc!SpXJIyumRHwn@&jWu+rgTv-&c36&u@YVSf$` z@a3>=d%j5way|8?c!ERBPA`I(g5q@+K@yirhql zwz8jP8ep+>2P2-rf&`y#!)#o>_&hyp0}(2EuPeRxSAIP|k)lc^;tazS37)$pHNl2a zA-rPTd1X1hY?bC$qFI{jbT4C$l=nXGAuNI@7o!4l7m7ALx0vUQ zi+KAAy$CWbU1F^Vg;R8ZoVCfAA(9x3y{8rtswh8YvJmW+aEGO}7V8BleAZ{=@YVze zV?t?$4Nt1iw29ek@kDnZfIZERoH=PL&UT+5-h5{o$;i<`QyL^xu!XxjJCN5Hork<5 zM05_Wh_T1Q4`Yk~8X|EpiYFnvgwyQ37zvNG7?z~YS73K``Z4(Nj8)H33!Rdao~cmL z*jAXm$sDfxhiC|N`+GULZoff{2 zS8m(rIz<7F{QW>yEl%^?Fn+3!h}40pvdF;a#ShF|iU|n}96uNyKCxM9IEE|Q^aggY zByefZU+h}8*huD~QHtM4q~J6dP-VEb$yUYlezS*`b?>g!TP7c(tyV!< zLdrYk0?#^Njc;mAa}|g_Ij89X*UqUOQq?|lA%cRbj?eUH0%is!8naAcph@hsggL9+ zZSdn2u>pJNj)Se0%5AhY9-Q3h`bcOn%G`BxQaX!t^W9-8cjGO82}*T;$h0SqTvZJE{EAcq>3 z?g>eRrmDPX^y|$5S9oN!?pIf(W0lpvqB>Ec;KB&fZ_eAh{w17BjBwE&Dgg0w{3*f- z+YlkJzt7R5(Ol_)RHmDsC;C|dtRVo0nHmO1n#tEm6`(2?szeL33D1EWdJb~?_@DeB z!cg37*ytOLLD+xGV+&uw7X!p6!d?GU|9>zL-lWE|(9e4+%!mj=mXn38erp>G1X_`S z{SeIv`$wa}+qM0S9`a-+fhA)#nd;&j856= z{K_Sp9hkzjtwMl}U~|adXNV!@Acvs`6qezb1;b0P3Bz_}U$X~{JZaosEr^4LgZUpCa+WSDtFp=svq|CEE^*@1bDtk>M}_u&7Lb zr&iz5@E!|!1$unhRTK6B6Nd^0?hQ90NUz+RwD+h;_T@#_scl(h>e~6^{IJV!Wq@VX z$hwuc)mZU(7Fzdb5B`5Cfd5ne{}7Ou?Ib}%R7mAC3;Pe4R4VY+A+pK)vH!_{lbi&= z2VBmhJ4(X74Y0R@Li08#jLcJ5{yhc$G5D{U{tpiS;PBr*FV>{BhmmCdSk`~KwD%g#i0G8WlglkkMA@eOYAwWh#QM^*r(C>c%J#@}9 diff --git a/mrp_subcontracting_partner_management/tests/__init__.py b/mrp_subcontracting_partner_management/tests/__init__.py index c7819b815..6e67837d7 100644 --- a/mrp_subcontracting_partner_management/tests/__init__.py +++ b/mrp_subcontracting_partner_management/tests/__init__.py @@ -1,3 +1,3 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import test_create_sybcontractor_partner_location +from . import test_create_subcontractor_partner_location diff --git a/mrp_subcontracting_partner_management/tests/test_create_sybcontractor_partner_location.py b/mrp_subcontracting_partner_management/tests/test_create_subcontractor_partner_location.py similarity index 77% rename from mrp_subcontracting_partner_management/tests/test_create_sybcontractor_partner_location.py rename to mrp_subcontracting_partner_management/tests/test_create_subcontractor_partner_location.py index 2624fb4dd..6eee789b2 100644 --- a/mrp_subcontracting_partner_management/tests/test_create_sybcontractor_partner_location.py +++ b/mrp_subcontracting_partner_management/tests/test_create_subcontractor_partner_location.py @@ -5,23 +5,10 @@ from odoo.tests import common, tagged 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( { @@ -181,3 +168,66 @@ class TestSubcontractedPartner(common.SavepointCase): [("name", "=", partner_id.partner_buy_rule_id.name)] ) self.assertTrue(len(rules) == 2, "There are must be 2 subcontractor rules") + + def test_change_subcontractor_location(self): + expected_text = "Test partner" + partner = self.partner_obj.create( + { + "name": "Test partner", + "is_company": True, + "is_subcontractor_partner": True, + } + ) + location = partner.property_stock_subcontractor + self.assertEqual( + location.name, + expected_text, + msg="Location name must be equal to {}".format(expected_text), + ) + + fields = [ + "subcontracted_created_location_id", + "partner_buy_rule_id", + "partner_resupply_rule_id", + "property_stock_subcontractor", + ] + expected_text = "Test partner 1" + partner.name = expected_text + for field in fields: + location = getattr(partner, field) + self.assertEqual( + location.name, + expected_text, + msg="Record name must be equal to {}".format(expected_text), + ) + + picking = partner.partner_picking_type_id + expected_text = "%s: IN" % expected_text + self.assertEqual( + picking.name, + expected_text, + msg="Record name must be equal to '{}'".format(expected_text), + ) + self.assertEqual( + picking.sequence_code, "Tp1I", msg="Sequence code must be equal to 'Tp1I'" + ) + + def test_action_subcontractor_location_stock(self): + self.partner_id.update({"is_subcontractor_partner": True}) + action = self.partner_id.action_subcontractor_location_stock() + self.assertEqual( + action.get("domain"), + [ + ( + "location_id", + "child_of", + self.partner_id.property_stock_subcontractor.ids, + ) + ], + msg="Domains must be the same", + ) + self.assertEqual( + action.get("res_model"), + "stock.quant", + msg="Model must be equal to 'stock.quant'", + ) diff --git a/mrp_subcontracting_partner_management/views/res_partner.xml b/mrp_subcontracting_partner_management/views/res_partner.xml deleted file mode 100644 index 1802199a0..000000000 --- a/mrp_subcontracting_partner_management/views/res_partner.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - res.partner.form.inherit.subcontractor - res.partner - - - - - - - - - - diff --git a/mrp_subcontracting_partner_management/views/res_partner_views.xml b/mrp_subcontracting_partner_management/views/res_partner_views.xml new file mode 100644 index 000000000..29c77d7c0 --- /dev/null +++ b/mrp_subcontracting_partner_management/views/res_partner_views.xml @@ -0,0 +1,36 @@ + + + + + res.partner.form.inherit.subcontractor + res.partner + + + + + + + + + + res.partner.stock.property.form.inherit + res.partner + + + + +

Configuration

-
    -
  • No configuration is required
  • -
+

To see newly created rules, go to Settings > Inventory and activate “Multi step routes”.

+

It is also possible to allow a user to check inventory locations in Sales & Purchases tab of Vendor without activating dev mode. +To do so, enable technical extra right Display Inventory section on Vendor in user.

Usage

@@ -408,6 +408,9 @@ ul.auto-toc {
  • Enable the “Subcontractor” checkbox
  • New entities are created or existing are used if were created previously
  • When disabled all associated enties will be archived
  • +
  • When name of subcontractor is updated, names of entities are updated automatically.
  • +
  • It is also possible to check inventory locations using Subcontractor Location Stock smart button on partner.
  • +
  • When name of subcontractor is updated, names of entities (subcontracting location, operation type, rules) are updated automatically.
  • From 228d88b59146122da478b2203a20eb512540388e Mon Sep 17 00:00:00 2001 From: mymage Date: Sun, 18 Dec 2022 15:09:00 +0000 Subject: [PATCH 08/12] Translated using Weblate (Italian) Currently translated at 100.0% (12 of 12 strings) Translation: manufacture-14.0/manufacture-14.0-mrp_subcontracting_partner_management Translate-URL: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_subcontracting_partner_management/it/ --- .../i18n/it.po | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mrp_subcontracting_partner_management/i18n/it.po b/mrp_subcontracting_partner_management/i18n/it.po index dcd4d039a..10880f9fd 100644 --- a/mrp_subcontracting_partner_management/i18n/it.po +++ b/mrp_subcontracting_partner_management/i18n/it.po @@ -6,15 +6,15 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2022-09-21 19:07+0000\n" -"Last-Translator: Samuele Mariani \n" +"PO-Revision-Date: 2022-12-18 17:44+0000\n" +"Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.3.2\n" +"X-Generator: Weblate 4.14.1\n" #. module: mrp_subcontracting_partner_management #: model:ir.model,name:mrp_subcontracting_partner_management.model_res_partner @@ -36,13 +36,13 @@ msgstr "ID" #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type__is_subcontractor msgid "Is Subcontractor" -msgstr "è un Terzista" +msgstr "È un terzista" #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner____last_update #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_stock_picking_type____last_update msgid "Last Modified on" -msgstr "Ultima Modifica il" +msgstr "Ultima modifica il" #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_buy_rule_id @@ -54,24 +54,24 @@ msgstr "Regola di acquisto del partner" #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_picking_type_id #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_picking_type_id msgid "Partner Picking Type" -msgstr "Tipo Trasferimento del partner" +msgstr "Tipo prelievo del partner" #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__partner_resupply_rule_id #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__partner_resupply_rule_id msgid "Partner Resupply Rule" -msgstr "Rifornimento Regola Partner" +msgstr "Regola rifornimento del partner" #. module: mrp_subcontracting_partner_management #: model:ir.model,name:mrp_subcontracting_partner_management.model_stock_picking_type msgid "Picking Type" -msgstr "Tipo trasferimento" +msgstr "Tipo prelievo" #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__subcontracted_created_location_id #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_users__subcontracted_created_location_id msgid "Subcontracted Created Location" -msgstr "Ubicazione per terzista" +msgstr "Ubicazione creata per terzista" #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__is_subcontractor_partner @@ -83,4 +83,4 @@ msgstr "Terzista" #. module: mrp_subcontracting_partner_management #: model_terms:ir.ui.view,arch_db:mrp_subcontracting_partner_management.view_partner_stock_form msgid "Subcontractor Location Stock" -msgstr "" +msgstr "Magazzino ubicazione terzista" From efbe3f6a3829287b2f35472676f059054a6469c1 Mon Sep 17 00:00:00 2001 From: Francesco Foresti Date: Mon, 17 Jul 2023 14:11:48 +0000 Subject: [PATCH 09/12] Translated using Weblate (Italian) Currently translated at 100.0% (12 of 12 strings) Translation: manufacture-14.0/manufacture-14.0-mrp_subcontracting_partner_management Translate-URL: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_subcontracting_partner_management/it/ [UPD] README.rst --- .../README.rst | 15 +++-- .../i18n/it.po | 8 +-- .../static/description/index.html | 58 ++++++++++--------- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/mrp_subcontracting_partner_management/README.rst b/mrp_subcontracting_partner_management/README.rst index 2e49dd7bd..e0e8e7954 100644 --- a/mrp_subcontracting_partner_management/README.rst +++ b/mrp_subcontracting_partner_management/README.rst @@ -2,10 +2,13 @@ Subcontracting Partner Management ================================= -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:16e892122edafe36f84bd67e1692db60a22191ded7ce950e5d3295abf1ac13b4 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -19,11 +22,11 @@ Subcontracting Partner Management .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png :target: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_subcontracting_partner_management :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/129/14.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/manufacture&target_branch=14.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| The goal of this module is to simplify the management of the partner properties used in MRP Subcontracting. @@ -77,7 +80,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed +If you spotted it first, help us to smash it by providing a detailed and welcomed `feedback `_. Do not contact contributors directly about support or help with technical issues. diff --git a/mrp_subcontracting_partner_management/i18n/it.po b/mrp_subcontracting_partner_management/i18n/it.po index 10880f9fd..4b016729b 100644 --- a/mrp_subcontracting_partner_management/i18n/it.po +++ b/mrp_subcontracting_partner_management/i18n/it.po @@ -6,15 +6,15 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2022-12-18 17:44+0000\n" -"Last-Translator: mymage \n" +"PO-Revision-Date: 2023-07-17 17:16+0000\n" +"Last-Translator: Francesco Foresti \n" "Language-Team: none\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.14.1\n" +"X-Generator: Weblate 4.17\n" #. module: mrp_subcontracting_partner_management #: model:ir.model,name:mrp_subcontracting_partner_management.model_res_partner @@ -65,7 +65,7 @@ msgstr "Regola rifornimento del partner" #. module: mrp_subcontracting_partner_management #: model:ir.model,name:mrp_subcontracting_partner_management.model_stock_picking_type msgid "Picking Type" -msgstr "Tipo prelievo" +msgstr "Tipologia prelievo" #. module: mrp_subcontracting_partner_management #: model:ir.model.fields,field_description:mrp_subcontracting_partner_management.field_res_partner__subcontracted_created_location_id diff --git a/mrp_subcontracting_partner_management/static/description/index.html b/mrp_subcontracting_partner_management/static/description/index.html index 0bff3897e..10ee66800 100644 --- a/mrp_subcontracting_partner_management/static/description/index.html +++ b/mrp_subcontracting_partner_management/static/description/index.html @@ -1,20 +1,20 @@ - + - + Subcontracting Partner Management