diff --git a/purchase_mrp_distribution/README.rst b/purchase_mrp_distribution/README.rst new file mode 100644 index 000000000..3bb3e645c --- /dev/null +++ b/purchase_mrp_distribution/README.rst @@ -0,0 +1,132 @@ +========================= +Purchase MRP Distribution +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:51d9b4cab80068fcea738841c68a570388273d7cde664d9603cad40b83184c13 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/15.0/purchase_mrp_distribution + :alt: OCA/manufacture +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-15-0/manufacture-15-0-purchase_mrp_distribution + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/manufacture&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to buy a product that will be divided into others without +knowing the exact quantity of the others. That quantity will be filled in the picking. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To use this module, we will need to configure n different distribution-type Bills of +Materials (BoM) for n products. To do this, we will follow these steps: + +#. Go to Manufacturing > Products > Bills of Materials +#. Click on Create +#. Select the product to be distributed in the receptions +#. Select the BoM type "Distribution" +#. In the components, add the products that can be filled in the reception (it is not + necessary to mark the quantity as it will default to 0) + +Usage +===== + +To see the module's functionality: + +1. Create a new purchase order +2. Add the product for which the BoM has been created +3. Confirm the order +4. Go to the created delivery order +5. You will see a new button in the move that contains the product, click on it +6. A wizard will open to define the quantity of each product to distribute +7. By clicking on Distribute, the move lines will be added with the products from the + BoM + +Known issues / Roadmap +====================== + +#. The current module is not designed to handle multi-step receipts. It operates on a + single-step process, making it unsuitable for more complex inventory management + scenarios. +#. The current module is not designed to handle multiple lots for a product. +#. The current module is not designed to handle multiple packages for a product. + +Changelog +========= + +This module aims to address the need to receive a generic product, but until the +moment of reception, we do not know the quantity that we might receive of its +specific products. + +Using kits was not suitable for us, because to account for a unit of the kit product, +the quantity specified in the BoM must be received. Therefore, we opted to define a +new type of BoM called distribution. + +In this way, upon receiving a unit of any product added to the BoM, it will be +accounted for as a unit of the generic product and will be reflected in the svls as +separate products, but they will be accounted for in the purchase line as part of the +product. + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnaiva + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_: + + * Pedro M. Baeza + * Carlos Roca + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/manufacture `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/purchase_mrp_distribution/__init__.py b/purchase_mrp_distribution/__init__.py new file mode 100644 index 000000000..aee8895e7 --- /dev/null +++ b/purchase_mrp_distribution/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/purchase_mrp_distribution/__manifest__.py b/purchase_mrp_distribution/__manifest__.py new file mode 100644 index 000000000..e18a27e80 --- /dev/null +++ b/purchase_mrp_distribution/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2024 Tecnatva - Carlos Roca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Purchase MRP Distribution", + "version": "15.0.1.0.0", + "author": "Tecnaiva, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/manufacture", + "category": "Manufacturing", + "depends": ["purchase_mrp"], + "data": [ + "security/ir.model.access.csv", + "views/mrp_bom_views.xml", + "views/stock_picking_views.xml", + "wizards/stock_move_distribution_wiz_views.xml", + ], + "license": "AGPL-3", + "installable": True, + "application": False, +} diff --git a/purchase_mrp_distribution/i18n/es.po b/purchase_mrp_distribution/i18n/es.po new file mode 100644 index 000000000..3821e6f14 --- /dev/null +++ b/purchase_mrp_distribution/i18n/es.po @@ -0,0 +1,189 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_mrp_distribution +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-07-09 09:38+0000\n" +"PO-Revision-Date: 2024-07-09 11:50+0200\n" +"Last-Translator: \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: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.0.1\n" + +#. module: purchase_mrp_distribution +#: model_terms:ir.ui.view,arch_db:purchase_mrp_distribution.purchase_order_form +msgid "" +"A BoM of type distribution is used to select the quantities of components." +msgstr "" +"Una lista de materiales (BoM) de tipo distribución se utiliza para " +"seleccionar las cantidades de componentes." + +#. module: purchase_mrp_distribution +#: model_terms:ir.ui.view,arch_db:purchase_mrp_distribution.purchase_order_form +msgid "At a Stock Transfer." +msgstr "En una transferencia de stock." + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_mrp_bom +msgid "Bill of Material" +msgstr "Lista de material" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_mrp_bom__type +msgid "BoM Type" +msgstr "Tipo de LdM" + +#. module: purchase_mrp_distribution +#: model_terms:ir.ui.view,arch_db:purchase_mrp_distribution.stock_move_distribution_wiz_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__create_uid +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__create_date +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__demand_quantity +msgid "Demand" +msgstr "Demanda" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__display_name +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: purchase_mrp_distribution +#: model_terms:ir.ui.view,arch_db:purchase_mrp_distribution.stock_move_distribution_wiz_form +msgid "Distribute" +msgstr "Distribuir" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields.selection,name:purchase_mrp_distribution.selection__mrp_bom__type__distribution +msgid "Distribution" +msgstr "Distribución" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move__distribution_bom_id +msgid "Distribution Bom" +msgstr "LdM de distribución" + +#. module: purchase_mrp_distribution +#: code:addons/purchase_mrp_distribution/models/stock_move.py:0 +#, python-format +msgid "Distribution for %s" +msgstr "Distribución para %s" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__id +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__id +msgid "ID" +msgstr "ID" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz____last_update +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__write_uid +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__write_date +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__line_ids +msgid "Line" +msgstr "Linea" + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_stock_move_distribution_wiz_line +msgid "Lines of wizard to distribute product qty to diferent products" +msgstr "" +"Líneas del asistente para distribuir la cantidad del producto a diferentes " +"productos" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__move_id +msgid "Move" +msgstr "Movimiento" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__product_id +msgid "Product" +msgstr "Producto" + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_purchase_order_line +msgid "Purchase Order Line" +msgstr "Línea de pedido de venta" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__quantity +msgid "Quantity" +msgstr "Cantidad" + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_stock_move +msgid "Stock Move" +msgstr "Movimiento de Stock" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,help:purchase_mrp_distribution.field_stock_move_distribution_wiz__demand_quantity +msgid "" +"This is the quantity of products from an inventory point of view. For moves " +"in the state 'done', this is the quantity of products that were actually " +"moved. For other moves, this is the quantity of product that is planned to " +"be moved. Lowering this quantity does not generate a backorder. Changing " +"this quantity on assigned moves affects the product reservation, and should " +"be done with care." +msgstr "" +"Ésta es la cantidad de productos desde un punto de vista de inventario. " +"Para movimientos en el estado 'Realizado', ésta es la cantidad de productos " +"que se movieron realmente. Para otros movimientos, ésta es la cantidad de " +"producto que está planeado mover. Disminuyendo esta cantidad no se genera " +"un pedido en espera. Cambiando esta cantidad en movimientos asignados, " +"afecta la reserva de producto, y debe ser realizado con cuidado." + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__move_uom_id +msgid "UoM" +msgstr "UdM" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__uom_id +msgid "Uom" +msgstr "UdM" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__wizard_id +msgid "Wizard" +msgstr "Asistente" + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_stock_move_distribution_wiz +msgid "Wizard to distribute product qty to diferent products" +msgstr "" +"Asistente para distribuir la cantidad de producto a diferentes productos" diff --git a/purchase_mrp_distribution/i18n/purchase_mrp_distribution.pot b/purchase_mrp_distribution/i18n/purchase_mrp_distribution.pot new file mode 100644 index 000000000..52d0922f4 --- /dev/null +++ b/purchase_mrp_distribution/i18n/purchase_mrp_distribution.pot @@ -0,0 +1,176 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_mrp_distribution +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-07-09 09:38+0000\n" +"PO-Revision-Date: 2024-07-09 09:38+0000\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_mrp_distribution +#: model_terms:ir.ui.view,arch_db:purchase_mrp_distribution.purchase_order_form +msgid "" +"A BoM of type distribution is used to select the quantities of components." +msgstr "" + +#. module: purchase_mrp_distribution +#: model_terms:ir.ui.view,arch_db:purchase_mrp_distribution.purchase_order_form +msgid "At a Stock Transfer." +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_mrp_bom +msgid "Bill of Material" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_mrp_bom__type +msgid "BoM Type" +msgstr "" + +#. module: purchase_mrp_distribution +#: model_terms:ir.ui.view,arch_db:purchase_mrp_distribution.stock_move_distribution_wiz_form +msgid "Cancel" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__create_uid +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__create_uid +msgid "Created by" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__create_date +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__create_date +msgid "Created on" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__demand_quantity +msgid "Demand" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__display_name +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__display_name +msgid "Display Name" +msgstr "" + +#. module: purchase_mrp_distribution +#: model_terms:ir.ui.view,arch_db:purchase_mrp_distribution.stock_move_distribution_wiz_form +msgid "Distribute" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields.selection,name:purchase_mrp_distribution.selection__mrp_bom__type__distribution +msgid "Distribution" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move__distribution_bom_id +msgid "Distribution Bom" +msgstr "" + +#. module: purchase_mrp_distribution +#: code:addons/purchase_mrp_distribution/models/stock_move.py:0 +#, python-format +msgid "Distribution for %s" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__id +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__id +msgid "ID" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz____last_update +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__write_uid +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__write_date +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__write_date +msgid "Last Updated on" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__line_ids +msgid "Line" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_stock_move_distribution_wiz_line +msgid "Lines of wizard to distribute product qty to diferent products" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__move_id +msgid "Move" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__product_id +msgid "Product" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_purchase_order_line +msgid "Purchase Order Line" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__quantity +msgid "Quantity" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_stock_move +msgid "Stock Move" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,help:purchase_mrp_distribution.field_stock_move_distribution_wiz__demand_quantity +msgid "" +"This is the quantity of products from an inventory point of view. For moves " +"in the state 'done', this is the quantity of products that were actually " +"moved. For other moves, this is the quantity of product that is planned to " +"be moved. Lowering this quantity does not generate a backorder. Changing " +"this quantity on assigned moves affects the product reservation, and should " +"be done with care." +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz__move_uom_id +msgid "UoM" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__uom_id +msgid "Uom" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model.fields,field_description:purchase_mrp_distribution.field_stock_move_distribution_wiz_line__wizard_id +msgid "Wizard" +msgstr "" + +#. module: purchase_mrp_distribution +#: model:ir.model,name:purchase_mrp_distribution.model_stock_move_distribution_wiz +msgid "Wizard to distribute product qty to diferent products" +msgstr "" diff --git a/purchase_mrp_distribution/models/__init__.py b/purchase_mrp_distribution/models/__init__.py new file mode 100644 index 000000000..8ef1c3daf --- /dev/null +++ b/purchase_mrp_distribution/models/__init__.py @@ -0,0 +1,3 @@ +from . import mrp_bom +from . import purchase +from . import stock_move diff --git a/purchase_mrp_distribution/models/mrp_bom.py b/purchase_mrp_distribution/models/mrp_bom.py new file mode 100644 index 000000000..ba59cb9cf --- /dev/null +++ b/purchase_mrp_distribution/models/mrp_bom.py @@ -0,0 +1,13 @@ +# Copyright 2024 Tecnativa - Carlos Roca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class MrpBom(models.Model): + _inherit = "mrp.bom" + + type = fields.Selection( + selection_add=[("distribution", "Distribution")], + ondelete={"distribution": "set default"}, + ) diff --git a/purchase_mrp_distribution/models/purchase.py b/purchase_mrp_distribution/models/purchase.py new file mode 100644 index 000000000..0bf396ade --- /dev/null +++ b/purchase_mrp_distribution/models/purchase.py @@ -0,0 +1,39 @@ +# Copyright 2024 Tecnativa - Carlos Roca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class PurchaseOrderLine(models.Model): + _inherit = "purchase.order.line" + + def _prepare_stock_move_vals( + self, picking, price_unit, product_uom_qty, product_uom + ): + res = super()._prepare_stock_move_vals( + picking, price_unit, product_uom_qty, product_uom + ) + bom = ( + self.env["mrp.bom"] + .sudo() + ._bom_find( + self.product_id, company_id=self.company_id.id, bom_type="distribution" + )[self.product_id] + ) + if bom: + res["distribution_bom_id"] = bom.id + return res + + def _get_po_line_moves(self): + res = super()._get_po_line_moves() + bom = ( + self.env["mrp.bom"] + .sudo() + ._bom_find( + self.product_id, company_id=self.company_id.id, bom_type="distribution" + )[self.product_id] + ) + res |= self.move_ids.filtered( + lambda m: m.product_id in bom.bom_line_ids.product_id + ) + return res diff --git a/purchase_mrp_distribution/models/stock_move.py b/purchase_mrp_distribution/models/stock_move.py new file mode 100644 index 000000000..0888ec094 --- /dev/null +++ b/purchase_mrp_distribution/models/stock_move.py @@ -0,0 +1,50 @@ +# Copyright 2024 Tecnativa - Carlos Roca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import _, fields, models + + +class StockMove(models.Model): + _inherit = "stock.move" + + distribution_bom_id = fields.Many2one("mrp.bom") + + def action_open_distribution_wizard(self): + return { + "name": _("Distribution for %s") % self.product_id.display_name, + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "stock.move.distribution.wiz", + "target": "new", + "context": {"active_model": "stock.move", "active_id": self.id}, + } + + def _action_done(self, cancel_backorder=False): + distribution_moves = self.filtered(lambda m: m.distribution_bom_id) + new_moves = self.env["stock.move"] + for move in distribution_moves: + cancel_move = False + for sml in move.move_line_ids.filtered( + lambda ml, move=move: ml.product_id != move.product_id + ): + # Create new moves with the corresponding product and the linked sml + new_moves += self.env["stock.move"].create( + { + "picking_id": sml.picking_id.id, + "product_id": sml.product_id.id, + "name": sml.product_id.display_name, + "purchase_line_id": move.purchase_line_id.id, + "product_uom": sml.product_uom_id.id, + "state": "assigned", + "location_id": sml.location_id.id, + "location_dest_id": sml.location_dest_id.id, + "price_unit": move.price_unit, + "move_line_ids": [(4, sml.id)], + } + ) + cancel_move = True + if cancel_move: + move.state = "cancel" + return super(StockMove, self + new_moves)._action_done( + cancel_backorder=cancel_backorder + ) diff --git a/purchase_mrp_distribution/readme/CONFIGURE.rst b/purchase_mrp_distribution/readme/CONFIGURE.rst new file mode 100644 index 000000000..3079b442e --- /dev/null +++ b/purchase_mrp_distribution/readme/CONFIGURE.rst @@ -0,0 +1,9 @@ +To use this module, we will need to configure n different distribution-type Bills of +Materials (BoM) for n products. To do this, we will follow these steps: + +#. Go to Manufacturing > Products > Bills of Materials +#. Click on Create +#. Select the product to be distributed in the receptions +#. Select the BoM type "Distribution" +#. In the components, add the products that can be filled in the reception (it is not + necessary to mark the quantity as it will default to 0) diff --git a/purchase_mrp_distribution/readme/CONTRIBUTORS.rst b/purchase_mrp_distribution/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..42f94d59e --- /dev/null +++ b/purchase_mrp_distribution/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* `Tecnativa `_: + + * Pedro M. Baeza + * Carlos Roca diff --git a/purchase_mrp_distribution/readme/DESCRIPTION.rst b/purchase_mrp_distribution/readme/DESCRIPTION.rst new file mode 100644 index 000000000..aef6ff65a --- /dev/null +++ b/purchase_mrp_distribution/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module allows you to buy a product that will be divided into others without +knowing the exact quantity of the others. That quantity will be filled in the picking. diff --git a/purchase_mrp_distribution/readme/HISTORY.rst b/purchase_mrp_distribution/readme/HISTORY.rst new file mode 100644 index 000000000..5db4cc093 --- /dev/null +++ b/purchase_mrp_distribution/readme/HISTORY.rst @@ -0,0 +1,12 @@ +This module aims to address the need to receive a generic product, but until the +moment of reception, we do not know the quantity that we might receive of its +specific products. + +Using kits was not suitable for us, because to account for a unit of the kit product, +the quantity specified in the BoM must be received. Therefore, we opted to define a +new type of BoM called distribution. + +In this way, upon receiving a unit of any product added to the BoM, it will be +accounted for as a unit of the generic product and will be reflected in the svls as +separate products, but they will be accounted for in the purchase line as part of the +product. diff --git a/purchase_mrp_distribution/readme/ROADMAP.rst b/purchase_mrp_distribution/readme/ROADMAP.rst new file mode 100644 index 000000000..40d31618b --- /dev/null +++ b/purchase_mrp_distribution/readme/ROADMAP.rst @@ -0,0 +1,5 @@ +#. The current module is not designed to handle multi-step receipts. It operates on a + single-step process, making it unsuitable for more complex inventory management + scenarios. +#. The current module is not designed to handle multiple lots for a product. +#. The current module is not designed to handle multiple packages for a product. diff --git a/purchase_mrp_distribution/readme/USAGE.rst b/purchase_mrp_distribution/readme/USAGE.rst new file mode 100644 index 000000000..75de1ea2a --- /dev/null +++ b/purchase_mrp_distribution/readme/USAGE.rst @@ -0,0 +1,10 @@ +To see the module's functionality: + +1. Create a new purchase order +2. Add the product for which the BoM has been created +3. Confirm the order +4. Go to the created delivery order +5. You will see a new button in the move that contains the product, click on it +6. A wizard will open to define the quantity of each product to distribute +7. By clicking on Distribute, the move lines will be added with the products from the + BoM diff --git a/purchase_mrp_distribution/security/ir.model.access.csv b/purchase_mrp_distribution/security/ir.model.access.csv new file mode 100644 index 000000000..5119cda00 --- /dev/null +++ b/purchase_mrp_distribution/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +purchase_mrp_distribution.access_stock_move_distribution_wiz,access_stock_move_distribution_wiz,purchase_mrp_distribution.model_stock_move_distribution_wiz,stock.group_stock_user,1,1,1,1 +purchase_mrp_distribution.access_stock_move_distribution_wiz_line,access_stock_move_distribution_wiz_line,purchase_mrp_distribution.model_stock_move_distribution_wiz_line,stock.group_stock_user,1,1,1,1 diff --git a/purchase_mrp_distribution/static/description/index.html b/purchase_mrp_distribution/static/description/index.html new file mode 100644 index 000000000..f9a0553e8 --- /dev/null +++ b/purchase_mrp_distribution/static/description/index.html @@ -0,0 +1,482 @@ + + + + + +Purchase MRP Distribution + + + +
+

