diff --git a/account_operating_unit/models/account_journal.py b/account_operating_unit/models/account_journal.py index 203d5b5b17..d6b33bd005 100644 --- a/account_operating_unit/models/account_journal.py +++ b/account_operating_unit/models/account_journal.py @@ -8,9 +8,14 @@ class AccountJournal(models.Model): _inherit = "account.journal" + + @api.model + def _default_operating_unit_id(self): + return self.env["res.users"].operating_unit_default_get() operating_unit_id = fields.Many2one( comodel_name="operating.unit", + default=_default_operating_unit_id, help="Operating Unit that will be used in payments, " "when this journal is used.", ) diff --git a/pos_operating_unit/README.rst b/pos_operating_unit/README.rst new file mode 100644 index 0000000000..4d285fa9f2 --- /dev/null +++ b/pos_operating_unit/README.rst @@ -0,0 +1,84 @@ +===================== +Operating Unit in POS +===================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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%2Foperating--unit-lightgray.png?logo=github + :target: https://github.com/OCA/operating-unit/tree/15.0/pos_operating_unit + :alt: OCA/operating-unit +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/operating-unit-15-0/operating-unit-15-0-pos_operating_unit + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/213/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module introduces the following features: + +- It introduces the operating unit to the pos order, pos session, pos payment and pos config. +- The operating unit is copied to the picking, invoice and all related accounting moves. +- It implements user's security rules. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +#. Open a POS Session: The POS Config Operating Unit will be assigned to the Session. +#. Create a POS Order: The Session Operating Unit will be assigned to the Order. +#. All accounting operations will be performed on the Order Operating Unit. + +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 +~~~~~~~ + +* Jarsa + +Contributors +~~~~~~~~~~~~ + +* Alan Ramos + +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/operating-unit `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_operating_unit/__init__.py b/pos_operating_unit/__init__.py new file mode 100644 index 0000000000..8ffc31e42b --- /dev/null +++ b/pos_operating_unit/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import models diff --git a/pos_operating_unit/__manifest__.py b/pos_operating_unit/__manifest__.py new file mode 100644 index 0000000000..6cd079629d --- /dev/null +++ b/pos_operating_unit/__manifest__.py @@ -0,0 +1,26 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +{ + "name": "Operating Unit in POS", + "summary": "Adds the concept of operating unit (OU) in POS" "management", + "version": "15.0.1.0.0", + "author": "Jarsa," "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/operating-unit", + "category": "Point of Sale", + "depends": [ + "point_of_sale", + "stock_account_operating_unit", + ], + "license": "LGPL-3", + "data": [ + "security/pos_security.xml", + "views/pos_order_view.xml", + "views/pos_config_view.xml", + "views/pos_session_view.xml", + "views/pos_payment_view.xml", + "views/report_pos_order_view.xml", + ], + "demo": [], + "installable": True, +} diff --git a/pos_operating_unit/models/__init__.py b/pos_operating_unit/models/__init__.py new file mode 100644 index 0000000000..b1a32244af --- /dev/null +++ b/pos_operating_unit/models/__init__.py @@ -0,0 +1,11 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import account_payment +from . import pos_config +from . import pos_order_line +from . import pos_order +from . import pos_payment +from . import pos_session +from . import report_pos_order +from . import stock_picking diff --git a/pos_operating_unit/models/account_payment.py b/pos_operating_unit/models/account_payment.py new file mode 100644 index 0000000000..a454e7b039 --- /dev/null +++ b/pos_operating_unit/models/account_payment.py @@ -0,0 +1,15 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import api, models + + +class AccountPayment(models.Model): + _inherit = "account.payment" + + @api.model + def create(self, values): + if values.get("pos_session_id"): + session = self.env["pos.session"].browse(values["pos_session_id"]) + values["operating_unit_id"] = session.operating_unit_id.id + return super().create(values) diff --git a/pos_operating_unit/models/pos_config.py b/pos_operating_unit/models/pos_config.py new file mode 100644 index 0000000000..0df1ee11dd --- /dev/null +++ b/pos_operating_unit/models/pos_config.py @@ -0,0 +1,75 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PosConfig(models.Model): + _inherit = "pos.config" + + operating_unit_id = fields.Many2one( + comodel_name="operating.unit", + ) + + @api.constrains("operating_unit_id", "company_id") + def _check_company_operating_unit(self): + for rec in self: + if ( + rec.company_id + and rec.operating_unit_id + and rec.company_id != rec.operating_unit_id.company_id + ): + raise ValidationError( + _( + "Configuration error. The Company in the POS Config " + "and in the Operating Unit must be the same." + ) + ) + + @api.constrains("operating_unit_id", "invoice_journal_id") + def _check_invoice_journal_operating_unit(self): + for rec in self: + if ( + rec.invoice_journal_id + and rec.operating_unit_id + and rec.invoice_journal_id.operating_unit_id != rec.operating_unit_id + ): + raise ValidationError( + _( + "Configuration error. The Operating Unit in the Invoice Journal" + " and in the POS Config must be the same." + ) + ) + + @api.constrains("operating_unit_id", "journal_id") + def _check_journal_operating_unit(self): + for rec in self: + if ( + rec.journal_id + and rec.operating_unit_id + and rec.journal_id.operating_unit_id != rec.operating_unit_id + ): + raise ValidationError( + _( + "Configuration error. The Operating Unit in the POS Journal" + " and in the POS Config must be the same." + ) + ) + + @api.constrains("operating_unit_id", "picking_type_id") + def _check_picking_type_operating_unit(self): + for rec in self: + warehouse = rec.picking_type_id.warehouse_id + if ( + warehouse.operating_unit_id + and rec.picking_type_id + and rec.operating_unit_id + and warehouse.operating_unit_id != rec.operating_unit_id + ): + raise ValidationError( + _( + "Configuration error. The Operating Unit in the Picking Type" + "Warehouse and in the POS Config must be the same." + ) + ) diff --git a/pos_operating_unit/models/pos_order.py b/pos_operating_unit/models/pos_order.py new file mode 100644 index 0000000000..95b927f8b5 --- /dev/null +++ b/pos_operating_unit/models/pos_order.py @@ -0,0 +1,51 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PosOrder(models.Model): + _inherit = "pos.order" + + operating_unit_id = fields.Many2one( + comodel_name="operating.unit", + string="Operating Unit", + readonly=True, + ) + + def _prepare_invoice_vals(self): + vals = super()._prepare_invoice_vals() + vals["operating_unit_id"] = self.operating_unit_id.id + return vals + + @api.model + def _order_fields(self, ui_order): + res = super()._order_fields(ui_order) + res["operating_unit_id"] = ( + self.env["pos.session"] + .browse(ui_order["pos_session_id"]) + .operating_unit_id.id + ) + return res + + @api.model + def _payment_fields(self, order, ui_paymentline): + res = super()._payment_fields(order, ui_paymentline) + res["operating_unit_id"] = order.operating_unit_id.id + return res + + @api.constrains("operating_unit_id", "company_id") + def _check_company_operating_unit(self): + for rec in self: + if ( + rec.company_id + and rec.operating_unit_id + and rec.company_id != rec.operating_unit_id.company_id + ): + raise ValidationError( + _( + "Configuration error. The Company in the POS Order " + "and in the Operating Unit must be the same." + ) + ) diff --git a/pos_operating_unit/models/pos_order_line.py b/pos_operating_unit/models/pos_order_line.py new file mode 100644 index 0000000000..cb373d9a0a --- /dev/null +++ b/pos_operating_unit/models/pos_order_line.py @@ -0,0 +1,23 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class PosOrderLine(models.Model): + _inherit = "pos.order.line" + + operating_unit_id = fields.Many2one( + comodel_name="operating.unit", + string="Operating Unit", + readonly=True, + ) + + def _order_line_fields(self, line, session_id=None): + res = super()._order_line_fields(line, session_id) + session = ( + self.env["pos.session"].browse(session_id).exists() if session_id else None + ) + if session: + res[2]["operating_unit_id"] = session.operating_unit_id.id + return res diff --git a/pos_operating_unit/models/pos_payment.py b/pos_operating_unit/models/pos_payment.py new file mode 100644 index 0000000000..d0a3e8d721 --- /dev/null +++ b/pos_operating_unit/models/pos_payment.py @@ -0,0 +1,14 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class PosPayment(models.Model): + _inherit = "pos.payment" + + operating_unit_id = fields.Many2one( + comodel_name="operating.unit", + string="Operating Unit", + readonly=True, + ) diff --git a/pos_operating_unit/models/pos_session.py b/pos_operating_unit/models/pos_session.py new file mode 100644 index 0000000000..848ce2d333 --- /dev/null +++ b/pos_operating_unit/models/pos_session.py @@ -0,0 +1,55 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class PosSession(models.Model): + _inherit = "pos.session" + + operating_unit_id = fields.Many2one( + comodel_name="operating.unit", + string="Operating Unit", + readonly=True, + ) + + @api.model + def create(self, values): + config_id = values.get("config_id") or self.env.context.get("default_config_id") + pos_config = self.env["pos.config"].browse(config_id) + if pos_config.operating_unit_id: + values["operating_unit_id"] = pos_config.operating_unit_id.id + return super().create(values) + + def _get_sale_vals(self, key, amount, amount_converted): + res = super()._get_sale_vals(key, amount, amount_converted) + move = self.env["account.move"].browse(res["move_id"]) + move.write({"operating_unit_id": self.operating_unit_id.id}) + res["operating_unit_id"] = self.operating_unit_id.id + return res + + def _credit_amounts( + self, + partial_move_line_vals, + amount, + amount_converted, + force_company_currency=False, + ): + res = super()._credit_amounts( + partial_move_line_vals, amount, amount_converted, force_company_currency + ) + res["operating_unit_id"] = self.operating_unit_id.id + return res + + def _debit_amounts( + self, + partial_move_line_vals, + amount, + amount_converted, + force_company_currency=False, + ): + res = super()._debit_amounts( + partial_move_line_vals, amount, amount_converted, force_company_currency + ) + res["operating_unit_id"] = self.operating_unit_id.id + return res diff --git a/pos_operating_unit/models/report_pos_order.py b/pos_operating_unit/models/report_pos_order.py new file mode 100644 index 0000000000..7e659ee5b3 --- /dev/null +++ b/pos_operating_unit/models/report_pos_order.py @@ -0,0 +1,20 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class ReportPosOrder(models.Model): + _inherit = "report.pos.order" + + operating_unit_id = fields.Many2one("operating.unit", readonly=True) + + def _select(self): + res = super()._select() + res += ",l.operating_unit_id AS operating_unit_id" + return res + + def _group_by(self): + res = super()._group_by() + res += ",l.operating_unit_id" + return res diff --git a/pos_operating_unit/models/stock_picking.py b/pos_operating_unit/models/stock_picking.py new file mode 100644 index 0000000000..5b81f7cc80 --- /dev/null +++ b/pos_operating_unit/models/stock_picking.py @@ -0,0 +1,17 @@ +# Copyright 2022 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import models + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + def _prepare_picking_vals( + self, partner, picking_type, location_id, location_dest_id + ): + res = super()._prepare_picking_vals( + partner, picking_type, location_id, location_dest_id + ) + res["operating_unit_id"] = picking_type.warehouse_id.operating_unit_id.id + return res diff --git a/pos_operating_unit/readme/CONTRIBUTORS.rst b/pos_operating_unit/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..9c31b0ce2d --- /dev/null +++ b/pos_operating_unit/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Alan Ramos diff --git a/pos_operating_unit/readme/DESCRIPTION.rst b/pos_operating_unit/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..caadcb3124 --- /dev/null +++ b/pos_operating_unit/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module introduces the following features: + +- It introduces the operating unit to the pos order, pos session, pos payment and pos config. +- The operating unit is copied to the picking, invoice and all related accounting moves. +- It implements user's security rules. diff --git a/pos_operating_unit/readme/USAGE.rst b/pos_operating_unit/readme/USAGE.rst new file mode 100644 index 0000000000..e435a0362c --- /dev/null +++ b/pos_operating_unit/readme/USAGE.rst @@ -0,0 +1,3 @@ +#. Open a POS Session: The POS Config Operating Unit will be assigned to the Session. +#. Create a POS Order: The Session Operating Unit will be assigned to the Order. +#. All accounting operations will be performed on the Order Operating Unit. diff --git a/pos_operating_unit/security/pos_security.xml b/pos_operating_unit/security/pos_security.xml new file mode 100644 index 0000000000..e394251f23 --- /dev/null +++ b/pos_operating_unit/security/pos_security.xml @@ -0,0 +1,66 @@ + + + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + POS Orders from allowed operating units + + + + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + POS Orders lines from allowed operating units + + + + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + POS Sessions lines from allowed operating units + + + + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + POS Payments lines from allowed operating units + + + + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + POS Config lines from allowed operating units + + + + + + + diff --git a/pos_operating_unit/static/description/index.html b/pos_operating_unit/static/description/index.html new file mode 100644 index 0000000000..1ea99914f5 --- /dev/null +++ b/pos_operating_unit/static/description/index.html @@ -0,0 +1,433 @@ + + + + + + +Operating Unit in POS + + + +
+

Operating Unit in POS

+ + +

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

+

This module introduces the following features:

+
    +
  • It introduces the operating unit to the pos order, pos session, pos payment and pos config.
  • +
  • The operating unit is copied to the picking, invoice and all related accounting moves.
  • +
  • It implements user’s security rules.
  • +
+

Table of contents

+ +
+

Usage

+
    +
  1. Open a POS Session: The POS Config Operating Unit will be assigned to the Session.
  2. +
  3. Create a POS Order: The Session Operating Unit will be assigned to the Order.
  4. +
  5. All accounting operations will be performed on the Order Operating Unit.
  6. +
+
+
+

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

+
    +
  • Jarsa
  • +
+
+
+

Contributors

+ +
+
+

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/operating-unit project on GitHub.

+

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

