From 4655be5be58a68dd7baf45f52a6bcb62319cc7b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Tue, 4 May 2021 21:26:51 +0200 Subject: [PATCH 1/8] s_r_s_mrp_subcontracting: compatibility layer New module to make compatible the reception screen with the 'mrp_subcontracting' module. With the reception screen we allowed to receive goods move by move (each time validated), and this new module makes this flow compatible with subcontracted goods. --- .../__init__.py | 2 + .../__manifest__.py | 20 +++++ .../models/__init__.py | 2 + .../models/stock_move_line.py | 15 ++++ .../models/stock_reception_screen.py | 55 ++++++++++++ .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 2 + .../tests/__init__.py | 1 + .../tests/test_reception_screen.py | 86 +++++++++++++++++++ .../wizards/__init__.py | 1 + .../wizards/mrp_product_produce.py | 24 ++++++ 11 files changed, 209 insertions(+) create mode 100644 stock_reception_screen_mrp_subcontracting/__init__.py create mode 100644 stock_reception_screen_mrp_subcontracting/__manifest__.py create mode 100644 stock_reception_screen_mrp_subcontracting/models/__init__.py create mode 100644 stock_reception_screen_mrp_subcontracting/models/stock_move_line.py create mode 100644 stock_reception_screen_mrp_subcontracting/models/stock_reception_screen.py create mode 100644 stock_reception_screen_mrp_subcontracting/readme/CONTRIBUTORS.rst create mode 100644 stock_reception_screen_mrp_subcontracting/readme/DESCRIPTION.rst create mode 100644 stock_reception_screen_mrp_subcontracting/tests/__init__.py create mode 100644 stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py create mode 100644 stock_reception_screen_mrp_subcontracting/wizards/__init__.py create mode 100644 stock_reception_screen_mrp_subcontracting/wizards/mrp_product_produce.py diff --git a/stock_reception_screen_mrp_subcontracting/__init__.py b/stock_reception_screen_mrp_subcontracting/__init__.py new file mode 100644 index 00000000000..aee8895e7a3 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/stock_reception_screen_mrp_subcontracting/__manifest__.py b/stock_reception_screen_mrp_subcontracting/__manifest__.py new file mode 100644 index 00000000000..98dc2746a6b --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +{ + "name": "Stock - Reception screen (Subcontract Productions integration)", + "summary": "Reception screen integrated with subcontracted productions.", + "version": "13.0.1.0.0", + "category": "Stock", + "license": "AGPL-3", + "author": "Camptocamp, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/wms", + "depends": [ + "mrp_subcontracting", + # OCA/wms + "stock_reception_screen", + ], + "data": [], + "installable": True, + "auto_install": True, + "development_status": "Alpha", +} diff --git a/stock_reception_screen_mrp_subcontracting/models/__init__.py b/stock_reception_screen_mrp_subcontracting/models/__init__.py new file mode 100644 index 00000000000..0b87f758b13 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/models/__init__.py @@ -0,0 +1,2 @@ +from . import stock_reception_screen +from . import stock_move_line diff --git a/stock_reception_screen_mrp_subcontracting/models/stock_move_line.py b/stock_reception_screen_mrp_subcontracting/models/stock_move_line.py new file mode 100644 index 00000000000..a4c4f214fac --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/models/stock_move_line.py @@ -0,0 +1,15 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class StockMoveLine(models.Model): + _inherit = "stock.move.line" + + def unlink(self): + # Overridden to not unlink lines of finished products moves + # if the context key 'subcontracting_skip_unlink' is set + if self.env.context.get("subcontracting_skip_unlink"): + return False + return super().unlink() diff --git a/stock_reception_screen_mrp_subcontracting/models/stock_reception_screen.py b/stock_reception_screen_mrp_subcontracting/models/stock_reception_screen.py new file mode 100644 index 00000000000..f4aeb6f1cb4 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/models/stock_reception_screen.py @@ -0,0 +1,55 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class StockReceptionScreen(models.Model): + _inherit = "stock.reception.screen" + + def _split_move(self, move, qty): + # Overridden to remove the link between the move of finished product + # and the move to receive to ease the split + move_is_subcontract = move.is_subcontract + move_move_orig = move.move_orig_ids + if move_is_subcontract: + move.move_orig_ids = False + new_move_id = super()._split_move(move, qty) + if move_is_subcontract: + new_move = move.browse(new_move_id) + new_move.move_orig_ids = move_move_orig + move.move_orig_ids = move_move_orig + return new_move_id + + def _validate_current_move(self): + # Overridden to automatically recheck availability each time a move + # is validated to reserve qties from the production for remaining moves + res = super()._validate_current_move() + if self.current_move_id.is_subcontract: + if self.picking_id.state not in ("cancel", "done"): + self.picking_id.action_assign() + return res + + def _action_done_move(self, move): + if move.is_subcontract: + move = move.with_context(cancel_backorder=False) + res = super()._action_done_move(move) + return res + + def _action_done_picking(self): + # Overridden to unlink existing move lines of finished products + # as it is done in 'picking.action_done' in module 'mrp_subcontracting', + # but here we do this only one time and not each time we iterate on + # received move (which removes each time the move line created by the + # previous received move, as in the context of reception screen we + # could have received the goods in several moves while having only one + # finished product move) + for move in self.picking_id.move_lines: + if not move.is_subcontract: + continue + if move._has_tracked_subcontract_components(): + move.move_orig_ids.filtered( + lambda m: m.state not in ("done", "cancel") + ).move_line_ids.unlink() + self = self.with_context(subcontracting_skip_unlink=True) + return super()._action_done_picking() diff --git a/stock_reception_screen_mrp_subcontracting/readme/CONTRIBUTORS.rst b/stock_reception_screen_mrp_subcontracting/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..c452804a90b --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Sébastien Alix diff --git a/stock_reception_screen_mrp_subcontracting/readme/DESCRIPTION.rst b/stock_reception_screen_mrp_subcontracting/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..cfd4c3b6792 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module makes compatible the reception screen flow with subcontracted goods +(`mrp_subcontracting` module). diff --git a/stock_reception_screen_mrp_subcontracting/tests/__init__.py b/stock_reception_screen_mrp_subcontracting/tests/__init__.py new file mode 100644 index 00000000000..92d2e3f5386 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/tests/__init__.py @@ -0,0 +1 @@ +from . import test_reception_screen diff --git a/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py b/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py new file mode 100644 index 00000000000..5a5b6b5f694 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py @@ -0,0 +1,86 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields + +from odoo.addons.stock_reception_screen.tests.common import Common + + +class TestReceptionScreenMrpSubcontracting(Common): + @classmethod + def setUpClass(cls): + super().setUpClass() + # product.product_delivery_02 has a subcontracted BoM defined with + # 'mrp_subcontracting' installed + cls.picking = cls._create_picking_in(partner=cls.env.ref("base.res_partner_12")) + cls._create_picking_line(cls.picking, cls.product_2, 4) + cls.picking.action_confirm() + cls.picking.action_reception_screen_open() + cls.screen = cls.picking.reception_screen_id + + def test_reception_screen_with_subcontracted_products(self): + # Select the product to receive + self.assertEqual(self.screen.current_step, "select_product") + move = fields.first(self.screen.picking_filtered_move_lines) + move.action_select_product() + # Receive 2/4 qties (corresponding to the product packaging qty) + self.assertEqual(self.screen.current_step, "set_quantity") + self.screen.current_move_line_qty_done = 2 + self.assertEqual(self.screen.current_move_line_qty_status, "lt") + # Check package data (automatically filled normally) + self.screen.button_save_step() + self.assertEqual(self.screen.current_step, "select_packaging") + self.assertEqual(self.screen.product_packaging_id, self.product_2_packaging) + self.assertEqual(self.screen.package_storage_type_id, self.storage_type_pallet) + self.assertEqual(self.screen.package_height, self.product_2_packaging.height) + # Check that a destination location is defined by default + self.screen.button_save_step() + self.assertEqual(self.screen.current_step, "set_location") + self.screen.current_move_line_location_dest_stored_id = self.location_dest + self.screen.button_save_step() + self.assertEqual( + self.screen.current_move_line_location_dest_id, + self.screen.current_move_line_location_dest_stored_id, + ) + # Set a package + self.assertEqual(self.screen.current_step, "set_package") + self.screen.current_move_line_package = "PID-TEST-1" + self.assertEqual(self.screen.current_move_line_package_stored, "PID-TEST-1") + move_line = self.screen.current_move_line_id + self.assertFalse(move_line.result_package_id) + self.assertEqual(len(self.picking.move_lines), 1) + self.screen.button_save_step() + self.assertEqual(move_line.result_package_id.name, "PID-TEST-1") + # The first 2 qties should be validated, creating a 2nd move to process + self.assertEqual(len(self.picking.move_lines), 2) + self.screen.button_save_step() + self.assertEqual(self.screen.current_step, "set_quantity") + self.screen.current_move_line_qty_done = 2 + self.assertEqual(self.screen.current_move_line_qty_status, "eq") + self.screen.button_save_step() + # Check package data (automatically filled as before) + self.assertEqual(self.screen.current_step, "select_packaging") + self.assertEqual(self.screen.product_packaging_id, self.product_2_packaging) + self.assertEqual(self.screen.package_storage_type_id, self.storage_type_pallet) + self.assertEqual(self.screen.package_height, self.product_2_packaging.height) + self.screen.button_save_step() + # Set location + self.assertEqual(self.screen.current_step, "set_location") + self.screen.current_move_line_location_dest_stored_id = self.location_dest + # Set a package + self.screen.button_save_step() + self.assertEqual(self.screen.current_step, "set_package") + self.screen.current_move_line_package = "PID-TEST-2" + self.assertEqual(self.screen.current_move_line_package_stored, "PID-TEST-2") + move_line = self.screen.current_move_line_id + self.assertFalse(move_line.result_package_id) + self.screen.button_save_step() # Reception done + self.assertEqual(move_line.result_package_id.name, "PID-TEST-2") + # Check that the manufacturing order is now done + production = self.picking.move_lines.move_orig_ids.production_id + self.assertEqual(production.state, "done") + self.assertEqual(production.move_finished_ids.product_uom_qty, 4) + self.assertEqual(production.move_finished_ids.quantity_done, 4) + self.assertEqual(production.move_finished_ids.state, "done") + for ml in production.finished_move_line_ids: + self.assertEqual(ml.qty_done, 2) diff --git a/stock_reception_screen_mrp_subcontracting/wizards/__init__.py b/stock_reception_screen_mrp_subcontracting/wizards/__init__.py new file mode 100644 index 00000000000..21600358964 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/wizards/__init__.py @@ -0,0 +1 @@ +from . import mrp_product_produce diff --git a/stock_reception_screen_mrp_subcontracting/wizards/mrp_product_produce.py b/stock_reception_screen_mrp_subcontracting/wizards/mrp_product_produce.py new file mode 100644 index 00000000000..37bd987a6c8 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/wizards/mrp_product_produce.py @@ -0,0 +1,24 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class MrpProductProduce(models.TransientModel): + _inherit = "mrp.product.produce" + + def _record_production(self): + # Overridden to unlink existing move lines when the components + # consumption is recorded. As a result we will get only new move lines + # related to the production, avoiding issues when processing them with + # the reception screen. + dest_moves = self.move_finished_ids.move_dest_ids.filtered( + lambda m: m.state not in ("done", "cancel") + ) + incoming = "incoming" in dest_moves.mapped("picking_code") + if incoming: + dest_moves._do_unreserve() + res = super()._record_production() + if incoming: + dest_moves._action_assign() + return res From bd6dbbb98e510fe26f9e2ffe3a8d715cc78eb997 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Fri, 11 Jun 2021 08:45:52 +0000 Subject: [PATCH 2/8] [UPD] Update stock_reception_screen_mrp_subcontracting.pot --- ...ck_reception_screen_mrp_subcontracting.pot | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 stock_reception_screen_mrp_subcontracting/i18n/stock_reception_screen_mrp_subcontracting.pot diff --git a/stock_reception_screen_mrp_subcontracting/i18n/stock_reception_screen_mrp_subcontracting.pot b/stock_reception_screen_mrp_subcontracting/i18n/stock_reception_screen_mrp_subcontracting.pot new file mode 100644 index 00000000000..a68d72a72cd --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/i18n/stock_reception_screen_mrp_subcontracting.pot @@ -0,0 +1,34 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_reception_screen_mrp_subcontracting +# +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: stock_reception_screen_mrp_subcontracting +#: model:ir.model,name:stock_reception_screen_mrp_subcontracting.model_stock_move_line +msgid "Product Moves (Stock Move Line)" +msgstr "" + +#. module: stock_reception_screen_mrp_subcontracting +#: model:ir.model,name:stock_reception_screen_mrp_subcontracting.model_mrp_product_produce +msgid "Record Production" +msgstr "" + +#. module: stock_reception_screen_mrp_subcontracting +#: model:ir.model,name:stock_reception_screen_mrp_subcontracting.model_stock_move +msgid "Stock Move" +msgstr "" + +#. module: stock_reception_screen_mrp_subcontracting +#: model:ir.model,name:stock_reception_screen_mrp_subcontracting.model_stock_reception_screen +msgid "Stock Reception Screen" +msgstr "" From 8a6c5f2392d0d61054d5a53c376b439792a6933a Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 11 Jun 2021 09:15:55 +0000 Subject: [PATCH 3/8] [UPD] README.rst --- .../README.rst | 79 ++++ .../static/description/index.html | 426 ++++++++++++++++++ 2 files changed, 505 insertions(+) create mode 100644 stock_reception_screen_mrp_subcontracting/README.rst create mode 100644 stock_reception_screen_mrp_subcontracting/static/description/index.html diff --git a/stock_reception_screen_mrp_subcontracting/README.rst b/stock_reception_screen_mrp_subcontracting/README.rst new file mode 100644 index 00000000000..1b256964e47 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/README.rst @@ -0,0 +1,79 @@ +============================================================== +Stock - Reception screen (Subcontract Productions integration) +============================================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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%2Fwms-lightgray.png?logo=github + :target: https://github.com/OCA/wms/tree/13.0/stock_reception_screen_mrp_subcontracting + :alt: OCA/wms +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/wms-13-0/wms-13-0-stock_reception_screen_mrp_subcontracting + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/285/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module makes compatible the reception screen flow with subcontracted goods +(`mrp_subcontracting` module). + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +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 +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Sébastien Alix + +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/wms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_reception_screen_mrp_subcontracting/static/description/index.html b/stock_reception_screen_mrp_subcontracting/static/description/index.html new file mode 100644 index 00000000000..6bfcc28a663 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/static/description/index.html @@ -0,0 +1,426 @@ + + + + + + +Stock - Reception screen (Subcontract Productions integration) + + + +
+

Stock - Reception screen (Subcontract Productions integration)

+ + +

Alpha License: AGPL-3 OCA/wms Translate me on Weblate Try me on Runbot

+

This module makes compatible the reception screen flow with subcontracted goods +(mrp_subcontracting module).

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

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

+
    +
  • Camptocamp
  • +
+
+
+

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

+

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

+
+
+
+ + From 6f1f9e27cd3fa8b7eb902ab203c00df207e1a879 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 11 Jun 2021 09:15:55 +0000 Subject: [PATCH 4/8] [ADD] icon.png --- .../static/description/icon.png | Bin 0 -> 9455 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 stock_reception_screen_mrp_subcontracting/static/description/icon.png diff --git a/stock_reception_screen_mrp_subcontracting/static/description/icon.png b/stock_reception_screen_mrp_subcontracting/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zl&#s4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6# z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f# zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv! zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG z-wfS zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9 z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE# z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1 zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$ zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV( z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4 z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{ zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx} z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22 zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t< z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{} zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 From 4767e8be698f87ff0a7bd8c5632b9042f2e30bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Tue, 20 Jul 2021 13:43:39 +0200 Subject: [PATCH 5/8] s_r_screen: subcontracting fix with components tracked by lot When receiving goods with the reception screen we are able to process a partial quantity, splitting the current move and validating it on its own. By convenience, the stock.move.line related to the finished moves (in the production order) are removed and recreated (by duplicating move lines received) when the reception is validated. This last part has been rewritten in 'stock_reception_screen_mrp_subcontracting' module to satisfy a technical need for the reception screen, but this was causing an issue when receiving subcontracted goods with components tracked by lots. The fix here is to disable the blocking check which compares the produced qty of the production order (which is 0 because all produced move lines have been removed) to the qty received on the move when the reception is validated by the reception screen. --- .../models/__init__.py | 1 + .../models/stock_move.py | 23 ++++ .../tests/test_reception_screen.py | 103 ++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 stock_reception_screen_mrp_subcontracting/models/stock_move.py diff --git a/stock_reception_screen_mrp_subcontracting/models/__init__.py b/stock_reception_screen_mrp_subcontracting/models/__init__.py index 0b87f758b13..703238ea53f 100644 --- a/stock_reception_screen_mrp_subcontracting/models/__init__.py +++ b/stock_reception_screen_mrp_subcontracting/models/__init__.py @@ -1,2 +1,3 @@ from . import stock_reception_screen from . import stock_move_line +from . import stock_move diff --git a/stock_reception_screen_mrp_subcontracting/models/stock_move.py b/stock_reception_screen_mrp_subcontracting/models/stock_move.py new file mode 100644 index 00000000000..39121eb62f2 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/models/stock_move.py @@ -0,0 +1,23 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class StockMove(models.Model): + _inherit = "stock.move" + + def _check_overprocessed_subcontract_qty(self): + # Disable the check if finished move lines have been removed by the + # `_action_done_picking` method of the reception screen (resetting the + # 'qty_produced' computed field of the production order to 0). Indeed, + # these move lines are automatically recreated by the 'mrp_subcontracting' + # module in the picking 'action_done()' override by duplicating received + # move lines, so the production order will get its 'qty_produced' correct + # afterwards anyway. + # We know that we come from a validation of the reception screen thanks + # to the 'subcontracting_skip_unlink' context key defined in + # '_action_done_picking' method of the reception screen. + if self.env.context.get("subcontracting_skip_unlink"): + return False + return super()._check_overprocessed_subcontract_qty() diff --git a/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py b/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py index 5a5b6b5f694..3afa8217306 100644 --- a/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py +++ b/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py @@ -10,6 +10,10 @@ class TestReceptionScreenMrpSubcontracting(Common): @classmethod def setUpClass(cls): super().setUpClass() + cls._prepare_reception_screen() + + @classmethod + def _prepare_reception_screen(cls): # product.product_delivery_02 has a subcontracted BoM defined with # 'mrp_subcontracting' installed cls.picking = cls._create_picking_in(partner=cls.env.ref("base.res_partner_12")) @@ -18,7 +22,106 @@ def setUpClass(cls): cls.picking.action_reception_screen_open() cls.screen = cls.picking.reception_screen_id + def _record_components_for_picking(self, picking): + for move in picking.move_lines: + if not move.is_subcontract: + continue + context = dict( + default_production_id=move.move_orig_ids.production_id.id, + default_subcontract_move_id=move.id, + ) + wiz_model = self.env["mrp.product.produce"].with_context(context) + wiz = wiz_model.create({}) + for line in wiz.move_raw_ids.move_line_ids: + line.qty_done = line.product_uom_qty + wiz.do_produce() + def test_reception_screen_with_subcontracted_products(self): + """Receive subcontracted goods.""" + # Select the product to receive + self.assertEqual(self.screen.current_step, "select_product") + move = fields.first(self.screen.picking_filtered_move_lines) + move.action_select_product() + # Receive 2/4 qties (corresponding to the product packaging qty) + self.assertEqual(self.screen.current_step, "set_quantity") + self.screen.current_move_line_qty_done = 2 + self.assertEqual(self.screen.current_move_line_qty_status, "lt") + # Check package data (automatically filled normally) + self.screen.button_save_step() + self.assertEqual(self.screen.current_step, "select_packaging") + self.assertEqual(self.screen.product_packaging_id, self.product_2_packaging) + self.assertEqual(self.screen.package_storage_type_id, self.storage_type_pallet) + self.assertEqual(self.screen.package_height, self.product_2_packaging.height) + # Check that a destination location is defined by default + self.screen.button_save_step() + self.assertEqual(self.screen.current_step, "set_location") + self.screen.current_move_line_location_dest_stored_id = self.location_dest + self.screen.button_save_step() + self.assertEqual( + self.screen.current_move_line_location_dest_id, + self.screen.current_move_line_location_dest_stored_id, + ) + # Set a package + self.assertEqual(self.screen.current_step, "set_package") + self.screen.current_move_line_package = "PID-TEST-1" + self.assertEqual(self.screen.current_move_line_package_stored, "PID-TEST-1") + move_line = self.screen.current_move_line_id + self.assertFalse(move_line.result_package_id) + self.assertEqual(len(self.picking.move_lines), 1) + self.screen.button_save_step() + self.assertEqual(move_line.result_package_id.name, "PID-TEST-1") + # The first 2 qties should be validated, creating a 2nd move to process + self.assertEqual(len(self.picking.move_lines), 2) + self.screen.button_save_step() + self.assertEqual(self.screen.current_step, "set_quantity") + self.screen.current_move_line_qty_done = 2 + self.assertEqual(self.screen.current_move_line_qty_status, "eq") + self.screen.button_save_step() + # Check package data (automatically filled as before) + self.assertEqual(self.screen.current_step, "select_packaging") + self.assertEqual(self.screen.product_packaging_id, self.product_2_packaging) + self.assertEqual(self.screen.package_storage_type_id, self.storage_type_pallet) + self.assertEqual(self.screen.package_height, self.product_2_packaging.height) + self.screen.button_save_step() + # Set location + self.assertEqual(self.screen.current_step, "set_location") + self.screen.current_move_line_location_dest_stored_id = self.location_dest + # Set a package + self.screen.button_save_step() + self.assertEqual(self.screen.current_step, "set_package") + self.screen.current_move_line_package = "PID-TEST-2" + self.assertEqual(self.screen.current_move_line_package_stored, "PID-TEST-2") + move_line = self.screen.current_move_line_id + self.assertFalse(move_line.result_package_id) + self.screen.button_save_step() # Reception done + self.assertEqual(move_line.result_package_id.name, "PID-TEST-2") + # Check that the manufacturing order is now done + production = self.picking.move_lines.move_orig_ids.production_id + self.assertEqual(production.state, "done") + self.assertEqual(production.move_finished_ids.product_uom_qty, 4) + self.assertEqual(production.move_finished_ids.quantity_done, 4) + self.assertEqual(production.move_finished_ids.state, "done") + for ml in production.finished_move_line_ids: + self.assertEqual(ml.qty_done, 2) + + def test_reception_screen_with_subcontracted_products_with_tracked_components(self): + """Receive subcontracted goods having components tracked by lots.""" + # Update the existing BoM to have a component tracked by lot + bom_line = self.product_2.bom_ids.bom_line_ids + component = bom_line.product_id.copy({"type": "product", "tracking": "lot"}) + bom_line.product_id = component + component_lot = self.env["stock.production.lot"].create( + {"product_id": component.id, "company_id": self.picking.company_id.id} + ) + production = self.picking.move_lines.move_orig_ids.production_id + self.env["stock.quant"]._update_available_quantity( + component, production.move_raw_ids.location_id, 4, lot_id=component_lot + ) + # Prepare a new reception + self.picking.action_cancel() + self._prepare_reception_screen() + # Record components (mandatory) + self._record_components_for_picking(self.picking) # Select the product to receive self.assertEqual(self.screen.current_step, "select_product") move = fields.first(self.screen.picking_filtered_move_lines) From 3634e37c6f2a5d687c839eca483c2f0288ec22be Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 3 Aug 2021 09:28:06 +0000 Subject: [PATCH 6/8] stock_reception_screen_mrp_subcontracting 13.0.1.0.1 --- stock_reception_screen_mrp_subcontracting/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_reception_screen_mrp_subcontracting/__manifest__.py b/stock_reception_screen_mrp_subcontracting/__manifest__.py index 98dc2746a6b..9f57a154b84 100644 --- a/stock_reception_screen_mrp_subcontracting/__manifest__.py +++ b/stock_reception_screen_mrp_subcontracting/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock - Reception screen (Subcontract Productions integration)", "summary": "Reception screen integrated with subcontracted productions.", - "version": "13.0.1.0.0", + "version": "13.0.1.0.1", "category": "Stock", "license": "AGPL-3", "author": "Camptocamp, Odoo Community Association (OCA)", From 90f52b3a7b052e1f5bbd9b47e1a4d2d66e6b0bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Tue, 18 Jan 2022 11:00:07 +0100 Subject: [PATCH 7/8] [MIG] s_r_s_mrp_subcontracting: Migrate to 14.0 The subcontracting implementation has changed a bit in 14.0, like the wizard 'mrp.product.produce' that has been remove, replaced directly by the 'mrp.production' model + other changes. --- .../stock_reception_screen_mrp_subcontracting | 1 + .../setup.py | 6 ++ .../__manifest__.py | 2 +- .../models/__init__.py | 4 +- .../models/mrp_production.py | 22 ++++++ .../models/stock_move.py | 23 ------ .../models/stock_move_line.py | 15 ---- .../models/stock_picking.py | 75 +++++++++++++++++++ .../models/stock_reception_screen.py | 38 +++------- .../tests/common.py | 31 ++++++++ .../tests/test_reception_screen.py | 36 +-------- .../wizards/__init__.py | 2 +- .../wizards/change_production_qty.py | 15 ++++ .../wizards/mrp_product_produce.py | 24 ------ 14 files changed, 167 insertions(+), 127 deletions(-) create mode 120000 setup/stock_reception_screen_mrp_subcontracting/odoo/addons/stock_reception_screen_mrp_subcontracting create mode 100644 setup/stock_reception_screen_mrp_subcontracting/setup.py create mode 100644 stock_reception_screen_mrp_subcontracting/models/mrp_production.py delete mode 100644 stock_reception_screen_mrp_subcontracting/models/stock_move.py delete mode 100644 stock_reception_screen_mrp_subcontracting/models/stock_move_line.py create mode 100644 stock_reception_screen_mrp_subcontracting/models/stock_picking.py create mode 100644 stock_reception_screen_mrp_subcontracting/tests/common.py create mode 100644 stock_reception_screen_mrp_subcontracting/wizards/change_production_qty.py delete mode 100644 stock_reception_screen_mrp_subcontracting/wizards/mrp_product_produce.py diff --git a/setup/stock_reception_screen_mrp_subcontracting/odoo/addons/stock_reception_screen_mrp_subcontracting b/setup/stock_reception_screen_mrp_subcontracting/odoo/addons/stock_reception_screen_mrp_subcontracting new file mode 120000 index 00000000000..dd0eaf3072f --- /dev/null +++ b/setup/stock_reception_screen_mrp_subcontracting/odoo/addons/stock_reception_screen_mrp_subcontracting @@ -0,0 +1 @@ +../../../../stock_reception_screen_mrp_subcontracting \ No newline at end of file diff --git a/setup/stock_reception_screen_mrp_subcontracting/setup.py b/setup/stock_reception_screen_mrp_subcontracting/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/stock_reception_screen_mrp_subcontracting/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_reception_screen_mrp_subcontracting/__manifest__.py b/stock_reception_screen_mrp_subcontracting/__manifest__.py index 9f57a154b84..464e7937a4c 100644 --- a/stock_reception_screen_mrp_subcontracting/__manifest__.py +++ b/stock_reception_screen_mrp_subcontracting/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock - Reception screen (Subcontract Productions integration)", "summary": "Reception screen integrated with subcontracted productions.", - "version": "13.0.1.0.1", + "version": "14.0.1.0.0", "category": "Stock", "license": "AGPL-3", "author": "Camptocamp, Odoo Community Association (OCA)", diff --git a/stock_reception_screen_mrp_subcontracting/models/__init__.py b/stock_reception_screen_mrp_subcontracting/models/__init__.py index 703238ea53f..9e5de6cc6ff 100644 --- a/stock_reception_screen_mrp_subcontracting/models/__init__.py +++ b/stock_reception_screen_mrp_subcontracting/models/__init__.py @@ -1,3 +1,3 @@ +from . import mrp_production +from . import stock_picking from . import stock_reception_screen -from . import stock_move_line -from . import stock_move diff --git a/stock_reception_screen_mrp_subcontracting/models/mrp_production.py b/stock_reception_screen_mrp_subcontracting/models/mrp_production.py new file mode 100644 index 00000000000..7e35685222b --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/models/mrp_production.py @@ -0,0 +1,22 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class MrpProduction(models.Model): + _inherit = "mrp.production" + + def _set_qty_producing(self): + # Inhibited during the call of 'stock.picking._action_done()' of + # 'mrp_subcontracting' addon. + if self.env.context.get("subcontracting_skip_action_done"): + return False + return super()._set_qty_producing() + + def button_mark_done(self): + # Inhibited during the call of 'stock.picking._action_done()' of + # 'mrp_subcontracting' addon. + if self.env.context.get("subcontracting_skip_action_done"): + return False + return super().button_mark_done() diff --git a/stock_reception_screen_mrp_subcontracting/models/stock_move.py b/stock_reception_screen_mrp_subcontracting/models/stock_move.py deleted file mode 100644 index 39121eb62f2..00000000000 --- a/stock_reception_screen_mrp_subcontracting/models/stock_move.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2021 Camptocamp SA -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) - -from odoo import models - - -class StockMove(models.Model): - _inherit = "stock.move" - - def _check_overprocessed_subcontract_qty(self): - # Disable the check if finished move lines have been removed by the - # `_action_done_picking` method of the reception screen (resetting the - # 'qty_produced' computed field of the production order to 0). Indeed, - # these move lines are automatically recreated by the 'mrp_subcontracting' - # module in the picking 'action_done()' override by duplicating received - # move lines, so the production order will get its 'qty_produced' correct - # afterwards anyway. - # We know that we come from a validation of the reception screen thanks - # to the 'subcontracting_skip_unlink' context key defined in - # '_action_done_picking' method of the reception screen. - if self.env.context.get("subcontracting_skip_unlink"): - return False - return super()._check_overprocessed_subcontract_qty() diff --git a/stock_reception_screen_mrp_subcontracting/models/stock_move_line.py b/stock_reception_screen_mrp_subcontracting/models/stock_move_line.py deleted file mode 100644 index a4c4f214fac..00000000000 --- a/stock_reception_screen_mrp_subcontracting/models/stock_move_line.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2021 Camptocamp SA -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) - -from odoo import models - - -class StockMoveLine(models.Model): - _inherit = "stock.move.line" - - def unlink(self): - # Overridden to not unlink lines of finished products moves - # if the context key 'subcontracting_skip_unlink' is set - if self.env.context.get("subcontracting_skip_unlink"): - return False - return super().unlink() diff --git a/stock_reception_screen_mrp_subcontracting/models/stock_picking.py b/stock_reception_screen_mrp_subcontracting/models/stock_picking.py new file mode 100644 index 00000000000..4ad61d2a303 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/models/stock_picking.py @@ -0,0 +1,75 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models +from odoo.tools import float_compare + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + def _action_done(self): + # In std Odoo we are not supposed to split & validate moves + # independently in only one receipt, and Odoo assumes that there is + # only one move corresponding to a MO, so if we receive more qty than + # the expected one, Odoo will automatically update the MO produced qty + # with the quantity done of the move. + # But with the reception screen, as several moves could be created from + # the original one, we need to update this produced qty by grouping all + # moves sharing the same MO. + # Here we disable some std behavior with the 'subcontracting_skip_action_done' + # context key and we rewrite the business logic. + new_self = self + if any(picking._is_subcontract() for picking in self): + new_self = self.with_context(subcontracting_skip_action_done=True) + res = super(StockPicking, new_self)._action_done() + for picking in self.filtered(lambda p: p._is_subcontract()): + productions = picking.move_lines.move_orig_ids.production_id.filtered( + lambda p: p.state not in ("done", "cancel") + )[-1:] + for production in productions: + # Update the qty to produce as it is done in 'mrp_subcontracting' + # but we group all received moves sharing the same MO here. + moves = production.move_finished_ids.move_dest_ids.filtered( + lambda m: ( + m.picking_id == picking + and not m._has_tracked_subcontract_components() + ) + ) + if not moves: + continue + quantity_done = sum( + move.product_uom._compute_quantity( + move.quantity_done, production.product_uom_id + ) + for move in moves + ) + received_more_qty = ( + float_compare( + production.product_qty, + quantity_done, + precision_rounding=production.product_uom_id.rounding, + ) + == -1 + ) + if received_more_qty: + change_qty = self.env["change.production.qty"].create( + {"mo_id": production.id, "product_qty": quantity_done} + ) + change_qty.with_context(skip_activity=True).change_prod_qty() + production.qty_producing = quantity_done + production._set_qty_producing() + + # Validate MO as it is done in 'mrp_subcontracting' + productions_to_done = ( + picking._get_subcontracted_productions()._subcontracting_filter_to_done() + ) + production_ids_backorder = [] + if not self.env.context.get("cancel_backorder"): + production_ids_backorder = productions_to_done.filtered( + lambda mo: mo.state == "progress" + ).ids + productions_to_done.with_context( + subcontract_move_id=True, mo_ids_to_backorder=production_ids_backorder + ).button_mark_done() + return res diff --git a/stock_reception_screen_mrp_subcontracting/models/stock_reception_screen.py b/stock_reception_screen_mrp_subcontracting/models/stock_reception_screen.py index f4aeb6f1cb4..59f01ac5908 100644 --- a/stock_reception_screen_mrp_subcontracting/models/stock_reception_screen.py +++ b/stock_reception_screen_mrp_subcontracting/models/stock_reception_screen.py @@ -1,4 +1,4 @@ -# Copyright 2021 Camptocamp SA +# Copyright 2022 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import models @@ -9,17 +9,21 @@ class StockReceptionScreen(models.Model): def _split_move(self, move, qty): # Overridden to remove the link between the move of finished product - # and the move to receive to ease the split + # and the move to receive to not update the qty to produce on the MO + # with a partial received qty. That way we always keep the original qty + # to produce on the MO while having the received qty split among + # several moves in the receipt. move_is_subcontract = move.is_subcontract move_move_orig = move.move_orig_ids if move_is_subcontract: move.move_orig_ids = False - new_move_id = super()._split_move(move, qty) + new_move = super()._split_move(move, qty) if move_is_subcontract: - new_move = move.browse(new_move_id) + # Link the finished product move with the remaining move to process new_move.move_orig_ids = move_move_orig + # Restore the link between the finished product move with the received one move.move_orig_ids = move_move_orig - return new_move_id + return new_move def _validate_current_move(self): # Overridden to automatically recheck availability each time a move @@ -29,27 +33,3 @@ def _validate_current_move(self): if self.picking_id.state not in ("cancel", "done"): self.picking_id.action_assign() return res - - def _action_done_move(self, move): - if move.is_subcontract: - move = move.with_context(cancel_backorder=False) - res = super()._action_done_move(move) - return res - - def _action_done_picking(self): - # Overridden to unlink existing move lines of finished products - # as it is done in 'picking.action_done' in module 'mrp_subcontracting', - # but here we do this only one time and not each time we iterate on - # received move (which removes each time the move line created by the - # previous received move, as in the context of reception screen we - # could have received the goods in several moves while having only one - # finished product move) - for move in self.picking_id.move_lines: - if not move.is_subcontract: - continue - if move._has_tracked_subcontract_components(): - move.move_orig_ids.filtered( - lambda m: m.state not in ("done", "cancel") - ).move_line_ids.unlink() - self = self.with_context(subcontracting_skip_unlink=True) - return super()._action_done_picking() diff --git a/stock_reception_screen_mrp_subcontracting/tests/common.py b/stock_reception_screen_mrp_subcontracting/tests/common.py new file mode 100644 index 00000000000..d132da1a853 --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/tests/common.py @@ -0,0 +1,31 @@ +# Copyright 2021 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import tests + +from odoo.addons.stock_reception_screen.tests.common import Common + + +class MrpSubcontractingCommon(Common): + @classmethod + def _prepare_reception_screen(cls): + # product.product_delivery_02 has a subcontracted BoM defined with + # 'mrp_subcontracting' installed + cls.picking = cls._create_picking_in(partner=cls.env.ref("base.res_partner_12")) + cls._create_picking_line(cls.picking, cls.product_2, 4) + cls.picking.action_confirm() + cls.picking.action_reception_screen_open() + cls.screen = cls.picking.reception_screen_id + + def _record_components_for_picking(self, picking): + action = picking.action_record_components() + if action: + production = ( + self.env[action["res_model"]] + .with_context(action["context"]) + .browse(action["res_id"]) + ) + wiz_form = tests.Form(production) + wiz = wiz_form.save() + wiz.qty_producing = wiz.product_qty + wiz.subcontracting_record_component() diff --git a/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py b/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py index 3afa8217306..4799fbecd1c 100644 --- a/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py +++ b/stock_reception_screen_mrp_subcontracting/tests/test_reception_screen.py @@ -3,39 +3,15 @@ from odoo import fields -from odoo.addons.stock_reception_screen.tests.common import Common +from .common import MrpSubcontractingCommon -class TestReceptionScreenMrpSubcontracting(Common): +class TestReceptionScreenMrpSubcontracting(MrpSubcontractingCommon): @classmethod def setUpClass(cls): super().setUpClass() cls._prepare_reception_screen() - @classmethod - def _prepare_reception_screen(cls): - # product.product_delivery_02 has a subcontracted BoM defined with - # 'mrp_subcontracting' installed - cls.picking = cls._create_picking_in(partner=cls.env.ref("base.res_partner_12")) - cls._create_picking_line(cls.picking, cls.product_2, 4) - cls.picking.action_confirm() - cls.picking.action_reception_screen_open() - cls.screen = cls.picking.reception_screen_id - - def _record_components_for_picking(self, picking): - for move in picking.move_lines: - if not move.is_subcontract: - continue - context = dict( - default_production_id=move.move_orig_ids.production_id.id, - default_subcontract_move_id=move.id, - ) - wiz_model = self.env["mrp.product.produce"].with_context(context) - wiz = wiz_model.create({}) - for line in wiz.move_raw_ids.move_line_ids: - line.qty_done = line.product_uom_qty - wiz.do_produce() - def test_reception_screen_with_subcontracted_products(self): """Receive subcontracted goods.""" # Select the product to receive @@ -101,8 +77,6 @@ def test_reception_screen_with_subcontracted_products(self): self.assertEqual(production.move_finished_ids.product_uom_qty, 4) self.assertEqual(production.move_finished_ids.quantity_done, 4) self.assertEqual(production.move_finished_ids.state, "done") - for ml in production.finished_move_line_ids: - self.assertEqual(ml.qty_done, 2) def test_reception_screen_with_subcontracted_products_with_tracked_components(self): """Receive subcontracted goods having components tracked by lots.""" @@ -182,8 +156,6 @@ def test_reception_screen_with_subcontracted_products_with_tracked_components(se # Check that the manufacturing order is now done production = self.picking.move_lines.move_orig_ids.production_id self.assertEqual(production.state, "done") - self.assertEqual(production.move_finished_ids.product_uom_qty, 4) - self.assertEqual(production.move_finished_ids.quantity_done, 4) + self.assertEqual(sum(production.move_finished_ids.mapped("product_uom_qty")), 4) + self.assertEqual(sum(production.move_finished_ids.mapped("quantity_done")), 4) self.assertEqual(production.move_finished_ids.state, "done") - for ml in production.finished_move_line_ids: - self.assertEqual(ml.qty_done, 2) diff --git a/stock_reception_screen_mrp_subcontracting/wizards/__init__.py b/stock_reception_screen_mrp_subcontracting/wizards/__init__.py index 21600358964..276b572779f 100644 --- a/stock_reception_screen_mrp_subcontracting/wizards/__init__.py +++ b/stock_reception_screen_mrp_subcontracting/wizards/__init__.py @@ -1 +1 @@ -from . import mrp_product_produce +from . import change_production_qty diff --git a/stock_reception_screen_mrp_subcontracting/wizards/change_production_qty.py b/stock_reception_screen_mrp_subcontracting/wizards/change_production_qty.py new file mode 100644 index 00000000000..20b738f86cb --- /dev/null +++ b/stock_reception_screen_mrp_subcontracting/wizards/change_production_qty.py @@ -0,0 +1,15 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class ChangeProductionQty(models.TransientModel): + _inherit = "change.production.qty" + + def change_prod_qty(self): + # Inhibited during the call of 'stock.picking._action_done()' of + # 'mrp_subcontracting' addon. + if self.env.context.get("subcontracting_skip_action_done"): + return {} + return super().change_prod_qty() diff --git a/stock_reception_screen_mrp_subcontracting/wizards/mrp_product_produce.py b/stock_reception_screen_mrp_subcontracting/wizards/mrp_product_produce.py deleted file mode 100644 index 37bd987a6c8..00000000000 --- a/stock_reception_screen_mrp_subcontracting/wizards/mrp_product_produce.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2021 Camptocamp SA -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) - -from odoo import models - - -class MrpProductProduce(models.TransientModel): - _inherit = "mrp.product.produce" - - def _record_production(self): - # Overridden to unlink existing move lines when the components - # consumption is recorded. As a result we will get only new move lines - # related to the production, avoiding issues when processing them with - # the reception screen. - dest_moves = self.move_finished_ids.move_dest_ids.filtered( - lambda m: m.state not in ("done", "cancel") - ) - incoming = "incoming" in dest_moves.mapped("picking_code") - if incoming: - dest_moves._do_unreserve() - res = super()._record_production() - if incoming: - dest_moves._action_assign() - return res From 6bcc05f871fd56018d87d683040dff23ff181536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 22 Sep 2023 17:21:54 +0200 Subject: [PATCH 8/8] fixup! [MIG] s_r_s_mrp_subcontracting: Migrate to 14.0 --- stock_reception_screen_mrp_subcontracting/tests/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/stock_reception_screen_mrp_subcontracting/tests/common.py b/stock_reception_screen_mrp_subcontracting/tests/common.py index d132da1a853..ec6cbfc6da9 100644 --- a/stock_reception_screen_mrp_subcontracting/tests/common.py +++ b/stock_reception_screen_mrp_subcontracting/tests/common.py @@ -28,4 +28,5 @@ def _record_components_for_picking(self, picking): wiz_form = tests.Form(production) wiz = wiz_form.save() wiz.qty_producing = wiz.product_qty + wiz._onchange_producing() wiz.subcontracting_record_component()