Purchase MRP Distribution

+ + +

Beta License: AGPL-3 OCA/manufacture Translate me on Weblate Try me on Runboat

+

This module allows you to buy a product that will be divided into others without +knowing the exact quantity of the others. That quantity will be filled in the picking.

+

Table of contents

+ +
+

Configuration

+

To use this module, we will need to configure n different distribution-type Bills of +Materials (BoM) for n products. To do this, we will follow these steps:

+
    +
  1. Go to Manufacturing > Products > Bills of Materials
  2. +
  3. Click on Create
  4. +
  5. Select the product to be distributed in the receptions
  6. +
  7. Select the BoM type “Distribution”
  8. +
  9. In the components, add the products that can be filled in the reception (it is not +necessary to mark the quantity as it will default to 0)
  10. +
+
+
+

Usage

+

To see the module’s functionality:

+
    +
  1. Create a new purchase order
  2. +
  3. Add the product for which the BoM has been created
  4. +
  5. Confirm the order
  6. +
  7. Go to the created delivery order
  8. +
  9. You will see a new button in the move that contains the product, click on it
  10. +
  11. A wizard will open to define the quantity of each product to distribute
  12. +
  13. By clicking on Distribute, the move lines will be added with the products from the +BoM
  14. +
+
+
+

Known issues / Roadmap