+
+
+
+ + diff --git a/pos_operating_unit/views/pos_config_view.xml b/pos_operating_unit/views/pos_config_view.xml new file mode 100644 index 0000000000..289454ca69 --- /dev/null +++ b/pos_operating_unit/views/pos_config_view.xml @@ -0,0 +1,52 @@ + + + + pos.config.operating.unit.view.tree + pos.config + + + + + + + + + pos.config.operating.unit.view.search + pos.config + + + + + + + + + + pos.config.operating.unit.view.form + pos.config + + + +
+
+
+
+
+
+
+
diff --git a/pos_operating_unit/views/pos_order_view.xml b/pos_operating_unit/views/pos_order_view.xml new file mode 100644 index 0000000000..5a554fba5b --- /dev/null +++ b/pos_operating_unit/views/pos_order_view.xml @@ -0,0 +1,55 @@ + + + + pos.order.operating.unit.view.tree + pos.order + + + + + + + + + pos.order.operating.unit.view.search + pos.order + + + + + + + + + + + + pos.order.operating.unit.view.form + pos.order + + + + + + + + + + + + + + + + + diff --git a/pos_operating_unit/views/pos_payment_view.xml b/pos_operating_unit/views/pos_payment_view.xml new file mode 100644 index 0000000000..29c626f91b --- /dev/null +++ b/pos_operating_unit/views/pos_payment_view.xml @@ -0,0 +1,40 @@ + + + + pos.payment.operating.unit.view.tree + pos.payment + + + + + + + + + pos.payment.operating.unit.view.search + pos.payment + + + + + + + + + + + + pos.payment.operating.unit.view.form + pos.payment + + + + + + + + diff --git a/pos_operating_unit/views/pos_session_view.xml b/pos_operating_unit/views/pos_session_view.xml new file mode 100644 index 0000000000..87b928c2ce --- /dev/null +++ b/pos_operating_unit/views/pos_session_view.xml @@ -0,0 +1,40 @@ + + + + pos.session.operating.unit.view.tree + pos.session + + + + + + + + + pos.session.operating.unit.view.search + pos.session + + + + + + + + + + + + pos.session.operating.unit.view.form + pos.session + + + + + + + + diff --git a/pos_operating_unit/views/report_pos_order_view.xml b/pos_operating_unit/views/report_pos_order_view.xml new file mode 100644 index 0000000000..9201dc0360 --- /dev/null +++ b/pos_operating_unit/views/report_pos_order_view.xml @@ -0,0 +1,20 @@ + + + + report.pos.order.operating.unit.view.search + report.pos.order + + + + + + + + + + + diff --git a/purchase_operating_unit/README.rst b/purchase_operating_unit/README.rst new file mode 100644 index 0000000000..48a6aea0f1 --- /dev/null +++ b/purchase_operating_unit/README.rst @@ -0,0 +1,101 @@ +================================= +Operating Unit in Purchase Orders +================================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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%2Foperating--unit-lightgray.png?logo=github + :target: https://github.com/OCA/operating-unit/tree/14.0/purchase_operating_unit + :alt: OCA/operating-unit +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/operating-unit-14-0/operating-unit-14-0-purchase_operating_unit + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/213/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module introduces the following features: + +- It introduces the operating unit to the purchase order. +- The operating unit is copied to the invoice. +- The operating unit is copied to the stock picking. +- It implements user's security rules. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +#. Create a PO: the Default Operating Unit is assigned to the PO. If you want, + you can change to another Operating Unit. +#. Validate the PO: the Operating Unit is propagated to the Pickings and + Invoices. +#. From the invoice, it is not possible to change the Operating Unit, it has to + be the same as the one of the PO. + +Known issues / Roadmap +====================== + +Procurement Orders were removed. Procurement Operating Unit module is +deprecated. Stock Operating Unit ensures consistency between the operating unit +from different models. + +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 +~~~~~~~ + +* ForgeFlow +* Serpent Consulting Services Pvt. Ltd. + +Contributors +~~~~~~~~~~~~ + +* Jordi Ballester Alomar +* Aaron Henriquez +* Sudhir Arya +* Nicola Studer +* Nikul Chaudhary +* Kitti U. + +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/operating-unit `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/purchase_operating_unit/__init__.py b/purchase_operating_unit/__init__.py new file mode 100644 index 0000000000..133f68732d --- /dev/null +++ b/purchase_operating_unit/__init__.py @@ -0,0 +1,2 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import models diff --git a/purchase_operating_unit/__manifest__.py b/purchase_operating_unit/__manifest__.py new file mode 100644 index 0000000000..2bfcaa471b --- /dev/null +++ b/purchase_operating_unit/__manifest__.py @@ -0,0 +1,25 @@ +# © 2015-17 ForgeFlow S.L. +# - Jordi Ballester Alomar +# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +{ + "name": "Operating Unit in Purchase Orders", + "summary": "Adds the concecpt of operating unit (OU) in purchase order " + "management", + "version": "15.0.1.0.0", + "author": "ForgeFlow, " + "Serpent Consulting Services Pvt. Ltd.," + "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/operating-unit", + "category": "Purchase Management", + "depends": ["purchase", "account_operating_unit"], + "license": "LGPL-3", + "data": [ + "security/purchase_security.xml", + "views/purchase_order_view.xml", + "views/purchase_order_line_view.xml", + ], + "demo": ["demo/purchase_order_demo.xml"], + "installable": True, +} diff --git a/purchase_operating_unit/demo/purchase_order_demo.xml b/purchase_operating_unit/demo/purchase_order_demo.xml new file mode 100644 index 0000000000..af80cc9aac --- /dev/null +++ b/purchase_operating_unit/demo/purchase_order_demo.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/purchase_operating_unit/i18n/purchase_operating_unit.pot b/purchase_operating_unit/i18n/purchase_operating_unit.pot new file mode 100644 index 0000000000..7ce760c8a5 --- /dev/null +++ b/purchase_operating_unit/i18n/purchase_operating_unit.pot @@ -0,0 +1,112 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_operating_unit +# +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: purchase_operating_unit +#: code:addons/purchase_operating_unit/models/purchase_order.py:0 +#, python-format +msgid "" +"Configuration error. The Company in the Purchase Order and in the Operating " +"Unit must be the same." +msgstr "" + +#. module: purchase_operating_unit +#: code:addons/purchase_operating_unit/models/purchase_order.py:0 +#, python-format +msgid "" +"Configuration error. The Quotation / Purchase Order and the Warehouse of " +"picking type must belong to the same Operating Unit." +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order__picking_type_id +msgid "Deliver To" +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model.fields,field_description:purchase_operating_unit.field_account_move__display_name +#: model:ir.model.fields,field_description:purchase_operating_unit.field_account_move_line__display_name +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order__display_name +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order_line__display_name +msgid "Display Name" +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model.fields,field_description:purchase_operating_unit.field_account_move__id +#: model:ir.model.fields,field_description:purchase_operating_unit.field_account_move_line__id +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order__id +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order_line__id +msgid "ID" +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model,name:purchase_operating_unit.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model,name:purchase_operating_unit.model_account_move_line +msgid "Journal Item" +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model.fields,field_description:purchase_operating_unit.field_account_move____last_update +#: model:ir.model.fields,field_description:purchase_operating_unit.field_account_move_line____last_update +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order____last_update +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: purchase_operating_unit +#: code:addons/purchase_operating_unit/models/purchase_order.py:0 +#, python-format +msgid "" +"No Warehouse found with the Operating Unit indicated in the Purchase Order" +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order__operating_unit_id +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order_line__operating_unit_id +#: model_terms:ir.ui.view,arch_db:purchase_operating_unit.purchase_order_line_search +#: model_terms:ir.ui.view,arch_db:purchase_operating_unit.view_purchase_order_filter +msgid "Operating Unit" +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model,name:purchase_operating_unit.model_purchase_order +msgid "Purchase Order" +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model,name:purchase_operating_unit.model_purchase_order_line +msgid "Purchase Order Line" +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model.fields,field_description:purchase_operating_unit.field_purchase_order__requesting_operating_unit_id +msgid "Requesting Operating Unit" +msgstr "" + +#. module: purchase_operating_unit +#: code:addons/purchase_operating_unit/models/account_invoice.py:0 +#, python-format +msgid "" +"The operating unit of the purchase order must be the same as in the " +"associated invoices." +msgstr "" + +#. module: purchase_operating_unit +#: model:ir.model.fields,help:purchase_operating_unit.field_purchase_order__picking_type_id +msgid "This will determine operation type of incoming shipment" +msgstr "" diff --git a/purchase_operating_unit/models/__init__.py b/purchase_operating_unit/models/__init__.py new file mode 100644 index 0000000000..105a10e6f7 --- /dev/null +++ b/purchase_operating_unit/models/__init__.py @@ -0,0 +1,3 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import purchase_order +from . import account_move diff --git a/purchase_operating_unit/models/account_move.py b/purchase_operating_unit/models/account_move.py new file mode 100644 index 0000000000..dd7a5a32cb --- /dev/null +++ b/purchase_operating_unit/models/account_move.py @@ -0,0 +1,52 @@ +# Copyright 2015-17 ForgeFlow S.L. +# - Jordi Ballester Alomar +# Copyright 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import _, api, exceptions, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + # Load all unsold PO lines + @api.onchange("purchase_vendor_bill_id", "purchase_id") + def _onchange_purchase_auto_complete(self): + """ + Override to add Operating Unit from Purchase Order to Invoice. + """ + purchase_id = self.purchase_id + if self.purchase_vendor_bill_id.purchase_order_id: + purchase_id = self.purchase_vendor_bill_id.purchase_order_id + if purchase_id and purchase_id.operating_unit_id: + # Assign OU from PO to Invoice + self.operating_unit_id = purchase_id.operating_unit_id.id + return super()._onchange_purchase_auto_complete() + + @api.onchange("operating_unit_id") + def _onchange_operating_unit_id(self): + """ + Show only the purchase orders that have the same operating unit + """ + return { + "domain": { + "purchase_id": [("operating_unit_id", "=", self.operating_unit_id.id)] + } + } + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + @api.constrains("operating_unit_id", "purchase_line_id") + def _check_invoice_ou(self): + for line in self: + if ( + line.purchase_line_id + and line.operating_unit_id != line.purchase_line_id.operating_unit_id + ): + raise exceptions.ValidationError( + _( + "The operating unit of the purchase order must " + "be the same as in the associated invoices." + ) + ) diff --git a/purchase_operating_unit/models/purchase_order.py b/purchase_operating_unit/models/purchase_order.py new file mode 100644 index 0000000000..0b44463d64 --- /dev/null +++ b/purchase_operating_unit/models/purchase_order.py @@ -0,0 +1,73 @@ +# Copyright 2015-17 ForgeFlow S.L. +# - Jordi Ballester Alomar +# Copyright 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PurchaseOrder(models.Model): + _inherit = "purchase.order" + + READONLY_STATES = { + "purchase": [("readonly", True)], + "done": [("readonly", True)], + "cancel": [("readonly", True)], + } + + operating_unit_id = fields.Many2one( + comodel_name="operating.unit", + string="Operating Unit", + states=READONLY_STATES, + default=lambda self: ( + self.env["res.users"].operating_unit_default_get(self.env.uid) + ), + ) + + requesting_operating_unit_id = fields.Many2one( + comodel_name="operating.unit", + string="Requesting Operating Unit", + states=READONLY_STATES, + default=lambda self: ( + self.env["res.users"].operating_unit_default_get(self.env.uid) + ), + ) + + @api.constrains("operating_unit_id", "company_id") + def _check_company_operating_unit(self): + for record in self: + if ( + record.company_id + and record.operating_unit_id + and record.company_id != record.operating_unit_id.company_id + ): + raise ValidationError( + _( + "Configuration error. The Company in the Purchase Order " + "and in the Operating Unit must be the same." + ) + ) + + def _prepare_invoice(self): + invoice_vals = super()._prepare_invoice() + journal = self.env["account.journal"].search( + [ + ("operating_unit_id", "=", self.operating_unit_id.id), + ("type", "=", "purchase"), + ] + ) + invoice_vals.update( + { + "operating_unit_id": self.operating_unit_id.id, + "journal_id": journal.id, + } + ) + return invoice_vals + + +class PurchaseOrderLine(models.Model): + _inherit = "purchase.order.line" + + operating_unit_id = fields.Many2one( + related="order_id.operating_unit_id", string="Operating Unit" + ) diff --git a/purchase_operating_unit/readme/CONTRIBUTORS.rst b/purchase_operating_unit/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..9f02e3f0b7 --- /dev/null +++ b/purchase_operating_unit/readme/CONTRIBUTORS.rst @@ -0,0 +1,7 @@ +* Jordi Ballester Alomar +* Aaron Henriquez +* Sudhir Arya +* Nicola Studer +* Nikul Chaudhary +* Kitti U. +* Alan Ramos diff --git a/purchase_operating_unit/readme/DESCRIPTION.rst b/purchase_operating_unit/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..10d7911fa2 --- /dev/null +++ b/purchase_operating_unit/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module introduces the following features: + +- It introduces the operating unit to the purchase order. +- The operating unit is copied to the invoice. +- It implements user's security rules. diff --git a/purchase_operating_unit/readme/USAGE.rst b/purchase_operating_unit/readme/USAGE.rst new file mode 100644 index 0000000000..fb2deacb52 --- /dev/null +++ b/purchase_operating_unit/readme/USAGE.rst @@ -0,0 +1,5 @@ +#. Create a PO: the Default Operating Unit is assigned to the PO. If you want, + you can change to another Operating Unit. +#. Validate the PO: the Operating Unit is propagated to the Invoices. +#. From the invoice, it is not possible to change the Operating Unit, it has to + be the same as the one of the PO. diff --git a/purchase_operating_unit/security/purchase_security.xml b/purchase_operating_unit/security/purchase_security.xml new file mode 100644 index 0000000000..970d195ac3 --- /dev/null +++ b/purchase_operating_unit/security/purchase_security.xml @@ -0,0 +1,31 @@ + + + + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + PO's from allowed operating units + + + + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + PO lines from allowed operating units + + + + + + + diff --git a/purchase_operating_unit/static/description/icon.png b/purchase_operating_unit/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/purchase_operating_unit/static/description/icon.png differ diff --git a/purchase_operating_unit/static/description/index.html b/purchase_operating_unit/static/description/index.html new file mode 100644 index 0000000000..a62e19ecc6 --- /dev/null +++ b/purchase_operating_unit/static/description/index.html @@ -0,0 +1,450 @@ + + + + + + +Operating Unit in Purchase Orders + + + +
+

Operating Unit in Purchase Orders

+ + +

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

+

This module introduces the following features:

+
    +
  • It introduces the operating unit to the purchase order.
  • +
  • The operating unit is copied to the invoice.
  • +
  • The operating unit is copied to the stock picking.
  • +
  • It implements user’s security rules.
  • +
+

Table of contents

+ +
+

Usage

+
    +
  1. Create a PO: the Default Operating Unit is assigned to the PO. If you want, +you can change to another Operating Unit.
  2. +
  3. Validate the PO: the Operating Unit is propagated to the Pickings and +Invoices.
  4. +
  5. From the invoice, it is not possible to change the Operating Unit, it has to +be the same as the one of the PO.
  6. +
+
+
+

Known issues / Roadmap

+

Procurement Orders were removed. Procurement Operating Unit module is +deprecated. Stock Operating Unit ensures consistency between the operating unit +from different models.

+
+
+

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

+
    +
  • ForgeFlow
  • +
  • Serpent Consulting Services Pvt. Ltd.
  • +
+
+
+

Contributors

+ +
+
+

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/operating-unit project on GitHub.

+

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

+
+
+
+ + diff --git a/purchase_operating_unit/tests/__init__.py b/purchase_operating_unit/tests/__init__.py new file mode 100644 index 0000000000..9c86acd517 --- /dev/null +++ b/purchase_operating_unit/tests/__init__.py @@ -0,0 +1,3 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import test_purchase_operating_unit +from . import test_po_security diff --git a/purchase_operating_unit/tests/test_po_security.py b/purchase_operating_unit/tests/test_po_security.py new file mode 100644 index 0000000000..ae0a82f5e8 --- /dev/null +++ b/purchase_operating_unit/tests/test_po_security.py @@ -0,0 +1,40 @@ +# Copyright 2015-17 ForgeFlow S.L. +# - Jordi Ballester Alomar +# Copyright 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import test_purchase_operating_unit as test_po_ou # noqa + + +class TestPoSecurity(test_po_ou.TestPurchaseOperatingUnit): + def test_po_ou_security(self): + """Test Security of Purchase Operating Unit""" + # User 2 is only assigned to Operating Unit 2, and cannot list + # purchase orders from Operating Unit 1. + po_ids = ( + self.PurchaseOrder.with_user(self.user2_id) + .search([("operating_unit_id", "=", self.ou1.id)]) + .ids + ) + self.assertEqual(po_ids, []) + # User 2 cannot list the invoice that was created from PO 1 + invoice_ids = ( + self.AccountInvoice.with_user(self.user2_id) + .search([("id", "=", self.invoice.id)]) + .ids + ) + self.assertEqual(invoice_ids, []) + # User 1 is assigned to Operating Unit 1, and can list + # the purchase order 1 from Operating Unit 1. + po_ids = ( + self.PurchaseOrder.with_user(self.user1_id) + .search([("operating_unit_id", "=", self.ou1.id)]) + .ids + ) + self.assertNotEqual(po_ids, []) + # User 1 can list the invoice that was created from PO 2 + invoice_ids = ( + self.AccountInvoice.with_user(self.user1_id) + .search([("id", "=", self.invoice.id)]) + .ids + ) + self.assertNotEqual(invoice_ids, []) diff --git a/purchase_operating_unit/tests/test_purchase_operating_unit.py b/purchase_operating_unit/tests/test_purchase_operating_unit.py new file mode 100644 index 0000000000..94b4031f6e --- /dev/null +++ b/purchase_operating_unit/tests/test_purchase_operating_unit.py @@ -0,0 +1,117 @@ +# Copyright 2015-17 ForgeFlow S.L. +# - Jordi Ballester Alomar +# Copyright 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +import time + +from odoo.tests import common +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT + + +class TestPurchaseOperatingUnit(common.TransactionCase): + def setUp(self): + super(TestPurchaseOperatingUnit, self).setUp() + self.ResUsers = self.env["res.users"] + self.PurchaseOrder = self.env["purchase.order"] + self.AccountInvoice = self.env["account.move"] + self.AccountAccount = self.env["account.account"] + # company + self.company = self.env.ref("base.main_company") + # groups + self.group_purchase_user = self.env.ref("purchase.group_purchase_user") + # Main Operating Unit + self.ou1 = self.env.ref("operating_unit.main_operating_unit") + # B2B Operating Unit + self.b2b = self.env.ref("operating_unit.b2b_operating_unit") + # Partner + self.partner1 = self.env.ref("base.res_partner_1") + # Products + self.product1 = self.env.ref("product.product_product_7") + self.product2 = self.env.ref("product.product_product_9") + self.product3 = self.env.ref("product.product_product_11") + # Account + payable_acc_type = self.env.ref("account.data_account_type_payable").id + self.account = self.AccountAccount.search( + [("user_type_id", "=", payable_acc_type)], limit=1 + ) + # Create users + self.user1_id = self._create_user( + "user_1", + [self.group_purchase_user], + self.company, + [self.ou1], + ) + self.user2_id = self._create_user( + "user_2", + [self.group_purchase_user], + self.company, + [self.b2b], + ) + self.purchase1 = self._create_purchase( + self.user1_id, + [(self.product1, 1000), (self.product2, 500), (self.product3, 800)], + ) + self.purchase1.with_user(self.user1_id).button_confirm() + self.invoice = self._create_invoice(self.purchase1, self.partner1, self.account) + + def _create_user(self, login, groups, company, operating_units): + """Create a user.""" + group_ids = [group.id for group in groups] + user = self.ResUsers.with_context(**{"no_reset_password": True}).create( + { + "name": "Chicago Purchase User", + "login": login, + "password": "demo", + "email": "chicago@yourcompany.com", + "company_id": company.id, + "company_ids": [(4, company.id)], + "operating_unit_ids": [(4, ou.id) for ou in operating_units], + "groups_id": [(6, 0, group_ids)], + } + ) + return user.id + + def _create_purchase(self, user_id, line_products): + """Create a purchase order. + ``line_products`` is a list of tuple [(product, qty)] + """ + lines = [] + for product, qty in line_products: + line_values = { + "name": product.name, + "product_id": product.id, + "product_qty": qty, + "product_uom": product.uom_id.id, + "price_unit": 50, + "date_planned": time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + } + lines.append((0, 0, line_values)) + purchase = self.PurchaseOrder.with_user(user_id).create( + { + "operating_unit_id": self.ou1.id, + "requesting_operating_unit_id": self.ou1.id, + "partner_id": self.partner1.id, + "order_line": lines, + "company_id": self.company.id, + } + ) + return purchase + + def _create_invoice(self, purchase, partner, account): + """Create a vendor invoice for the purchase order.""" + invoice_vals = { + "purchase_id": purchase.id, + "partner_id": partner.id, + "move_type": "in_invoice", + } + purchase_context = { + "active_id": purchase.id, + "active_ids": purchase.ids, + "active_model": "purchase.order", + } + res = ( + self.env["account.move"] + .with_context(**purchase_context) + .create(invoice_vals) + ) + return res diff --git a/purchase_operating_unit/views/purchase_order_line_view.xml b/purchase_operating_unit/views/purchase_order_line_view.xml new file mode 100644 index 0000000000..b4661f9d2e --- /dev/null +++ b/purchase_operating_unit/views/purchase_order_line_view.xml @@ -0,0 +1,53 @@ + + + + purchase_order_line_tree + purchase.order.line + + + + + + + + + purchase_order_line_form + purchase.order.line + + + + + + + + + purchase_order_line_search + purchase.order.line + + + + + + + + + + + diff --git a/purchase_operating_unit/views/purchase_order_view.xml b/purchase_operating_unit/views/purchase_order_view.xml new file mode 100644 index 0000000000..7c2613be05 --- /dev/null +++ b/purchase_operating_unit/views/purchase_order_view.xml @@ -0,0 +1,62 @@ + + + + purchase_order_tree + purchase.order + + + + + + + + + purchase_order_form + purchase.order + + + + + + + + {'default_state': 'draft', 'operating_unit_id': operating_unit_id} + + + + + view_purchase_order_filter + purchase.order + + + + + + + + + + + diff --git a/purchase_request_operating_unit/README.rst b/purchase_request_operating_unit/README.rst new file mode 100644 index 0000000000..4af8f6e27f --- /dev/null +++ b/purchase_request_operating_unit/README.rst @@ -0,0 +1,95 @@ +=================================== +Operating Unit in Purchase Requests +=================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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%2Foperating--unit-lightgray.png?logo=github + :target: https://github.com/OCA/operating-unit/tree/14.0/purchase_request_operating_unit + :alt: OCA/operating-unit +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/operating-unit-14-0/operating-unit-14-0-purchase_request_operating_unit + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/213/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module introduces the following features: + +* Adds the Operating Unit (OU) to the Purchase Request. + +* The user’s default Operating Unit (OU) is proposed at the time of creating the Purchase Request. + +* Security rules are defined to ensure that users can only see the Purchase Request of that Operating Units in which they are allowed access to. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Follow these steps: + +#. Create or use an existing Operating Unit (OU) in Settings -> + Users & Configuration -> Operating Unit. +#. Change the configuration of the Users. In Application Accesses, put an User + as Request manager and another one as Request User. +#. Create a Purchase Request Order. By default, if there is no Approver, any + Manager with the same OU can accept the PO. If you specify an Approver, he + must be in the same Operating Unit as the Purchase Request. + +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 +~~~~~~~ + +* Eficent +* SerpentCS + +Contributors +~~~~~~~~~~~~ + +* Jordi Ballester Alomar +* Serpent Consulting Services Pvt. Ltd. +* Saran Lim. + +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/operating-unit `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/purchase_request_operating_unit/__init__.py b/purchase_request_operating_unit/__init__.py new file mode 100644 index 0000000000..6081d63bec --- /dev/null +++ b/purchase_request_operating_unit/__init__.py @@ -0,0 +1,4 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import model +from . import wizard diff --git a/purchase_request_operating_unit/__manifest__.py b/purchase_request_operating_unit/__manifest__.py new file mode 100644 index 0000000000..660871832a --- /dev/null +++ b/purchase_request_operating_unit/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2016-19 Eficent Business and IT Consulting Services S.L. +# (http://www.eficent.com) +# Copyright 2016-19 Serpent Consulting Services Pvt. Ltd. +# () +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +{ + "name": "Operating Unit in Purchase Requests", + "version": "15.0.1.0.0", + "author": "Eficent, SerpentCS, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/operating-unit", + "license": "LGPL-3", + "category": "Purchase Management", + "depends": ["purchase_request", "purchase_operating_unit"], + "data": [ + "security/purchase_security.xml", + "view/purchase_request_view.xml", + "wizard/purchase_request_line_make_purchase_order_view.xml", + ], + "installable": True, +} diff --git a/purchase_request_operating_unit/i18n/fr.po b/purchase_request_operating_unit/i18n/fr.po new file mode 100644 index 0000000000..43d7af01ea --- /dev/null +++ b/purchase_request_operating_unit/i18n/fr.po @@ -0,0 +1,100 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_request_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-06-28 11:05+0000\n" +"Last-Translator: Khalid Hazam \n" +"Language-Team: none\n" +"Language: fr\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: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/model/purchase_request.py:0 +#, python-format +msgid "" +"Configuration error. The Purchase Request and theWarehouse of picking type " +"must belong to the same Operating Unit." +msgstr "" +"Erreur de configuration. La demande d'achat et l'entrepôt de prélèvement " +"doivent appartenir à la même unité opérationnelle." + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/model/purchase_request.py:0 +#, fuzzy, python-format +msgid "Configuration error. The approver has not the indicated Operating Unit" +msgstr "" +"Erreur de configuration. L’approbateur n'a pas l'unité opérationnelle " +"indiquée" + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/wizard/purchase_request_line_make_purchase_order.py:0 +#, fuzzy, python-format +msgid "" +"Could not process !You have to select linesfrom the same operating unit." +msgstr "" +"Impossible de traiter ! Vous devez sélectionner les lignes de la même unité " +"opérationnelle." + +#. module: purchase_request_operating_unit +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request__display_name +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line__display_name +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line_make_purchase_order__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: purchase_request_operating_unit +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request__id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line__id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line_make_purchase_order__id +msgid "ID" +msgstr "ID" + +#. module: purchase_request_operating_unit +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request____last_update +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line____last_update +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line_make_purchase_order____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: purchase_request_operating_unit +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request__operating_unit_id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line__operating_unit_id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line_make_purchase_order__operating_unit_id +#: model_terms:ir.ui.view,arch_db:purchase_request_operating_unit.purchase_request_line_search +#: model_terms:ir.ui.view,arch_db:purchase_request_operating_unit.view_purchase_request_search +msgid "Operating Unit" +msgstr "Unité opérationnelle" + +#. module: purchase_request_operating_unit +#: model:ir.model,name:purchase_request_operating_unit.model_purchase_request +msgid "Purchase Request" +msgstr "Demande d'achat" + +#. module: purchase_request_operating_unit +#: model:ir.model,name:purchase_request_operating_unit.model_purchase_request_line +msgid "Purchase Request Line" +msgstr "Ligne de la demande d'achat" + +#. module: purchase_request_operating_unit +#: model:ir.model,name:purchase_request_operating_unit.model_purchase_request_line_make_purchase_order +#, fuzzy +msgid "Purchase Request Line Make Purchase Order" +msgstr "Ligne de demande d'achat fait Bon de commande" + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/model/purchase_request.py:0 +#, python-format +msgid "" +"The Company in the Purchase Request and in the Operating Unit must bethe " +"same." +msgstr "" +"La société sur la demande d'achat et sur l'unité opérationnelle doit être la " +"même." diff --git a/purchase_request_operating_unit/i18n/pt.po b/purchase_request_operating_unit/i18n/pt.po new file mode 100644 index 0000000000..73c7ff0b36 --- /dev/null +++ b/purchase_request_operating_unit/i18n/pt.po @@ -0,0 +1,65 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_request_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-02-29 15:13+0000\n" +"Last-Translator: Pedro Castro Silva \n" +"Language-Team: none\n" +"Language: pt\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 3.10\n" + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/model/purchase_request.py:46 +#, python-format +msgid "Configuration error. The Purchase Request and the Warehouse of picking type must belong to the same Operating Unit." +msgstr "" + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/model/purchase_request.py:56 +#, python-format +msgid "Configuration error. The approver has not the indicated Operating Unit" +msgstr "" + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/wizard/purchase_request_line_make_purchase_order.py:32 +#, python-format +msgid "Could not process !You have to select linesfrom the same operating unit." +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request__operating_unit_id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line__operating_unit_id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line_make_purchase_order__operating_unit_id +#: model_terms:ir.ui.view,arch_db:purchase_request_operating_unit.purchase_request_line_search +#: model_terms:ir.ui.view,arch_db:purchase_request_operating_unit.view_purchase_request_search +msgid "Operating Unit" +msgstr "Unidade Operacional" + +#. module: purchase_request_operating_unit +#: model:ir.model,name:purchase_request_operating_unit.model_purchase_request +msgid "Purchase Request" +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model,name:purchase_request_operating_unit.model_purchase_request_line +msgid "Purchase Request Line" +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model,name:purchase_request_operating_unit.model_purchase_request_line_make_purchase_order +msgid "Purchase Request Line Make Purchase Order" +msgstr "" + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/model/purchase_request.py:31 +#, python-format +msgid "The Company in the Purchase Request and in the Operating Unit must bethe same." +msgstr "" diff --git a/purchase_request_operating_unit/i18n/purchase_request_operating_unit.pot b/purchase_request_operating_unit/i18n/purchase_request_operating_unit.pot new file mode 100644 index 0000000000..46a68c2c2f --- /dev/null +++ b/purchase_request_operating_unit/i18n/purchase_request_operating_unit.pot @@ -0,0 +1,88 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_request_operating_unit +# +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: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/model/purchase_request.py:0 +#, python-format +msgid "" +"Configuration error. The Purchase Request and theWarehouse of picking type " +"must belong to the same Operating Unit." +msgstr "" + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/model/purchase_request.py:0 +#, python-format +msgid "Configuration error. The approver has not the indicated Operating Unit" +msgstr "" + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/wizard/purchase_request_line_make_purchase_order.py:0 +#, python-format +msgid "" +"Could not process !You have to select linesfrom the same operating unit." +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request__display_name +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line__display_name +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line_make_purchase_order__display_name +msgid "Display Name" +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request__id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line__id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line_make_purchase_order__id +msgid "ID" +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request____last_update +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line____last_update +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line_make_purchase_order____last_update +msgid "Last Modified on" +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request__operating_unit_id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line__operating_unit_id +#: model:ir.model.fields,field_description:purchase_request_operating_unit.field_purchase_request_line_make_purchase_order__operating_unit_id +#: model_terms:ir.ui.view,arch_db:purchase_request_operating_unit.purchase_request_line_search +#: model_terms:ir.ui.view,arch_db:purchase_request_operating_unit.view_purchase_request_search +msgid "Operating Unit" +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model,name:purchase_request_operating_unit.model_purchase_request +msgid "Purchase Request" +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model,name:purchase_request_operating_unit.model_purchase_request_line +msgid "Purchase Request Line" +msgstr "" + +#. module: purchase_request_operating_unit +#: model:ir.model,name:purchase_request_operating_unit.model_purchase_request_line_make_purchase_order +msgid "Purchase Request Line Make Purchase Order" +msgstr "" + +#. module: purchase_request_operating_unit +#: code:addons/purchase_request_operating_unit/model/purchase_request.py:0 +#, python-format +msgid "" +"The Company in the Purchase Request and in the Operating Unit must bethe " +"same." +msgstr "" diff --git a/purchase_request_operating_unit/model/__init__.py b/purchase_request_operating_unit/model/__init__.py new file mode 100644 index 0000000000..639be8329b --- /dev/null +++ b/purchase_request_operating_unit/model/__init__.py @@ -0,0 +1,3 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import purchase_request diff --git a/purchase_request_operating_unit/model/purchase_request.py b/purchase_request_operating_unit/model/purchase_request.py new file mode 100644 index 0000000000..980b35e1bf --- /dev/null +++ b/purchase_request_operating_unit/model/purchase_request.py @@ -0,0 +1,87 @@ +# Copyright 2016-19 Eficent Business and IT Consulting Services S.L. +# (http://www.eficent.com) +# Copyright 2016-19 Serpent Consulting Services Pvt. Ltd. +# () +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PurchaseRequest(models.Model): + _inherit = "purchase.request" + + operating_unit_id = fields.Many2one( + "operating.unit", + string="Operating Unit", + states={ + "to_approve": [("readonly", True)], + "approved": [("readonly", True)], + "done": [("readonly", True)], + }, + default=lambda self: self.env["res.users"].operating_unit_default_get( + self._uid + ), + ) + + @api.constrains("operating_unit_id", "company_id") + def _check_company_operating_unit(self): + for rec in self: + if ( + rec.company_id + and rec.operating_unit_id + and rec.company_id != rec.operating_unit_id.company_id + ): + raise ValidationError( + _( + "The Company in the Purchase Request " + "and in the Operating Unit must be" + "the same." + ) + ) + + @api.constrains("operating_unit_id", "picking_type_id") + def _check_warehouse_operating_unit(self): + for rec in self: + picking_type = rec.picking_type_id + if picking_type: + if ( + picking_type.warehouse_id + and picking_type.warehouse_id.operating_unit_id + and rec.operating_unit_id + and picking_type.warehouse_id.operating_unit_id + != rec.operating_unit_id + ): + raise ValidationError( + _( + "Configuration error. The Purchase Request and the" + "Warehouse of picking type must belong to the same " + "Operating Unit." + ) + ) + + @api.constrains("operating_unit_id") + def _check_approver_operating_unit(self): + for rec in self: + if ( + rec.assigned_to + and rec.operating_unit_id + and rec.operating_unit_id not in rec.assigned_to.operating_unit_ids + ): + raise ValidationError( + _( + "Configuration error. The approver has not " + "the indicated Operating Unit" + ) + ) + + +class PurchaseRequestLine(models.Model): + _inherit = "purchase.request.line" + + operating_unit_id = fields.Many2one( + "operating.unit", + related="request_id.operating_unit_id", + string="Operating Unit", + store=True, + ) diff --git a/purchase_request_operating_unit/readme/CONTRIBUTORS.rst b/purchase_request_operating_unit/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..6251500b42 --- /dev/null +++ b/purchase_request_operating_unit/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Jordi Ballester Alomar +* Serpent Consulting Services Pvt. Ltd. +* Saran Lim. diff --git a/purchase_request_operating_unit/readme/DESCRIPTION.rst b/purchase_request_operating_unit/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..b24fb80c0a --- /dev/null +++ b/purchase_request_operating_unit/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +This module introduces the following features: + +* Adds the Operating Unit (OU) to the Purchase Request. + +* The user’s default Operating Unit (OU) is proposed at the time of creating the Purchase Request. + +* Security rules are defined to ensure that users can only see the Purchase Request of that Operating Units in which they are allowed access to. diff --git a/purchase_request_operating_unit/readme/USAGE.rst b/purchase_request_operating_unit/readme/USAGE.rst new file mode 100644 index 0000000000..48e96938bc --- /dev/null +++ b/purchase_request_operating_unit/readme/USAGE.rst @@ -0,0 +1,9 @@ +Follow these steps: + +#. Create or use an existing Operating Unit (OU) in Settings -> + Users & Configuration -> Operating Unit. +#. Change the configuration of the Users. In Application Accesses, put an User + as Request manager and another one as Request User. +#. Create a Purchase Request Order. By default, if there is no Approver, any + Manager with the same OU can accept the PO. If you specify an Approver, he + must be in the same Operating Unit as the Purchase Request. diff --git a/purchase_request_operating_unit/security/purchase_security.xml b/purchase_request_operating_unit/security/purchase_security.xml new file mode 100644 index 0000000000..bbc710f442 --- /dev/null +++ b/purchase_request_operating_unit/security/purchase_security.xml @@ -0,0 +1,30 @@ + + + + + + + + + + ['|',('operating_unit_id','=',False), + ('operating_unit_id','in',[g.id for g in user.operating_unit_ids])] + Purchase Requests from allowed operating units + + + + + + + + + ['|',('request_id.operating_unit_id','=',False), + ('request_id.operating_unit_id','in',[g.id for g in user.operating_unit_ids])] + Purchase Requests lines from allowed operating units + + + + + + + diff --git a/purchase_request_operating_unit/static/description/icon.png b/purchase_request_operating_unit/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/purchase_request_operating_unit/static/description/icon.png differ diff --git a/purchase_request_operating_unit/static/description/index.html b/purchase_request_operating_unit/static/description/index.html new file mode 100644 index 0000000000..1284ade76e --- /dev/null +++ b/purchase_request_operating_unit/static/description/index.html @@ -0,0 +1,441 @@ + + + + + + +Operating Unit in Purchase Requests + + + +
+