+
    +
  1. The current module is not designed to handle multi-step receipts. It operates on a +single-step process, making it unsuitable for more complex inventory management +scenarios.
  2. +
  3. The current module is not designed to handle multiple lots for a product.
  4. +
  5. The current module is not designed to handle multiple packages for a product.
  6. +
+
+
+

Changelog

+

This module aims to address the need to receive a generic product, but until the +moment of reception, we do not know the quantity that we might receive of its +specific products.

+

Using kits was not suitable for us, because to account for a unit of the kit product, +the quantity specified in the BoM must be received. Therefore, we opted to define a +new type of BoM called distribution.

+

In this way, upon receiving a unit of any product added to the BoM, it will be +accounted for as a unit of the generic product and will be reflected in the svls as +separate products, but they will be accounted for in the purchase line as part of the +product.

+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnaiva
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:
      +
    • Pedro M. Baeza
    • +
    • Carlos Roca
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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

+

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

+

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

+
+
+
+ + diff --git a/purchase_mrp_distribution/tests/__init__.py b/purchase_mrp_distribution/tests/__init__.py new file mode 100644 index 000000000..a914df78f --- /dev/null +++ b/purchase_mrp_distribution/tests/__init__.py @@ -0,0 +1 @@ +from . import test_purchase_mrp_distribution diff --git a/purchase_mrp_distribution/tests/test_purchase_mrp_distribution.py b/purchase_mrp_distribution/tests/test_purchase_mrp_distribution.py new file mode 100644 index 000000000..012a60611 --- /dev/null +++ b/purchase_mrp_distribution/tests/test_purchase_mrp_distribution.py @@ -0,0 +1,105 @@ +# Copyright 2024 Tecnativa - Carlos Roca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import Form, TransactionCase + + +class TestPurchaseMrpDistribution(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.category = cls.env["product.category"].create( + { + "name": "AVCO", + "property_cost_method": "average", + } + ) + cls.product = cls.env["product.template"].create( + { + "name": "General product", + "categ_id": cls.category.id, + } + ) + cls.subproduct_1 = cls.env["product.template"].create( + { + "name": "Product 1", + "categ_id": cls.category.id, + } + ) + cls.subproduct_2 = cls.env["product.template"].create( + { + "name": "Product 2", + "categ_id": cls.category.id, + } + ) + cls.bom = cls.env["mrp.bom"].create( + { + "product_tmpl_id": cls.product.id, + "type": "distribution", + "bom_line_ids": [ + ( + 0, + 0, + { + "product_id": cls.subproduct_1.product_variant_id.id, + }, + ), + ( + 0, + 0, + { + "product_id": cls.subproduct_2.product_variant_id.id, + }, + ), + ], + } + ) + cls.partner = cls.env["res.partner"].create( + { + "name": "Partner", + } + ) + + def _create_po_picking(self): + purchase_form = Form(self.env["purchase.order"]) + purchase_form.partner_id = self.partner + with purchase_form.order_line.new() as line_form: + line_form.product_id = self.product.product_variant_id + line_form.product_qty = 6 + line_form.price_unit = 2 + purchase = purchase_form.save() + purchase.button_confirm() + return purchase, purchase.picking_ids + + def test_distribute_process(self): + purchase, picking = self._create_po_picking() + wiz_action = picking.move_lines.action_open_distribution_wizard() + wiz = ( + self.env["stock.move.distribution.wiz"] + .with_context(**wiz_action["context"]) + .create({}) + ) + self.assertIn(self.subproduct_1.product_variant_id, wiz.line_ids.product_id) + self.assertIn(self.subproduct_2.product_variant_id, wiz.line_ids.product_id) + wiz.line_ids.quantity = 3 + wiz.button_distribute_qty() + self.assertEqual(len(picking.move_lines), 1) + self.assertEqual(len(picking.move_lines.move_line_ids), 2) + for sml in picking.move_lines.move_line_ids: + self.assertEqual(sml.qty_done, 3) + original_move = picking.move_lines + picking.button_validate() + self.assertEqual(len(picking.move_lines), 3) + self.assertEqual(original_move.state, "cancel") + self.assertIn( + self.subproduct_1.product_variant_id, picking.move_lines.product_id + ) + self.assertIn( + self.subproduct_2.product_variant_id, picking.move_lines.product_id + ) + self.assertEqual(purchase.order_line.qty_received, 6) + svl_action = picking.action_view_stock_valuation_layers() + svls = self.env["stock.valuation.layer"].search(svl_action["domain"]) + for svl in svls: + self.assertEqual(svl.quantity, 3) + self.assertEqual(svl.value, 6) diff --git a/purchase_mrp_distribution/views/mrp_bom_views.xml b/purchase_mrp_distribution/views/mrp_bom_views.xml new file mode 100644 index 000000000..e2ae5681d --- /dev/null +++ b/purchase_mrp_distribution/views/mrp_bom_views.xml @@ -0,0 +1,23 @@ + + + + mrp.bom + + + +

+ A BoM of type distribution is used to select the quantities of components. +

    +
  • + At a Stock Transfer. +
  • +
+

+
+
+
+
diff --git a/purchase_mrp_distribution/views/stock_picking_views.xml b/purchase_mrp_distribution/views/stock_picking_views.xml new file mode 100644 index 000000000..b563ef7c0 --- /dev/null +++ b/purchase_mrp_distribution/views/stock_picking_views.xml @@ -0,0 +1,21 @@ + + + + stock.picking + + + + +