Operating Unit in Purchase Requests

+ + +

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

+

This module introduces the following features:

+
    +
  • Adds the Operating Unit (OU) to the Purchase Request.
  • +
  • The user’s default Operating Unit (OU) is proposed at the time of creating the Purchase Request.
  • +
  • Security rules are defined to ensure that users can only see the Purchase Request of that Operating Units in which they are allowed access to.
  • +
+

Table of contents

+ +
+

Usage

+

Follow these steps:

+
    +
  1. Create or use an existing Operating Unit (OU) in Settings -> +Users & Configuration -> Operating Unit.
  2. +
  3. Change the configuration of the Users. In Application Accesses, put an User +as Request manager and another one as Request User.
  4. +
  5. Create a Purchase Request Order. By default, if there is no Approver, any +Manager with the same OU can accept the PO. If you specify an Approver, he +must be in the same Operating Unit as the Purchase Request.
  6. +
+
+
+

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

+
    +
  • Eficent
  • +
  • SerpentCS
  • +
+
+
+

Contributors

+ +
+
+

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/operating-unit project on GitHub.

+

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

+
+
+
+ + diff --git a/purchase_request_operating_unit/test-requirements.txt b/purchase_request_operating_unit/test-requirements.txt new file mode 100644 index 0000000000..60c9138394 --- /dev/null +++ b/purchase_request_operating_unit/test-requirements.txt @@ -0,0 +1 @@ +odoo-addon-purchase_operating_unit @ git+https://github.com/OCA/operating-unit@refs/pull/443/head#subdirectory=setup/purchase_operating_unit diff --git a/purchase_request_operating_unit/tests/__init__.py b/purchase_request_operating_unit/tests/__init__.py new file mode 100644 index 0000000000..5c730c69af --- /dev/null +++ b/purchase_request_operating_unit/tests/__init__.py @@ -0,0 +1,4 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import test_purchase_request_operating_unit +from . import test_purchase_request_to_rfq_operating_unit diff --git a/purchase_request_operating_unit/tests/test_purchase_request_operating_unit.py b/purchase_request_operating_unit/tests/test_purchase_request_operating_unit.py new file mode 100644 index 0000000000..d5ae7adc63 --- /dev/null +++ b/purchase_request_operating_unit/tests/test_purchase_request_operating_unit.py @@ -0,0 +1,100 @@ +# Copyright 2016-19 Eficent Business and IT Consulting Services S.L. +# (http://www.eficent.com) +# Copyright 2016-19 Serpent Consulting Services Pvt. Ltd. +# () +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo.tests import common + + +class TestPurchaseRequestOperatingUnit(common.TransactionCase): + def setUp(self): + super().setUp() + # Models + self.res_users_model = self.env["res.users"] + self.purchase_request = self.env["purchase.request"] + self.purchase_request_line = self.env["purchase.request.line"] + # Company + self.company = self.env.ref("base.main_company") + # Main Operating Unit + self.ou1 = self.env.ref("operating_unit.main_operating_unit") + # B2C Operating Unit + self.b2c = self.env.ref("operating_unit.b2c_operating_unit") + # Product + self.product1 = self.env.ref("product.product_product_7") + self.product_uom = self.env.ref("uom.product_uom_unit") + # User + self.user_root = self.env.ref("base.user_root") + # Groups + self.grp_pr_mngr = self.env.ref( + "purchase_request.group_purchase_request_manager" + ) + # Picking Type + b2c_wh = self.env.ref("stock_operating_unit.stock_warehouse_b2c") + self.b2c_type_in_id = b2c_wh.in_type_id.id + self.picking_type = self.env.ref("stock.picking_type_in") + + # Creates Users and Purchase request + self.user1 = self._create_user("user_1", [], self.company, [self.ou1]) + self.user2 = self._create_user( + "user_2", self.grp_pr_mngr, self.company, [self.b2c] + ) + self.request1 = self._create_purchase_request(self.ou1) + self._purchase_line(self.request1) + self.request2 = self._create_purchase_request(self.b2c, self.b2c_type_in_id) + self._purchase_line(self.request2) + + def _create_user(self, login, groups, company, operating_units, context=None): + """Create a user.""" + group_ids = [group.id for group in groups] + user = self.res_users_model.create( + { + "name": "Test Purchase Request User", + "login": login, + "password": "demo", + "email": "example@yourcompany.com", + "company_id": company.id, + "company_ids": [(4, company.id)], + "operating_unit_ids": [(4, ou.id) for ou in operating_units], + "groups_id": [(6, 0, group_ids)], + } + ) + return user + + def _purchase_line(self, request): + line = self.purchase_request_line.create( + { + "request_id": request.id, + "product_id": self.product1.id, + "product_uom_id": self.product_uom.id, + "product_qty": 5.0, + } + ) + return line + + def _create_purchase_request(self, operating_unit, picking_type=False): + if picking_type: + purchase_request = self.purchase_request.create( + { + "assigned_to": self.user_root.id, + "picking_type_id": self.b2c_type_in_id, + "operating_unit_id": operating_unit.id, + } + ) + else: + purchase_request = self.purchase_request.create( + { + "assigned_to": self.user_root.id, + "picking_type_id": self.picking_type.id, + "operating_unit_id": operating_unit.id, + } + ) + return purchase_request + + def test_purchase_request(self): + record = self.purchase_request.with_user(self.user2.id).search( + [("id", "=", self.request1.id), ("operating_unit_id", "=", self.ou1.id)] + ) + self.assertEqual( + record.ids, [], "User 2 should not have access to OU %s" % self.ou1.name + ) diff --git a/purchase_request_operating_unit/tests/test_purchase_request_to_rfq_operating_unit.py b/purchase_request_operating_unit/tests/test_purchase_request_to_rfq_operating_unit.py new file mode 100644 index 0000000000..bc4858c8cc --- /dev/null +++ b/purchase_request_operating_unit/tests/test_purchase_request_to_rfq_operating_unit.py @@ -0,0 +1,54 @@ +# Copyright 2016-19 Eficent Business and IT Consulting Services S.L. +# (http://www.eficent.com) +# Copyright 2016-19 Serpent Consulting Services Pvt. Ltd. +# () +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import SUPERUSER_ID +from odoo.tests import common + + +class TestPurchaseRequestToRfq(common.TransactionCase): + def setUp(self): + super().setUp() + self.purchase_request = self.env["purchase.request"] + self.purchase_request_line = self.env["purchase.request.line"] + self.wiz = self.env["purchase.request.line.make.purchase.order"] + self.purchase_order = self.env["purchase.order"] + # Main Operating Unit + self.ou1 = self.env.ref("operating_unit.main_operating_unit") + # Products + self.product1 = self.env.ref("product.product_product_9") + self._create_purchase_request() + + def _create_purchase_request(self): + vals = { + "picking_type_id": self.env.ref("stock.picking_type_in").id, + "requested_by": SUPERUSER_ID, + "operating_unit_id": self.ou1.id, + } + purchase_request = self.purchase_request.create(vals) + vals = { + "request_id": purchase_request.id, + "product_id": self.product1.id, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + "product_qty": 5.0, + } + self.purchase_request_line = self.purchase_request_line.create(vals) + purchase_request.button_to_approve() + purchase_request.button_approved() + + def test_purchase_request_to_purchase_requisition(self): + vals = {"supplier_id": self.env.ref("base.res_partner_12").id} + wiz_id = self.wiz.with_context( + active_model="purchase.request.line", + active_ids=[self.purchase_request_line.id], + active_id=self.purchase_request_line.id, + ).create(vals) + wiz_id.make_purchase_order() + purchase_id = self.purchase_request_line.purchase_lines.order_id + self.assertEqual( + purchase_id.operating_unit_id, + self.purchase_request_line.operating_unit_id, + "Should have the same Operating Unit", + ) diff --git a/purchase_request_operating_unit/view/purchase_request_view.xml b/purchase_request_operating_unit/view/purchase_request_view.xml new file mode 100644 index 0000000000..d3c76ab8c8 --- /dev/null +++ b/purchase_request_operating_unit/view/purchase_request_view.xml @@ -0,0 +1,110 @@ + + + + + + + + + purchase.request.tree + purchase.request + + + + + + + + + purchase.request.form + purchase.request + + + + + + + + {'operating_unit_id': operating_unit_id} + + + + + + purchase.request.list.select + purchase.request + + + + + + + + + + + + purchase.request.line.tree + purchase.request.line + + + + + + + + + purchase.request.line.form + purchase.request.line + + + + + + + + + purchase.request.line.search + purchase.request.line + + + + + + + + + + + diff --git a/purchase_request_operating_unit/wizard/__init__.py b/purchase_request_operating_unit/wizard/__init__.py new file mode 100644 index 0000000000..5869ceb5a3 --- /dev/null +++ b/purchase_request_operating_unit/wizard/__init__.py @@ -0,0 +1,3 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import purchase_request_line_make_purchase_order diff --git a/purchase_request_operating_unit/wizard/purchase_request_line_make_purchase_order.py b/purchase_request_operating_unit/wizard/purchase_request_line_make_purchase_order.py new file mode 100644 index 0000000000..d9306d6282 --- /dev/null +++ b/purchase_request_operating_unit/wizard/purchase_request_line_make_purchase_order.py @@ -0,0 +1,62 @@ +# Copyright 2016-19 Eficent Business and IT Consulting Services S.L. +# (http://www.eficent.com) +# Copyright 2016-19 Serpent Consulting Services Pvt. Ltd. +# () +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PurchaseRequestLineMakePurchaseOrder(models.TransientModel): + _inherit = "purchase.request.line.make.purchase.order" + + operating_unit_id = fields.Many2one( + "operating.unit", + string="Operating Unit", + readonly=True, + ) + + @api.model + def default_get(self, fields): + res = super().default_get(fields) + operating_unit_id = False + active_model = self.env.context.get("active_model", False) + active_ids = self.env.context.get("active_ids", False) + _model = { + "purchase.request.line": "", + "purchase.request": "line_ids", + } + request_lines = ( + self.env[active_model].browse(active_ids).mapped(_model[active_model]) + ) + for line in request_lines: + line_operating_unit_id = ( + line.request_id.operating_unit_id + and line.request_id.operating_unit_id.id + or False + ) + if operating_unit_id and line_operating_unit_id != operating_unit_id: + raise ValidationError( + _( + "Could not process !" + "You have to select lines" + "from the same operating unit." + ) + ) + else: + operating_unit_id = line_operating_unit_id + res["operating_unit_id"] = operating_unit_id + return res + + @api.model + def _prepare_purchase_order( + self, picking_type, group_id, company, currency, origin + ): + data = super()._prepare_purchase_order( + picking_type, group_id, company, currency, origin + ) + if self.operating_unit_id: + data["requesting_operating_unit_id"] = self.operating_unit_id.id + data["operating_unit_id"] = self.operating_unit_id.id + return data diff --git a/purchase_request_operating_unit/wizard/purchase_request_line_make_purchase_order_view.xml b/purchase_request_operating_unit/wizard/purchase_request_line_make_purchase_order_view.xml new file mode 100644 index 0000000000..bc584b8a6e --- /dev/null +++ b/purchase_request_operating_unit/wizard/purchase_request_line_make_purchase_order_view.xml @@ -0,0 +1,30 @@ + + + + + + + + + Purchase Request Line Make Purchase Order + purchase.request.line.make.purchase.order + + form + + + + + + ['|', ('operating_unit_id', '=', False), ('operating_unit_id', '=', operating_unit_id)] + + + + diff --git a/sale_operating_unit/README.rst b/sale_operating_unit/README.rst new file mode 100644 index 0000000000..08c90ed0cc --- /dev/null +++ b/sale_operating_unit/README.rst @@ -0,0 +1,88 @@ +======================= +Operating Unit in Sales +======================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| 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 +.. |badge2| image:: https://img.shields.io/badge/github-minditp-lightgray.png?logo=github + :target: https://github.com/brain-tec/minditp/tree/15.0-mig-sale_operating_unit/sale_operating_unit + :alt: brain-tec/minditp + +|badge1| |badge2| + +This module extends the Sales capabilities of Odoo and introduces the operating +unit to the Sales Order. Security rules are defined to ensure that users can +only display the Sales Orders in which they are allowed access to. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Follow these steps: + +#. Create a Sale Order. +#. The Operating Unit of the Sale Team is assigned to the Sale Order. + +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 +~~~~~~~ + +* ForgeFlow +* Serpent Consulting Services Pvt. Ltd. + +Contributors +~~~~~~~~~~~~ + +* Jordi Ballester Alomar +* Aaron Henriquez +* Lois Rilo +* Miquel Raich +* Sudhir Arya +* Darshan Patel +* Alan Ramos +* Carlos Ramos - + +Maintainers +~~~~~~~~~~~ + +This module is maintained by bt-group. + +.. image:: https://raw.githubusercontent.com/brain-tec/static/master/img/bt_logo_readme.png + :alt: bt-group + :width: 150px + :target: https://www.braintec-group.com/en-us + +bt-group is a is an independent international software and consulting company considered as one of the +best Odoo partners worldwide, being already awarded several times as Best Odoo Partner in Europe. + +Since its founding in 2000, they have supported small to large companies with +the introduction and implementation of business software projects from their locations +in Switzerland, Germany, Austria and Spain. + +Their goal is the commitment to a more efficient daily business routine in SMEs. + +This module is part of the `brain-tec/minditp `_ project on GitHub. + +You are welcome to contribute. diff --git a/sale_operating_unit/__init__.py b/sale_operating_unit/__init__.py new file mode 100644 index 0000000000..3244c50a00 --- /dev/null +++ b/sale_operating_unit/__init__.py @@ -0,0 +1,5 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import models +from . import report +from . import wizard diff --git a/sale_operating_unit/__manifest__.py b/sale_operating_unit/__manifest__.py new file mode 100644 index 0000000000..1910769ce5 --- /dev/null +++ b/sale_operating_unit/__manifest__.py @@ -0,0 +1,23 @@ +# © 2019 ForgeFlow S.L. +# - Jordi Ballester Alomar +# © 2019 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +{ + "name": "Operating Unit in Sales", + "version": "15.0.1.0.1", + "summary": "An operating unit (OU) is an organizational entity part of a " + "company", + "author": "ForgeFlow, " + "Serpent Consulting Services Pvt. Ltd.," + "Odoo Community Association (OCA)", + "license": "LGPL-3", + "website": "https://github.com/OCA/operating-unit", + "category": "Sales Management", + "depends": ["sale", "account_operating_unit", "sales_team_operating_unit"], + "data": [ + "security/sale_security.xml", + "views/sale_view.xml", + "views/sale_report_view.xml", + ], + "installable": True, +} diff --git a/sale_operating_unit/i18n/es.po b/sale_operating_unit/i18n/es.po new file mode 100644 index 0000000000..1b2a11eabd --- /dev/null +++ b/sale_operating_unit/i18n/es.po @@ -0,0 +1,81 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-03-14 11:17+0000\n" +"Last-Translator: Javier Colmenero \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: sale_operating_unit +#: code:addons/sale_operating_unit/models/crm_team.py:0 +#, python-format +msgid "" +"Configuration error. It is not possible to change this team. There are sale " +"orders referencing it in other operating units" +msgstr "" +"Error de configuración. No es posible cambiar este quipo. Hay pedidos de " +"venta referenciandos en otras unidades operacionales" + +#. module: sale_operating_unit +#: code:addons/sale_operating_unit/models/sale_order.py:0 +#, python-format +msgid "" +"Configuration error. The Company in the Sales Order and in the Operating " +"Unit must be the same." +msgstr "" +"Error de configuración. La compañía en el pedido de venta y en la unidad " +"operacional debe ser la misma." + +#. module: sale_operating_unit +#: code:addons/sale_operating_unit/models/sale_order.py:0 +#, python-format +msgid "" +"Configuration error. The Operating Unit of the sales team must match with " +"that of the quote/sales order." +msgstr "" +"Error de configuración. La unidad operacional del equipo de ventas debe " +"coincidir con la del presupuesto/pedido de venta." + +#. module: sale_operating_unit +#: model:ir.model.fields,field_description:sale_operating_unit.field_sale_order__operating_unit_id +#: model:ir.model.fields,field_description:sale_operating_unit.field_sale_order_line__operating_unit_id +#: model:ir.model.fields,field_description:sale_operating_unit.field_sale_report__operating_unit_id +#: model_terms:ir.ui.view,arch_db:sale_operating_unit.view_sale_report_search +#: model_terms:ir.ui.view,arch_db:sale_operating_unit.view_sales_order_filter +msgid "Operating Unit" +msgstr "Unidad operacional" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_sale_advance_payment_inv +msgid "Sales Advance Payment Invoice" +msgstr "Factura de pago anticipado de ventas" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_sale_report +msgid "Sales Analysis Report" +msgstr "Informe análisis de ventas" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_sale_order +msgid "Sales Order" +msgstr "Pedidos de venta" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea pedido de venta" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_crm_team +msgid "Sales Team" +msgstr "Equipo de ventas" diff --git a/sale_operating_unit/i18n/sale_operating_unit.pot b/sale_operating_unit/i18n/sale_operating_unit.pot new file mode 100644 index 0000000000..7c29fe52f0 --- /dev/null +++ b/sale_operating_unit/i18n/sale_operating_unit.pot @@ -0,0 +1,72 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.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: sale_operating_unit +#: code:addons/sale_operating_unit/models/crm_team.py:0 +#, python-format +msgid "" +"Configuration error. It is not possible to change this team. There are sale " +"orders referencing it in other operating units" +msgstr "" + +#. module: sale_operating_unit +#: code:addons/sale_operating_unit/models/sale_order.py:0 +#, python-format +msgid "" +"Configuration error. The Company in the Sales Order and in the Operating " +"Unit must be the same." +msgstr "" + +#. module: sale_operating_unit +#: code:addons/sale_operating_unit/models/sale_order.py:0 +#, python-format +msgid "" +"Configuration error. The Operating Unit of the sales team must match with " +"that of the quote/sales order." +msgstr "" + +#. module: sale_operating_unit +#: model:ir.model.fields,field_description:sale_operating_unit.field_sale_order__operating_unit_id +#: model:ir.model.fields,field_description:sale_operating_unit.field_sale_order_line__operating_unit_id +#: model:ir.model.fields,field_description:sale_operating_unit.field_sale_report__operating_unit_id +#: model_terms:ir.ui.view,arch_db:sale_operating_unit.view_sale_report_search +#: model_terms:ir.ui.view,arch_db:sale_operating_unit.view_sales_order_filter +msgid "Operating Unit" +msgstr "" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_sale_advance_payment_inv +msgid "Sales Advance Payment Invoice" +msgstr "" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_sale_report +msgid "Sales Analysis Report" +msgstr "" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: sale_operating_unit +#: model:ir.model,name:sale_operating_unit.model_crm_team +msgid "Sales Team" +msgstr "" diff --git a/sale_operating_unit/models/__init__.py b/sale_operating_unit/models/__init__.py new file mode 100644 index 0000000000..afea3f6575 --- /dev/null +++ b/sale_operating_unit/models/__init__.py @@ -0,0 +1,4 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import crm_team +from . import sale_order diff --git a/sale_operating_unit/models/crm_team.py b/sale_operating_unit/models/crm_team.py new file mode 100644 index 0000000000..89803d15b1 --- /dev/null +++ b/sale_operating_unit/models/crm_team.py @@ -0,0 +1,34 @@ +# © 2019 ForgeFlow S.L. +# - Jordi Ballester Alomar +# © 2019 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import SUPERUSER_ID, _, api, models +from odoo.exceptions import ValidationError + + +class CrmTeam(models.Model): + _inherit = "crm.team" + + @api.constrains("operating_unit_id") + def _check_sales_order_operating_unit(self): + for rec in self: + orders = ( + self.with_user(SUPERUSER_ID) + .env["sale.order"] + .search( + [ + ("team_id", "=", rec.id), + ("operating_unit_id", "!=", rec.operating_unit_id.id), + ] + ) + ) + if orders: + raise ValidationError( + _( + "Configuration error. It is not " + "possible to change this " + "team. There are sale orders " + "referencing it in other operating " + "units" + ) + ) diff --git a/sale_operating_unit/models/sale_order.py b/sale_operating_unit/models/sale_order.py new file mode 100644 index 0000000000..f80e6f111f --- /dev/null +++ b/sale_operating_unit/models/sale_order.py @@ -0,0 +1,95 @@ +# © 2019 ForgeFlow S.L. +# - Jordi Ballester Alomar +# © 2019 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + @api.model + def _default_operating_unit(self): + team = self.env["crm.team"]._get_default_team_id() + if team.operating_unit_id: + return team.operating_unit_id + return self.env.user.default_operating_unit_id + + operating_unit_id = fields.Many2one( + comodel_name="operating.unit", + string="Operating Unit", + default=_default_operating_unit, + readonly=True, + states={"draft": [("readonly", False)], "sent": [("readonly", False)]}, + ) + + @api.onchange("team_id") + def onchange_team_id(self): + if self.team_id: + self.operating_unit_id = self.team_id.operating_unit_id + + @api.onchange("operating_unit_id") + def onchange_operating_unit_id(self): + if self.team_id and self.team_id.operating_unit_id != self.operating_unit_id: + self.team_id = False + + @api.constrains("team_id", "operating_unit_id") + def _check_team_operating_unit(self): + for rec in self: + if rec.team_id and rec.team_id.operating_unit_id != rec.operating_unit_id: + raise ValidationError( + _( + "Configuration error. The Operating " + "Unit of the sales team must match " + "with that of the quote/sales order." + ) + ) + + @api.constrains("operating_unit_id", "company_id") + def _check_company_operating_unit(self): + for rec in self: + if ( + rec.company_id + and rec.operating_unit_id + and rec.company_id != rec.operating_unit_id.company_id + ): + raise ValidationError( + _( + "Configuration error. The Company in " + "the Sales Order and in the Operating " + "Unit must be the same." + ) + ) + + def _prepare_invoice(self): + self.ensure_one() + invoice_vals = super(SaleOrder, self)._prepare_invoice() + journal = self.env["account.journal"].search( + [ + ("operating_unit_id", "=", self.operating_unit_id.id), + ("type", "=", "sale"), + ], + limit=1, + ) + if not journal: + raise ValidationError( + _("You need to create a sales journal for this operating unit.") + ) + invoice_vals.update( + { + "operating_unit_id": self.operating_unit_id.id, + "journal_id": journal.id, + } + ) + return invoice_vals + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + operating_unit_id = fields.Many2one( + related="order_id.operating_unit_id", + string="Operating Unit", + store=True, + ) diff --git a/sale_operating_unit/readme/CONTRIBUTORS.rst b/sale_operating_unit/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..766aec23b2 --- /dev/null +++ b/sale_operating_unit/readme/CONTRIBUTORS.rst @@ -0,0 +1,8 @@ +* Jordi Ballester Alomar +* Aaron Henriquez +* Lois Rilo +* Miquel Raich +* Sudhir Arya +* Darshan Patel +* Alan Ramos +* Alejandro Padrón diff --git a/sale_operating_unit/readme/DESCRIPTION.rst b/sale_operating_unit/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..1352ea357f --- /dev/null +++ b/sale_operating_unit/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module extends the Sales capabilities of Odoo and introduces the operating +unit to the Sales Order. Security rules are defined to ensure that users can +only display the Sales Orders in which they are allowed access to. diff --git a/sale_operating_unit/readme/USAGE.rst b/sale_operating_unit/readme/USAGE.rst new file mode 100644 index 0000000000..d9002f8574 --- /dev/null +++ b/sale_operating_unit/readme/USAGE.rst @@ -0,0 +1,4 @@ +Follow these steps: + +#. Create a Sale Order. +#. The Operating Unit of the Sale Team is assigned to the Sale Order. diff --git a/sale_operating_unit/report/__init__.py b/sale_operating_unit/report/__init__.py new file mode 100644 index 0000000000..fca264f877 --- /dev/null +++ b/sale_operating_unit/report/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import sale_report diff --git a/sale_operating_unit/report/sale_report.py b/sale_operating_unit/report/sale_report.py new file mode 100644 index 0000000000..ef86c2bad1 --- /dev/null +++ b/sale_operating_unit/report/sale_report.py @@ -0,0 +1,18 @@ +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0). + +from odoo import fields, models + + +class SaleReport(models.Model): + _inherit = "sale.report" + + operating_unit_id = fields.Many2one("operating.unit", "Operating Unit") + + def _group_by_sale(self, groupby=""): + res = super()._group_by_sale(groupby) + res += """, s.operating_unit_id""" + return res + + def _select_additional_fields(self, fields): + fields["operating_unit_id"] = ", s.operating_unit_id as operating_unit_id" + return super()._select_additional_fields(fields) diff --git a/sale_operating_unit/security/sale_security.xml b/sale_operating_unit/security/sale_security.xml new file mode 100644 index 0000000000..a445d8e43e --- /dev/null +++ b/sale_operating_unit/security/sale_security.xml @@ -0,0 +1,42 @@ + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + Sales Orders from allowed operating units + + + + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + Sales Order lines from allowed operating units + + + + + + + + + ['|',('operating_unit_id','=',False),('operating_unit_id','in', user.operating_unit_ids.ids)] + Sales Report from allowed operating units + + + + + + + diff --git a/sale_operating_unit/static/description/icon.png b/sale_operating_unit/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/sale_operating_unit/static/description/icon.png differ diff --git a/sale_operating_unit/static/description/index.html b/sale_operating_unit/static/description/index.html new file mode 100644 index 0000000000..195a53461f --- /dev/null +++ b/sale_operating_unit/static/description/index.html @@ -0,0 +1,440 @@ + + + + + + +Operating Unit in Sales + + + +
+

Operating Unit in Sales

+ + +

License: LGPL-3 brain-tec/minditp

+

This module extends the Sales capabilities of Odoo and introduces the operating +unit to the Sales Order. Security rules are defined to ensure that users can +only display the Sales Orders in which they are allowed access to.

+

Table of contents

+ +
+

Usage

+

Follow these steps:

+
    +
  1. Create a Sale Order.
  2. +
  3. The Operating Unit of the Sale Team is assigned to the Sale Order.
  4. +
+
+
+

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

+
    +
  • ForgeFlow
  • +
  • Serpent Consulting Services Pvt. Ltd.
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by bt-group.

+bt-group +

bt-group is a is an independent international software and consulting company considered as one of the +best Odoo partners worldwide, being already awarded several times as Best Odoo Partner in Europe.

+

Since its founding in 2000, they have supported small to large companies with +the introduction and implementation of business software projects from their locations +in Switzerland, Germany, Austria and Spain.

+

Their goal is the commitment to a more efficient daily business routine in SMEs.

+

This module is part of the brain-tec/minditp project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/sale_operating_unit/test-requirements.txt b/sale_operating_unit/test-requirements.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sale_operating_unit/tests/__init__.py b/sale_operating_unit/tests/__init__.py new file mode 100644 index 0000000000..fb298f99cf --- /dev/null +++ b/sale_operating_unit/tests/__init__.py @@ -0,0 +1,3 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import test_sale_operating_unit diff --git a/sale_operating_unit/tests/test_sale_operating_unit.py b/sale_operating_unit/tests/test_sale_operating_unit.py new file mode 100644 index 0000000000..e82aa2f8de --- /dev/null +++ b/sale_operating_unit/tests/test_sale_operating_unit.py @@ -0,0 +1,174 @@ +# © 2019 ForgeFlow S.L. - +# Jordi Ballester Alomar +# © 2019 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo.tests import common + + +class TestSaleOperatingUnit(common.TransactionCase): + def setUp(self): + super(TestSaleOperatingUnit, self).setUp() + self.res_groups = self.env["res.groups"] + self.partner_model = self.env["res.partner"] + self.res_users_model = self.env["res.users"] + self.sale_model = self.env["sale.order"] + self.sale_line_model = self.env["sale.order.line"] + self.sale_team_model = self.env["crm.team"] + self.acc_move_model = self.env["account.move"] + self.res_company_model = self.env["res.company"] + self.product_model = self.env["product.product"] + self.operating_unit_model = self.env["operating.unit"] + self.company_model = self.env["res.company"] + self.payment_model = self.env["sale.advance.payment.inv"] + # Company + self.company = self.env.ref("base.main_company") + self.grp_sale_user = self.env.ref("sales_team.group_sale_manager") + self.grp_acc_user = self.env.ref("account.group_account_invoice") + # Main Operating Unit + self.ou1 = self.env.ref("operating_unit.main_operating_unit") + # B2B Operating Unit + self.b2b = self.env.ref("operating_unit.b2b_operating_unit") + # B2C Operating Unit + self.b2c = self.env.ref("operating_unit.b2c_operating_unit") + # Payment Term + self.pay = self.env.ref("account.account_payment_term_immediate") + # Customer + self.customer = self.env.ref("base.res_partner_2") + # Price list + self.pricelist = self.env.ref("product.list0") + # Partner + self.partner1 = self.env.ref("base.res_partner_1") + # Products + self.product1 = self.env.ref("product.product_product_7") + self.product1.write({"invoice_policy": "order"}) + # Create user1 + self.user1 = self._create_user( + "user_1", + [self.grp_sale_user, self.grp_acc_user], + self.company, + [self.ou1, self.b2c], + ) + # Create user2 + self.user2 = self._create_user( + "user_2", [self.grp_sale_user, self.grp_acc_user], self.company, [self.b2c] + ) + + # Create sales team OU1 + self.sale_team_ou1 = self._create_sale_team(self.user1.id, self.ou1) + + # Create sales team OU2 + self.sale_team_b2c = self._create_sale_team(self.user2.id, self.b2c) + + # Create Sale Order1 + self.sale1 = self._create_sale_order( + self.user1.id, + self.customer, + self.product1, + self.pricelist, + self.sale_team_ou1, + ) + # Create Sale Order2 + self.sale2 = self._create_sale_order( + self.user2.id, + self.customer, + self.product1, + self.pricelist, + self.sale_team_b2c, + ) + + def _create_user(self, login, groups, company, operating_units): + """Create a user.""" + group_ids = [group.id for group in groups] + user = self.res_users_model.create( + { + "name": "Test Sales User", + "login": login, + "password": "demo", + "email": "example@yourcompany.com", + "company_id": company.id, + "company_ids": [(4, company.id)], + "operating_unit_ids": [(4, ou.id) for ou in operating_units], + "groups_id": [(6, 0, group_ids)], + } + ) + return user + + def _create_sale_team(self, uid, operating_unit): + """Create a sale team.""" + team = ( + self.sale_team_model.with_user(uid) + .with_context(mail_create_nosubscribe=True) + .create( + {"name": operating_unit.name, "operating_unit_id": operating_unit.id} + ) + ) + return team + + def _create_sale_order(self, uid, customer, product, pricelist, team): + """Create a sale order.""" + sale = self.sale_model.with_user(uid).create( + { + "partner_id": customer.id, + "partner_invoice_id": customer.id, + "partner_shipping_id": customer.id, + "pricelist_id": pricelist.id, + "team_id": team.id, + "operating_unit_id": team.operating_unit_id.id, + } + ) + self.sale_line_model.with_user(uid).create( + {"order_id": sale.id, "product_id": product.id, "name": "Sale Order Line"} + ) + return sale + + def _confirm_sale(self, sale): + sale.action_confirm() + payment = self.payment_model.create({"advance_payment_method": "delivered"}) + sale_context = { + "active_id": sale.id, + "active_ids": sale.ids, + "active_model": "sale.order", + "open_invoices": True, + } + res = payment.with_context(**sale_context).create_invoices() + invoice_id = res["res_id"] + return invoice_id + + def test_security(self): + """Test Sale Operating Unit""" + # User 2 is only assigned to Operating Unit B2C, and cannot + # Access Sales order from Main Operating Unit. + sale = self.sale_model.with_user(self.user2.id).search( + [("id", "=", self.sale1.id), ("operating_unit_id", "=", self.ou1.id)] + ) + self.assertEqual( + sale.ids, [], "User 2 should not have access to " "OU %s" % self.ou1.name + ) + # Confirm Sale1 + self._confirm_sale(self.sale1) + # Confirm Sale2 + b2c_invoice_id = self._confirm_sale(self.sale2) + # Checks that invoice has OU b2c + b2c = self.acc_move_model.with_user(self.user2.id).search( + [("id", "=", b2c_invoice_id), ("operating_unit_id", "=", self.b2c.id)] + ) + self.assertNotEqual(b2c.ids, [], "Invoice should have b2c OU") + + def test_security_2(self): + """Test Sale Operating Unit""" + # User 2 is only assigned to Operating Unit B2C, and cannot + # Access Sales order from Main Operating Unit. + sale = self.sale_model.with_user(self.user2.id).search( + [("id", "=", self.sale1.id), ("operating_unit_id", "=", self.ou1.id)] + ) + self.assertEqual( + sale.ids, [], "User 2 should not have access to " "OU %s" % self.ou1.name + ) + + sale = self.sale_model.with_user(self.user2.id).search( + [("id", "=", self.sale2.id), ("operating_unit_id", "=", self.b2c.id)] + ) + + self.assertEqual( + len(sale.ids), 1, "User 1 should have access to " "OU %s" % self.b2c.name + ) diff --git a/sale_operating_unit/views/sale_report_view.xml b/sale_operating_unit/views/sale_report_view.xml new file mode 100644 index 0000000000..43adcf6c9e --- /dev/null +++ b/sale_operating_unit/views/sale_report_view.xml @@ -0,0 +1,27 @@ + + + + + sale.report.search + sale.report + + + + + + + + + + + diff --git a/sale_operating_unit/views/sale_view.xml b/sale_operating_unit/views/sale_view.xml new file mode 100644 index 0000000000..bff2c9a166 --- /dev/null +++ b/sale_operating_unit/views/sale_view.xml @@ -0,0 +1,70 @@ + + + + + sale.order.tree + sale.order + + + + + + + + + sale.order.tree + sale.order + + + + + + + + + sale.order.form + sale.order + + + + + + + + + sale.order.list.select + sale.order + + + + + + + + + + + diff --git a/sale_operating_unit/wizard/__init__.py b/sale_operating_unit/wizard/__init__.py new file mode 100644 index 0000000000..fca48c3d09 --- /dev/null +++ b/sale_operating_unit/wizard/__init__.py @@ -0,0 +1 @@ +from . import sale_make_invoice_advance diff --git a/sale_operating_unit/wizard/sale_make_invoice_advance.py b/sale_operating_unit/wizard/sale_make_invoice_advance.py new file mode 100644 index 0000000000..6a915c77b9 --- /dev/null +++ b/sale_operating_unit/wizard/sale_make_invoice_advance.py @@ -0,0 +1,36 @@ +from odoo import _, models +from odoo.exceptions import ValidationError + + +class SaleAdvancePaymentInv(models.TransientModel): + _inherit = "sale.advance.payment.inv" + + def _create_invoice(self, order, so_line, amount): + invoice = super(SaleAdvancePaymentInv, self)._create_invoice( + order, so_line, amount + ) + invoice.operating_unit_id = order.operating_unit_id.id + return invoice + + def _prepare_invoice_values(self, order, name, amount, so_line): + invoice_vals = super(SaleAdvancePaymentInv, self)._prepare_invoice_values( + order, name, amount, so_line + ) + journal = self.env["account.journal"].search( + [ + ("operating_unit_id", "=", order.operating_unit_id.id), + ("type", "=", "sale"), + ], + limit=1, + ) + if not journal: + raise ValidationError( + _("You need to create a sales journal for this operating unit.") + ) + invoice_vals.update( + { + "operating_unit_id": order.operating_unit_id.id, + "journal_id": journal.id, + } + ) + return invoice_vals diff --git a/sale_operating_unit_sequence/README.rst b/sale_operating_unit_sequence/README.rst new file mode 100644 index 0000000000..9f5d63a2a5 --- /dev/null +++ b/sale_operating_unit_sequence/README.rst @@ -0,0 +1,85 @@ +===================================== +Sale Order Sequence by Operating Unit +===================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Foperating--unit-lightgray.png?logo=github + :target: https://github.com/OCA/operating-unit/tree/13.0/sale_operating_unit_sequence + :alt: OCA/operating-unit +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/operating-unit-13-0/operating-unit-13-0-sale_operating_unit_sequence + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/213/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module use to define sequence of Sale Orders by Operating Unit. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +You can change the default sequence by the one of your choice +going to *Settings > Technical > Sequences & Identifiers > Sequences*, and +editing the record `Sale Order of ...`. + +You will only have access to that section if your section has `Technical features` +permission check marked. + +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 +~~~~~~~ + +* Ecosoft + +Contributors +~~~~~~~~~~~~ + +* `Ecosoft `__: + + * Pimolnat Suntian + +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/operating-unit `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_operating_unit_sequence/__init__.py b/sale_operating_unit_sequence/__init__.py new file mode 100644 index 0000000000..21101d3490 --- /dev/null +++ b/sale_operating_unit_sequence/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import models diff --git a/sale_operating_unit_sequence/__manifest__.py b/sale_operating_unit_sequence/__manifest__.py new file mode 100644 index 0000000000..b12dac1a68 --- /dev/null +++ b/sale_operating_unit_sequence/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2020 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +{ + "name": "Sale Order Sequence by Operating Unit", + "version": "15.0.1.0.0", + "license": "AGPL-3", + "category": "Sale", + "author": "Ecosoft, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/operating-unit", + "depends": ["sale_operating_unit"], + "data": ["views/operating_unit_view.xml"], + "installable": True, +} diff --git a/sale_operating_unit_sequence/i18n/sale_operating_unit_sequence.pot b/sale_operating_unit_sequence/i18n/sale_operating_unit_sequence.pot new file mode 100644 index 0000000000..1d22578703 --- /dev/null +++ b/sale_operating_unit_sequence/i18n/sale_operating_unit_sequence.pot @@ -0,0 +1,34 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_operating_unit_sequence +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.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: sale_operating_unit_sequence +#: model:ir.model,name:sale_operating_unit_sequence.model_operating_unit +msgid "Operating Unit" +msgstr "" + +#. module: sale_operating_unit_sequence +#: model:ir.model.fields,field_description:sale_operating_unit_sequence.field_operating_unit__sale_sequence_id +msgid "Sale Order Sequence" +msgstr "" + +#. module: sale_operating_unit_sequence +#: model:ir.model,name:sale_operating_unit_sequence.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_operating_unit_sequence +#: model:ir.model.fields,help:sale_operating_unit_sequence.field_operating_unit__sale_sequence_id +msgid "Sequence of sale order with this operating unit" +msgstr "" diff --git a/sale_operating_unit_sequence/models/__init__.py b/sale_operating_unit_sequence/models/__init__.py new file mode 100644 index 0000000000..91f981c6c5 --- /dev/null +++ b/sale_operating_unit_sequence/models/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import operating_unit +from . import sale_order diff --git a/sale_operating_unit_sequence/models/operating_unit.py b/sale_operating_unit_sequence/models/operating_unit.py new file mode 100644 index 0000000000..c3be455a13 --- /dev/null +++ b/sale_operating_unit_sequence/models/operating_unit.py @@ -0,0 +1,14 @@ +# Copyright 2020 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from odoo import fields, models + + +class OperatingUnit(models.Model): + _inherit = "operating.unit" + + sale_sequence_id = fields.Many2one( + comodel_name="ir.sequence", + string="Sale Order Sequence", + help="Sequence of sale order with this operating unit", + ) diff --git a/sale_operating_unit_sequence/models/sale_order.py b/sale_operating_unit_sequence/models/sale_order.py new file mode 100644 index 0000000000..6307fd695e --- /dev/null +++ b/sale_operating_unit_sequence/models/sale_order.py @@ -0,0 +1,16 @@ +# Copyright 2020 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from odoo import api, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + @api.model + def create(self, vals): + if vals.get("name", "/") == "/" and vals.get("operating_unit_id", False): + ou_id = self.env["operating.unit"].browse(vals["operating_unit_id"]) + if ou_id.sale_sequence_id: + vals["name"] = ou_id.sale_sequence_id.next_by_id() + return super().create(vals) diff --git a/sale_operating_unit_sequence/readme/CONFIGURE.rst b/sale_operating_unit_sequence/readme/CONFIGURE.rst new file mode 100644 index 0000000000..30ecec6fc0 --- /dev/null +++ b/sale_operating_unit_sequence/readme/CONFIGURE.rst @@ -0,0 +1,6 @@ +You can change the default sequence by the one of your choice +going to *Settings > Technical > Sequences & Identifiers > Sequences*, and +editing the record `Sale Order of ...`. + +You will only have access to that section if your section has `Technical features` +permission check marked. diff --git a/sale_operating_unit_sequence/readme/CONTRIBUTORS.rst b/sale_operating_unit_sequence/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..ea63aa7bc1 --- /dev/null +++ b/sale_operating_unit_sequence/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Ecosoft `__: + + * Pimolnat Suntian diff --git a/sale_operating_unit_sequence/readme/DESCRIPTION.rst b/sale_operating_unit_sequence/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..2b1f7aa88d --- /dev/null +++ b/sale_operating_unit_sequence/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module use to define sequence of Sale Orders by Operating Unit. diff --git a/sale_operating_unit_sequence/static/description/icon.png b/sale_operating_unit_sequence/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/sale_operating_unit_sequence/static/description/icon.png differ diff --git a/sale_operating_unit_sequence/static/description/index.html b/sale_operating_unit_sequence/static/description/index.html new file mode 100644 index 0000000000..3f0c823df5 --- /dev/null +++ b/sale_operating_unit_sequence/static/description/index.html @@ -0,0 +1,431 @@ + + + + + + +Sale Order Sequence by Operating Unit + + + +
+

Sale Order Sequence by Operating Unit

+ + +

Beta License: AGPL-3 OCA/operating-unit Translate me on Weblate Try me on Runbot

+

This module use to define sequence of Sale Orders by Operating Unit.

+

Table of contents

+ +
+

Configuration

+

You can change the default sequence by the one of your choice +going to Settings > Technical > Sequences & Identifiers > Sequences, and +editing the record Sale Order of ….

+

You will only have access to that section if your section has Technical features +permission check marked.

+
+
+

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

+
    +
  • Ecosoft
  • +
+
+
+

Contributors

+ +
+
+

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/operating-unit project on GitHub.

+

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

+
+
+
+ + diff --git a/sale_operating_unit_sequence/tests/__init__.py b/sale_operating_unit_sequence/tests/__init__.py new file mode 100644 index 0000000000..fbbe8c9d9b --- /dev/null +++ b/sale_operating_unit_sequence/tests/__init__.py @@ -0,0 +1,3 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import test_sale_operating_unit_sequence diff --git a/sale_operating_unit_sequence/tests/test_sale_operating_unit_sequence.py b/sale_operating_unit_sequence/tests/test_sale_operating_unit_sequence.py new file mode 100644 index 0000000000..1ee8e1e643 --- /dev/null +++ b/sale_operating_unit_sequence/tests/test_sale_operating_unit_sequence.py @@ -0,0 +1,22 @@ +# Copyright 2020 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestSaleOperatingUnitSequence(TransactionCase): + @classmethod + def setUpClass(cls): + super(TestSaleOperatingUnitSequence, cls).setUpClass() + cls.sale_model = cls.env["sale.order"] + cls.customer = cls.env["res.partner"].create({"name": "Customer"}) + + def test_create_sequence(self): + # Test name != '/' + so1 = self.sale_model.create({"partner_id": self.customer.id}) + self.assertNotEqual(so1.name, "/", "Sequence created") + # Test name 1 != name 2 + so1_sequence = so1.name + so2 = so1.copy() + so2_sequence = so2.name + self.assertNotEqual(so1_sequence, so2_sequence, "Sequences are different") diff --git a/sale_operating_unit_sequence/views/operating_unit_view.xml b/sale_operating_unit_sequence/views/operating_unit_view.xml new file mode 100644 index 0000000000..c166918b63 --- /dev/null +++ b/sale_operating_unit_sequence/views/operating_unit_view.xml @@ -0,0 +1,13 @@ + + + + operating.unit.form + operating.unit + + + + + + + + diff --git a/sale_stock_operating_unit/README.rst b/sale_stock_operating_unit/README.rst new file mode 100644 index 0000000000..9fc1acf254 --- /dev/null +++ b/sale_stock_operating_unit/README.rst @@ -0,0 +1,94 @@ +============================= +Operating Unit in Sales Stock +============================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| 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 +.. |badge2| image:: https://img.shields.io/badge/github-minditp-lightgray.png?logo=github + :target: https://github.com/brain-tec/minditp/tree/15.0-mig-sale_stock_operating_unit/sale_stock_operating_unit + :alt: brain-tec/minditp + +|badge1| |badge2| + +This module was written to extend the Sales and the Stock capabilities of Odoo. +This module introduces some new operating unit (OU) capabilities in order to +enhance the performance of already existing OU features for Sales and Stock. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Follow these steps: + +#. Use a created Operating Unit (OU) or create one in Settings -> + Users&Companies -> Operating + Units. +#. Use a created Sales Team or create a new one in Sales -> Configuration -> + Sales Team and assign an OU +#. Create a new Warehouse with the previous OU (or use a Warehouse with an + assigned OU) +#. Check the Location has the same OU as the Warehouse in Inventory -> + Configuration Locations (make sure you have Storage Locations checkbox + activated) +#. Create a new Sale Order with the Sales Team, Operating Unit and Warehouse + used/created before. +#. OU, Picking Type and Location are propagated to the Picking. + +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 +~~~~~~~ + +* Eficent +* Serpent Consulting Services Pvt. Ltd. + +Contributors +~~~~~~~~~~~~ + +* Eficent Business and IT Consulting Services S.L. +* Serpent Consulting Services Pvt. Ltd. +* Alejandro Padrón + +Maintainers +~~~~~~~~~~~ + +This module is maintained by bt-group. + +.. image:: https://raw.githubusercontent.com/brain-tec/static/master/img/bt_logo_readme.png + :alt: bt-group + :width: 150px + :target: https://www.braintec-group.com/en-us + +bt-group is a is an independent international software and consulting company considered as one of the +best Odoo partners worldwide, being already awarded several times as Best Odoo Partner in Europe. + +Since its founding in 2000, they have supported small to large companies with +the introduction and implementation of business software projects from their locations +in Switzerland, Germany, Austria and Spain. + +Their goal is the commitment to a more efficient daily business routine in SMEs. + +This module is part of the `brain-tec/minditp `_ project on GitHub. + +You are welcome to contribute. diff --git a/sale_stock_operating_unit/__init__.py b/sale_stock_operating_unit/__init__.py new file mode 100644 index 0000000000..133f68732d --- /dev/null +++ b/sale_stock_operating_unit/__init__.py @@ -0,0 +1,2 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import models diff --git a/sale_stock_operating_unit/__manifest__.py b/sale_stock_operating_unit/__manifest__.py new file mode 100644 index 0000000000..d85680de17 --- /dev/null +++ b/sale_stock_operating_unit/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2015-19 Eficent Business and IT Consulting Services S.L. - +# Jordi Ballester Alomar +# Copyright 2015-19 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +{ + "name": "Operating Unit in Sales Stock", + "summary": "An operating unit (OU) is an organizational entity part of a " + "company", + "version": "15.0.1.0.0", + "author": "Eficent, Serpent Consulting Services Pvt. Ltd., " + "Odoo Community Association (OCA)", + "license": "LGPL-3", + "website": "https://github.com/OCA/operating-unit", + "category": "Sales Management", + "depends": [ + "sale_stock", + "sale_operating_unit", + "stock_operating_unit", + ], + "installable": True, +} diff --git a/sale_stock_operating_unit/i18n/it.po b/sale_stock_operating_unit/i18n/it.po new file mode 100644 index 0000000000..586e41b1a0 --- /dev/null +++ b/sale_stock_operating_unit/i18n/it.po @@ -0,0 +1,50 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_stock_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2021-06-17 13:48+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.3.2\n" + +#. module: sale_stock_operating_unit +#: code:addons/sale_stock_operating_unit/models/sale_order.py:64 +#, python-format +msgid "Configuration error!\n" +"The OperatingUnit in the Sales Order and in the Warehouse must be the same." +msgstr "" +"Errore di configurazione!\n" +"L'Unità Operativa nell'ordine di vendita e nel magazzino deve essere la " +"stessa." + +#. module: sale_stock_operating_unit +#: model:ir.model,name:sale_stock_operating_unit.model_sale_order +msgid "Sale Order" +msgstr "Ordine di Vendita" + +#. module: sale_stock_operating_unit +#: code:addons/sale_stock_operating_unit/models/stock_warehouse.py:20 +#, python-format +msgid "Sales Order records already exist(s) for this warehouse and operating unit." +msgstr "" +"Esistono già degli ordini di vendita per questo magazzino e Unità Operativa." + +#. module: sale_stock_operating_unit +#: model:ir.model,name:sale_stock_operating_unit.model_stock_move +msgid "Stock Move" +msgstr "Movimento di Magazzino" + +#. module: sale_stock_operating_unit +#: model:ir.model,name:sale_stock_operating_unit.model_stock_warehouse +#: model:ir.model.fields,field_description:sale_stock_operating_unit.field_sale_order__warehouse_id +msgid "Warehouse" +msgstr "Magazzino" diff --git a/sale_stock_operating_unit/i18n/pt.po b/sale_stock_operating_unit/i18n/pt.po new file mode 100644 index 0000000000..df04187264 --- /dev/null +++ b/sale_stock_operating_unit/i18n/pt.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_stock_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-02-29 15:13+0000\n" +"Last-Translator: Pedro Castro Silva \n" +"Language-Team: none\n" +"Language: pt\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 3.10\n" + +#. module: sale_stock_operating_unit +#: code:addons/sale_stock_operating_unit/models/sale_order.py:64 +#, python-format +msgid "Configuration error!\n" +"The OperatingUnit in the Sales Order and in the Warehouse must be the same." +msgstr "" +"Erro de Configuração!\n" +"As Unidades Operacionais na Encomenda de Venda e no Armazém têm que ser " +"iguais." + +#. module: sale_stock_operating_unit +#: model:ir.model,name:sale_stock_operating_unit.model_sale_order +msgid "Sale Order" +msgstr "Encomenda de Venda" + +#. module: sale_stock_operating_unit +#: code:addons/sale_stock_operating_unit/models/stock_warehouse.py:20 +#, python-format +msgid "Sales Order records already exist(s) for this warehouse and operating unit." +msgstr "" +"Já existem registos de Encomendas de Vendas para este armazém e unidade " +"operacional." + +#. module: sale_stock_operating_unit +#: model:ir.model,name:sale_stock_operating_unit.model_stock_move +msgid "Stock Move" +msgstr "Movimento de Stock" + +#. module: sale_stock_operating_unit +#: model:ir.model,name:sale_stock_operating_unit.model_stock_warehouse +#: model:ir.model.fields,field_description:sale_stock_operating_unit.field_sale_order__warehouse_id +msgid "Warehouse" +msgstr "Armazém" diff --git a/sale_stock_operating_unit/i18n/sale_stock_operating_unit.pot b/sale_stock_operating_unit/i18n/sale_stock_operating_unit.pot new file mode 100644 index 0000000000..eae22608d6 --- /dev/null +++ b/sale_stock_operating_unit/i18n/sale_stock_operating_unit.pot @@ -0,0 +1,44 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_stock_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.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: sale_stock_operating_unit +#: code:addons/sale_stock_operating_unit/models/sale_order.py:64 +#, python-format +msgid "Configuration error!\n" +"The OperatingUnit in the Sales Order and in the Warehouse must be the same." +msgstr "" + +#. module: sale_stock_operating_unit +#: model:ir.model,name:sale_stock_operating_unit.model_sale_order +msgid "Sale Order" +msgstr "" + +#. module: sale_stock_operating_unit +#: code:addons/sale_stock_operating_unit/models/stock_warehouse.py:20 +#, python-format +msgid "Sales Order records already exist(s) for this warehouse and operating unit." +msgstr "" + +#. module: sale_stock_operating_unit +#: model:ir.model,name:sale_stock_operating_unit.model_stock_move +msgid "Stock Move" +msgstr "" + +#. module: sale_stock_operating_unit +#: model:ir.model,name:sale_stock_operating_unit.model_stock_warehouse +#: model:ir.model.fields,field_description:sale_stock_operating_unit.field_sale_order__warehouse_id +msgid "Warehouse" +msgstr "" + diff --git a/sale_stock_operating_unit/models/__init__.py b/sale_stock_operating_unit/models/__init__.py new file mode 100644 index 0000000000..18ebfa1c1e --- /dev/null +++ b/sale_stock_operating_unit/models/__init__.py @@ -0,0 +1,4 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import sale_order +from . import stock_move +from . import stock_warehouse diff --git a/sale_stock_operating_unit/models/sale_order.py b/sale_stock_operating_unit/models/sale_order.py new file mode 100644 index 0000000000..a54dca31a3 --- /dev/null +++ b/sale_stock_operating_unit/models/sale_order.py @@ -0,0 +1,87 @@ +# Copyright 2015-19 Eficent Business and IT Consulting Services S.L. - +# Jordi Ballester Alomar +# Copyright 2015-19 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import SUPERUSER_ID, _, api, fields, models +from odoo.exceptions import ValidationError + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + @api.model + def _default_warehouse_id(self): + res = super(SaleOrder, self)._default_warehouse_id() + team = self._get_default_team() + warehouses = self.env["stock.warehouse"].search( + [ + ( + "operating_unit_id", + "=", + team.with_user(SUPERUSER_ID).operating_unit_id.id, + ) + ], + limit=1, + ) + if warehouses: + return warehouses + return res + + warehouse_id = fields.Many2one( + comodel_name="stock.warehouse", default=_default_warehouse_id + ) + + @api.onchange("team_id") + def onchange_team_id(self): + res = super(SaleOrder, self).onchange_team_id() + if ( + self.team_id + and self.team_id.operating_unit_id + and self.team_id.operating_unit_id.id + != self.warehouse_id.operating_unit_id.id + ): + warehouses = self.env["stock.warehouse"].search( + [("operating_unit_id", "=", self.team_id.operating_unit_id.id)], limit=1 + ) + if warehouses: + self.warehouse_id = warehouses[0] + + return res + + @api.onchange("operating_unit_id") + def onchange_operating_unit_id(self): + if ( + self.operating_unit_id + and self.operating_unit_id.id != self.warehouse_id.operating_unit_id.id + ): + warehouses = self.env["stock.warehouse"].search( + [("operating_unit_id", "=", self.operating_unit_id.id)], limit=1 + ) + if warehouses: + self.warehouse_id = warehouses[0] + + @api.onchange("warehouse_id") + def onchange_warehouse_id(self): + if self.warehouse_id: + self.operating_unit_id = self.warehouse_id.operating_unit_id + if ( + self.team_id + and self.team_id.operating_unit_id != self.operating_unit_id + ): + self.team_id = False + + @api.constrains("operating_unit_id", "warehouse_id") + def _check_wh_operating_unit(self): + for rec in self: + if ( + rec.warehouse_id.operating_unit_id + and rec.operating_unit_id + and rec.operating_unit_id != rec.warehouse_id.operating_unit_id + ): + raise ValidationError( + _( + "Configuration error!\nThe Operating" + "Unit in the Sales Order and in the" + " Warehouse must be the same." + ) + ) diff --git a/sale_stock_operating_unit/models/stock_move.py b/sale_stock_operating_unit/models/stock_move.py new file mode 100644 index 0000000000..6c0c29baa3 --- /dev/null +++ b/sale_stock_operating_unit/models/stock_move.py @@ -0,0 +1,19 @@ +# Copyright 2015-19 Eficent Business and IT Consulting Services S.L. - +# Jordi Ballester Alomar +# Copyright 2015-19 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import models + + +class StockMove(models.Model): + _inherit = "stock.move" + + def _get_new_picking_values(self): + """ + Override to add Operating Units to Picking. + """ + values = super(StockMove, self)._get_new_picking_values() + + values.update({"operating_unit_id": self.operating_unit_id.id}) + + return values diff --git a/sale_stock_operating_unit/models/stock_warehouse.py b/sale_stock_operating_unit/models/stock_warehouse.py new file mode 100644 index 0000000000..f41c446ee7 --- /dev/null +++ b/sale_stock_operating_unit/models/stock_warehouse.py @@ -0,0 +1,27 @@ +# Copyright 2015-19 Eficent Business and IT Consulting Services S.L. - +# Jordi Ballester Alomar +# Copyright 2015-19 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import _, api, models +from odoo.exceptions import ValidationError + + +class StockWarehouse(models.Model): + _inherit = "stock.warehouse" + + @api.constrains("operating_unit_id") + def _check_existing_so_in_wh(self): + for rec in self: + sales = self.env["sale.order"].search( + [ + ("warehouse_id", "=", rec.id), + ("operating_unit_id", "!=", rec.operating_unit_id.id), + ] + ) + if sales: + raise ValidationError( + _( + "Sales Order records already exist(s) for this warehouse" + " and operating unit." + ) + ) diff --git a/sale_stock_operating_unit/readme/CONFIGURATION.rst b/sale_stock_operating_unit/readme/CONFIGURATION.rst new file mode 100644 index 0000000000..01a0841ee6 --- /dev/null +++ b/sale_stock_operating_unit/readme/CONFIGURATION.rst @@ -0,0 +1,2 @@ +Go to 'Settings / Technical / Actions / User-defined Defaults' and remove +the default set for the Shop. diff --git a/sale_stock_operating_unit/readme/CONTRIBUTORS.rst b/sale_stock_operating_unit/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..076ca13fda --- /dev/null +++ b/sale_stock_operating_unit/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Eficent Business and IT Consulting Services S.L. +* Serpent Consulting Services Pvt. Ltd. +* Alejandro Padrón diff --git a/sale_stock_operating_unit/readme/DESCRIPTION.rst b/sale_stock_operating_unit/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..4a1bbe58ae --- /dev/null +++ b/sale_stock_operating_unit/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module was written to extend the Sales and the Stock capabilities of Odoo. +This module introduces some new operating unit (OU) capabilities in order to +enhance the performance of already existing OU features for Sales and Stock. diff --git a/sale_stock_operating_unit/readme/USAGE.rst b/sale_stock_operating_unit/readme/USAGE.rst new file mode 100644 index 0000000000..ccc5f3babe --- /dev/null +++ b/sale_stock_operating_unit/readme/USAGE.rst @@ -0,0 +1,15 @@ +Follow these steps: + +#. Use a created Operating Unit (OU) or create one in Settings -> + Users&Companies -> Operating + Units. +#. Use a created Sales Team or create a new one in Sales -> Configuration -> + Sales Team and assign an OU +#. Create a new Warehouse with the previous OU (or use a Warehouse with an + assigned OU) +#. Check the Location has the same OU as the Warehouse in Inventory -> + Configuration Locations (make sure you have Storage Locations checkbox + activated) +#. Create a new Sale Order with the Sales Team, Operating Unit and Warehouse + used/created before. +#. OU, Picking Type and Location are propagated to the Picking. diff --git a/sale_stock_operating_unit/static/description/icon.png b/sale_stock_operating_unit/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/sale_stock_operating_unit/static/description/icon.png differ diff --git a/sale_stock_operating_unit/static/description/index.html b/sale_stock_operating_unit/static/description/index.html new file mode 100644 index 0000000000..7f1d4952a3 --- /dev/null +++ b/sale_stock_operating_unit/static/description/index.html @@ -0,0 +1,447 @@ + + + + + + +Operating Unit in Sales Stock + + + +
+

Operating Unit in Sales Stock

+ + +

License: LGPL-3 brain-tec/minditp

+

This module was written to extend the Sales and the Stock capabilities of Odoo. +This module introduces some new operating unit (OU) capabilities in order to +enhance the performance of already existing OU features for Sales and Stock.

+

Table of contents

+ +
+

Usage

+

Follow these steps:

+
    +
  1. Use a created Operating Unit (OU) or create one in Settings -> +Users&Companies -> Operating +Units.
  2. +
  3. Use a created Sales Team or create a new one in Sales -> Configuration -> +Sales Team and assign an OU
  4. +
  5. Create a new Warehouse with the previous OU (or use a Warehouse with an +assigned OU)
  6. +
  7. Check the Location has the same OU as the Warehouse in Inventory -> +Configuration Locations (make sure you have Storage Locations checkbox +activated)
  8. +
  9. Create a new Sale Order with the Sales Team, Operating Unit and Warehouse +used/created before.
  10. +
  11. OU, Picking Type and Location are propagated to the Picking.
  12. +
+
+
+

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

+
    +
  • Eficent
  • +
  • Serpent Consulting Services Pvt. Ltd.
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by bt-group.

+bt-group +

bt-group is a is an independent international software and consulting company considered as one of the +best Odoo partners worldwide, being already awarded several times as Best Odoo Partner in Europe.

+

Since its founding in 2000, they have supported small to large companies with +the introduction and implementation of business software projects from their locations +in Switzerland, Germany, Austria and Spain.

+

Their goal is the commitment to a more efficient daily business routine in SMEs.

+

This module is part of the brain-tec/minditp project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/sale_stock_operating_unit/test-requirements.txt b/sale_stock_operating_unit/test-requirements.txt new file mode 100644 index 0000000000..a0fa1689d0 --- /dev/null +++ b/sale_stock_operating_unit/test-requirements.txt @@ -0,0 +1,2 @@ +odoo-addon-sale_operating_unit @ git+https://github.com/OCA/operating-unit/pull/479/head#subdirectory=setup/sale_operating_unit +odoo-addon-stock_operating_unit @ git+https://github.com/OCA/operating-unit/pull/439/head#subdirectory=setup/stock_operating_unit diff --git a/sale_stock_operating_unit/tests/__init__.py b/sale_stock_operating_unit/tests/__init__.py new file mode 100644 index 0000000000..0d6189565e --- /dev/null +++ b/sale_stock_operating_unit/tests/__init__.py @@ -0,0 +1,2 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import test_sale_stock_operating_unit diff --git a/sale_stock_operating_unit/tests/test_sale_stock_operating_unit.py b/sale_stock_operating_unit/tests/test_sale_stock_operating_unit.py new file mode 100644 index 0000000000..3916f651b3 --- /dev/null +++ b/sale_stock_operating_unit/tests/test_sale_stock_operating_unit.py @@ -0,0 +1,146 @@ +# Copyright 2015-19 Eficent Business and IT Consulting Services S.L. - +# Jordi Ballester Alomar +# Copyright 2015-19 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo.tests import common + + +class TestSaleStockOperatingUnit(common.TransactionCase): + def setUp(self): + super(TestSaleStockOperatingUnit, self).setUp() + self.res_groups = self.env["res.groups"] + self.res_users_model = self.env["res.users"] + self.warehouse_model = self.env["stock.warehouse"] + self.sale_model = self.env["sale.order"] + self.sale_line_model = self.env["sale.order.line"] + self.sale_team_model = self.env["crm.team"] + # Company + self.company = self.env.ref("base.main_company") + # Groups + self.grp_sale_user = self.env.ref("sales_team.group_sale_manager") + self.grp_acc_user = self.env.ref("account.group_account_invoice") + # Main Operating Unit + self.ou1 = self.env.ref("operating_unit.main_operating_unit") + # B2C Operating Unit + self.b2c = self.env.ref("operating_unit.b2c_operating_unit") + # Customer + self.customer = self.env.ref("base.res_partner_2") + # Price list + self.pricelist = self.env.ref("product.list0") + # Products + self.product1 = self.env.ref("product.product_product_7") + self.product1.write({"invoice_policy": "order"}) + # Create user1 + self.user1 = self._create_user( + "user_1", + [self.grp_sale_user, self.grp_acc_user], + self.company, + [self.ou1, self.b2c], + ) + # Create user2 + self.user2 = self._create_user( + "user_2", [self.grp_sale_user, self.grp_acc_user], self.company, [self.b2c] + ) + + # Create sales team OU1 + self.sale_team_ou1 = self._create_sale_team(self.user1.id, self.ou1) + + # Create sales team OU2 + self.sale_team_b2c = self._create_sale_team(self.user2.id, self.b2c) + + # Warehouses + self.ou1_wh = self.env.ref("stock.warehouse0") + self.b2c_wh = self.env.ref("stock_operating_unit.stock_warehouse_b2c") + # Locations + self.b2c_wh.lot_stock_id.write( + {"company_id": self.company.id, "operating_unit_id": self.b2c.id} + ) + + # Create Sale Order1 + self.sale1 = self._create_sale_order( + self.user1.id, + self.customer, + self.product1, + self.pricelist, + self.sale_team_ou1, + self.ou1_wh, + ) + # Create Sale Order2 + self.sale2 = self._create_sale_order( + self.user2.id, + self.customer, + self.product1, + self.pricelist, + self.sale_team_b2c, + self.b2c_wh, + ) + + def _create_user(self, login, groups, company, operating_units): + """Create a user.""" + group_ids = [group.id for group in groups] + user = self.res_users_model.create( + { + "name": "Test Sales User", + "login": login, + "password": "demo", + "email": "example@yourcompany.com", + "company_id": company.id, + "company_ids": [(4, company.id)], + "operating_unit_ids": [(4, ou.id) for ou in operating_units], + "groups_id": [(6, 0, group_ids)], + } + ) + return user + + def _create_sale_team(self, uid, operating_unit): + """Create a sale team.""" + team = ( + self.sale_team_model.with_user(uid) + .with_context(mail_create_nosubscribe=True) + .create( + {"name": operating_unit.name, "operating_unit_id": operating_unit.id} + ) + ) + return team + + def _create_sale_order(self, uid, customer, product, pricelist, team, wh): + """Create a sale order.""" + sale = self.sale_model.with_user(uid).create( + { + "partner_id": customer.id, + "partner_invoice_id": customer.id, + "partner_shipping_id": customer.id, + "pricelist_id": pricelist.id, + "team_id": team.id, + "operating_unit_id": team.operating_unit_id.id, + "warehouse_id": wh.id, + } + ) + self.sale_line_model.with_user(uid).create( + {"order_id": sale.id, "product_id": product.id, "name": "Sale Order Line"} + ) + return sale + + def _confirm_sale(self, sale): + sale.action_confirm() + return True + + def test_security(self): + """Test Sale Operating Unit""" + # Confirm Sale1 + self._confirm_sale(self.sale1) + # Checks that OU in sale order and stock picking matches or not. + self.assertEqual( + self.sale1.operating_unit_id, + self.sale1.picking_ids.operating_unit_id, + "OU in Sale Order and Picking should be same", + ) + # Confirm Sale2 + self._confirm_sale(self.sale2) + # Checks that OU in sale order and stock picking matches or not. + self.assertEqual( + self.sale2.operating_unit_id, + self.sale2.picking_ids.operating_unit_id, + "OU in Sale Order and Picking should be same", + ) diff --git a/setup/pos_operating_unit/odoo/addons/pos_operating_unit b/setup/pos_operating_unit/odoo/addons/pos_operating_unit new file mode 120000 index 0000000000..7c64dbcfdf --- /dev/null +++ b/setup/pos_operating_unit/odoo/addons/pos_operating_unit @@ -0,0 +1 @@ +../../../../pos_operating_unit \ No newline at end of file diff --git a/setup/pos_operating_unit/setup.py b/setup/pos_operating_unit/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/pos_operating_unit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/purchase_operating_unit/odoo/addons/purchase_operating_unit b/setup/purchase_operating_unit/odoo/addons/purchase_operating_unit new file mode 120000 index 0000000000..4695973c4e --- /dev/null +++ b/setup/purchase_operating_unit/odoo/addons/purchase_operating_unit @@ -0,0 +1 @@ +../../../../purchase_operating_unit \ No newline at end of file diff --git a/setup/purchase_operating_unit/setup.py b/setup/purchase_operating_unit/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/purchase_operating_unit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/purchase_request_operating_unit/odoo/addons/purchase_request_operating_unit b/setup/purchase_request_operating_unit/odoo/addons/purchase_request_operating_unit new file mode 120000 index 0000000000..d252cd6e74 --- /dev/null +++ b/setup/purchase_request_operating_unit/odoo/addons/purchase_request_operating_unit @@ -0,0 +1 @@ +../../../../purchase_request_operating_unit \ No newline at end of file diff --git a/setup/purchase_request_operating_unit/setup.py b/setup/purchase_request_operating_unit/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/purchase_request_operating_unit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/sale_operating_unit/odoo/addons/sale_operating_unit b/setup/sale_operating_unit/odoo/addons/sale_operating_unit new file mode 120000 index 0000000000..47b46e01e9 --- /dev/null +++ b/setup/sale_operating_unit/odoo/addons/sale_operating_unit @@ -0,0 +1 @@ +../../../../sale_operating_unit \ No newline at end of file diff --git a/setup/sale_operating_unit/setup.py b/setup/sale_operating_unit/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/sale_operating_unit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/sale_operating_unit_sequence/odoo/addons/sale_operating_unit_sequence b/setup/sale_operating_unit_sequence/odoo/addons/sale_operating_unit_sequence new file mode 120000 index 0000000000..271df65100 --- /dev/null +++ b/setup/sale_operating_unit_sequence/odoo/addons/sale_operating_unit_sequence @@ -0,0 +1 @@ +../../../../sale_operating_unit_sequence \ No newline at end of file diff --git a/setup/sale_operating_unit_sequence/setup.py b/setup/sale_operating_unit_sequence/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/sale_operating_unit_sequence/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/sale_stock_operating_unit/odoo/addons/sale_stock_operating_unit b/setup/sale_stock_operating_unit/odoo/addons/sale_stock_operating_unit new file mode 120000 index 0000000000..9f5ec43e9d --- /dev/null +++ b/setup/sale_stock_operating_unit/odoo/addons/sale_stock_operating_unit @@ -0,0 +1 @@ +../../../../sale_stock_operating_unit \ No newline at end of file diff --git a/setup/sale_stock_operating_unit/setup.py b/setup/sale_stock_operating_unit/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/sale_stock_operating_unit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/stock_location_route_operating_unit/odoo/addons/stock_location_route_operating_unit b/setup/stock_location_route_operating_unit/odoo/addons/stock_location_route_operating_unit new file mode 120000 index 0000000000..868c92fd1c --- /dev/null +++ b/setup/stock_location_route_operating_unit/odoo/addons/stock_location_route_operating_unit @@ -0,0 +1 @@ +../../../../stock_location_route_operating_unit \ No newline at end of file diff --git a/setup/stock_location_route_operating_unit/setup.py b/setup/stock_location_route_operating_unit/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/stock_location_route_operating_unit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_account_operating_unit/test-requirements.txt b/stock_account_operating_unit/test-requirements.txt index 5602a95a22..e69de29bb2 100644 --- a/stock_account_operating_unit/test-requirements.txt +++ b/stock_account_operating_unit/test-requirements.txt @@ -1,2 +0,0 @@ -git+https://github.com/OCA/operating-unit/pull/420/head#subdirectory=setup/account_operating_unit -git+https://github.com/OCA/operating-unit/pull/439/head#subdirectory=setup/stock_operating_unit diff --git a/stock_location_route_operating_unit/README.rst b/stock_location_route_operating_unit/README.rst new file mode 100644 index 0000000000..64b07d11f4 --- /dev/null +++ b/stock_location_route_operating_unit/README.rst @@ -0,0 +1,102 @@ +=================================== +Stock Location Route Operating Unit +=================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png + :target: https://odoo-community.org/page/development-status + :alt: Production/Stable +.. |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%2Foperating--unit-lightgray.png?logo=github + :target: https://github.com/OCA/operating-unit/tree/15.0/stock_location_route_operating_unit + :alt: OCA/operating-unit +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/operating-unit-15-0/operating-unit-15-0-stock_location_route_operating_unit + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/213/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + + +This module extends the functionality of sale order lines to show only +the routes allowed for the operating unit of the sale order. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + + +To configure this module, you need to: + +#. Go to Inventory/Configuration/Routes +#. Select a route. +#. Define the operating units to be shown in sale lines. + +Usage +===== + + +To use this module, you need to: + +#. Go to configure routes to be shown in the sale line and select the operating units that you want to be available in the sale order lines. + +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 +~~~~~~~ + +* Jarsa + +Contributors +~~~~~~~~~~~~ + +* Alan Ramos +* Jarsa (`Jarsa `__) + +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. + +.. |maintainer-alan196| image:: https://github.com/alan196.png?size=40px + :target: https://github.com/alan196 + :alt: alan196 + +Current `maintainer `__: + +|maintainer-alan196| + +This module is part of the `OCA/operating-unit `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_location_route_operating_unit/__init__.py b/stock_location_route_operating_unit/__init__.py new file mode 100644 index 0000000000..c528507a40 --- /dev/null +++ b/stock_location_route_operating_unit/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2023 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + +from . import models diff --git a/stock_location_route_operating_unit/__manifest__.py b/stock_location_route_operating_unit/__manifest__.py new file mode 100644 index 0000000000..67e69a44ae --- /dev/null +++ b/stock_location_route_operating_unit/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2023 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + +{ + "name": "Stock Location Route Operating Unit", + "summary": "Manage Operating Unit in Stock Routes", + "version": "15.0.1.0.0", + "development_status": "Production/Stable", + "category": "Inventory/Inventory", + "website": "https://github.com/OCA/operating-unit", + "author": "Jarsa, Odoo Community Association (OCA)", + "maintainers": ["alan196"], + "license": "LGPL-3", + "application": False, + "installable": True, + "depends": [ + "sale_stock_operating_unit", + ], + "data": [ + "views/stock_location_route_view.xml", + "views/sale_order_view.xml", + ], +} diff --git a/stock_location_route_operating_unit/i18n/es.po b/stock_location_route_operating_unit/i18n/es.po new file mode 100644 index 0000000000..aa44692a19 --- /dev/null +++ b/stock_location_route_operating_unit/i18n/es.po @@ -0,0 +1,34 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_location_route_operating_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-03-11 17:53+0000\n" +"PO-Revision-Date: 2023-03-11 11:53-0600\n" +"Last-Translator: Jesús Alan Ramos Rodríguez \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 3.2.2\n" + +#. module: stock_location_route_operating_unit +#: model:ir.model,name:stock_location_route_operating_unit.model_stock_location_route +msgid "Inventory Routes" +msgstr "Rutas de inventario" + +#. module: stock_location_route_operating_unit +#: model:ir.model.fields,field_description:stock_location_route_operating_unit.field_stock_location_route__operating_unit_ids +#: model_terms:ir.ui.view,arch_db:stock_location_route_operating_unit.stock_location_route_view_form +msgid "Operating Units" +msgstr "Unidades operativas" + +#. module: stock_location_route_operating_unit +#: model:ir.model,name:stock_location_route_operating_unit.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de la orden de venta" diff --git a/stock_location_route_operating_unit/models/__init__.py b/stock_location_route_operating_unit/models/__init__.py new file mode 100644 index 0000000000..4690c6659b --- /dev/null +++ b/stock_location_route_operating_unit/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2023 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + +from . import stock_location_route diff --git a/stock_location_route_operating_unit/models/stock_location_route.py b/stock_location_route_operating_unit/models/stock_location_route.py new file mode 100644 index 0000000000..b6c45edd1d --- /dev/null +++ b/stock_location_route_operating_unit/models/stock_location_route.py @@ -0,0 +1,13 @@ +# Copyright 2023 Jarsa +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + +from odoo import fields, models + + +class StockLocationRoute(models.Model): + _inherit = "stock.location.route" + + operating_unit_ids = fields.Many2many( + comodel_name="operating.unit", + string="Operating Units", + ) diff --git a/stock_location_route_operating_unit/readme/CONFIGURE.rst b/stock_location_route_operating_unit/readme/CONFIGURE.rst new file mode 100644 index 0000000000..8be2fb3aff --- /dev/null +++ b/stock_location_route_operating_unit/readme/CONFIGURE.rst @@ -0,0 +1,6 @@ + +To configure this module, you need to: + +#. Go to Inventory/Configuration/Routes +#. Select a route. +#. Define the operating units to be shown in sale lines. diff --git a/stock_location_route_operating_unit/readme/CONTRIBUTORS.rst b/stock_location_route_operating_unit/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..0ef0d30847 --- /dev/null +++ b/stock_location_route_operating_unit/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Alan Ramos +* Jarsa (`Jarsa `__) diff --git a/stock_location_route_operating_unit/readme/DESCRIPTION.rst b/stock_location_route_operating_unit/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..9c983e98f5 --- /dev/null +++ b/stock_location_route_operating_unit/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ + +This module extends the functionality of sale order lines to show only +the routes allowed for the operating unit of the sale order. diff --git a/stock_location_route_operating_unit/readme/USAGE.rst b/stock_location_route_operating_unit/readme/USAGE.rst new file mode 100644 index 0000000000..5beaed0bd5 --- /dev/null +++ b/stock_location_route_operating_unit/readme/USAGE.rst @@ -0,0 +1,4 @@ + +To use this module, you need to: + +#. Go to configure routes to be shown in the sale line and select the operating units that you want to be available in the sale order lines. diff --git a/stock_location_route_operating_unit/readme/newsfragments/.gitkeep b/stock_location_route_operating_unit/readme/newsfragments/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/stock_location_route_operating_unit/static/description/icon.png b/stock_location_route_operating_unit/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/stock_location_route_operating_unit/static/description/icon.png differ diff --git a/stock_location_route_operating_unit/static/description/icon.svg b/stock_location_route_operating_unit/static/description/icon.svg new file mode 100644 index 0000000000..a7a26d0932 --- /dev/null +++ b/stock_location_route_operating_unit/static/description/icon.svg @@ -0,0 +1,79 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/stock_location_route_operating_unit/static/description/index.html b/stock_location_route_operating_unit/static/description/index.html new file mode 100644 index 0000000000..d64c9d6f3b --- /dev/null +++ b/stock_location_route_operating_unit/static/description/index.html @@ -0,0 +1,441 @@ + + + + + + +Stock Location Route Operating Unit + + + +
+

Stock Location Route Operating Unit

+ + +

Production/Stable License: LGPL-3 OCA/operating-unit Translate me on Weblate Try me on Runbot

+

This module extends the functionality of sale order lines to show only +the routes allowed for the operating unit of the sale order.

+

Table of contents

+ +
+

Configuration

+

To configure this module, you need to:

+
    +
  1. Go to Inventory/Configuration/Routes
  2. +
  3. Select a route.
  4. +
  5. Define the operating units to be shown in sale lines.
  6. +
+
+
+

Usage

+

To use this module, you need to:

+
    +
  1. Go to configure routes to be shown in the sale line and select the operating units that you want to be available in the sale order lines.
  2. +
+
+
+

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

+
    +
  • Jarsa
  • +
+
+
+

Contributors

+ +
+
+

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.

+

Current maintainer:

+

alan196

+

This module is part of the OCA/operating-unit project on GitHub.

+

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

+
+
+
+ + diff --git a/stock_location_route_operating_unit/views/sale_order_view.xml b/stock_location_route_operating_unit/views/sale_order_view.xml new file mode 100644 index 0000000000..dc4afbf740 --- /dev/null +++ b/stock_location_route_operating_unit/views/sale_order_view.xml @@ -0,0 +1,38 @@ + + + + + sale.order.view.form + sale.order + + + + + + + + + + + + + + diff --git a/stock_location_route_operating_unit/views/stock_location_route_view.xml b/stock_location_route_operating_unit/views/stock_location_route_view.xml new file mode 100644 index 0000000000..db4756063b --- /dev/null +++ b/stock_location_route_operating_unit/views/stock_location_route_view.xml @@ -0,0 +1,22 @@ + + + + + stock.location.route.view.form + stock.location.route + + + + + + + +