diff --git a/stock_move_location/README.rst b/stock_move_location/README.rst index 21cd7854d..c25c79b4f 100644 --- a/stock_move_location/README.rst +++ b/stock_move_location/README.rst @@ -1,21 +1,98 @@ -**This file is going to be generated by oca-gen-addon-readme.** +=================== +Move Stock Location +=================== -*Manual changes will be overwritten.* +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -Please provide content in the ``readme`` directory: +.. |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%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/11.0/stock_move_location + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-11-0/stock-logistics-warehouse-11-0-stock_move_location + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/153/11.0 + :alt: Try me on Runbot -* **DESCRIPTION.rst** (required) -* INSTALL.rst (optional) -* CONFIGURE.rst (optional) -* **USAGE.rst** (optional, highly recommended) -* DEVELOP.rst (optional) -* ROADMAP.rst (optional) -* HISTORY.rst (optional, recommended) -* **CONTRIBUTORS.rst** (optional, highly recommended) -* CREDITS.rst (optional) +|badge1| |badge2| |badge3| |badge4| |badge5| -Content of this README will also be drawn from the addon manifest, -from keys such as name, authors, maintainers, development_status, -and license. +This module allows to move entire location of products from one place to another -A good, one sentence summary in the manifest is also highly recommended. +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +* A new menuitem Stock > Move from location... opens a wizard + where 2 location ca be specified. +* Select origin and destination locations and press "IMMEDIATE TRANSFER" or "PLANNED TRANSFER" +* Press `ADD ALL` button to add all products available +* Those lines can be edited. Move quantity can't be more than a max available quantity +* Move doesn't care about the reservations and will move stuff anyway +* If during your operation with the wizard the real quantity will change + it will move only the available quantity at the button press +* Products will be moved and a form view of picking that did that will show up +* If "PLANNED TRANSFER" is used - the picking won't be validated automatically + +Known issues / Roadmap +====================== + +Change the current implementation (suggested by Denis Roussel from ACSONE): + +* A new parameter on stock picking types : 'Product Change Location' (with a little help). +* With this, go to the dashboard, create a picking with that type. +* Add a button on the picking form which is visible with that type that fill in the picking as now +* Nice to have: add a magic button on locations that with context creates a new picking of that type with the origin location already filled in. + +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 +~~~~~~~ + +* Julius Network Solutions + +Contributors +~~~~~~~~~~~~ + +* Mathieu Vatel +* Mykhailo Panarin + +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/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_move_location/i18n/stock_move_location.pot b/stock_move_location/i18n/stock_move_location.pot index ebbc50d73..fc500ae93 100644 --- a/stock_move_location/i18n/stock_move_location.pot +++ b/stock_move_location/i18n/stock_move_location.pot @@ -4,10 +4,8 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 11.0+e\n" +"Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-08 23:43+0000\n" -"PO-Revision-Date: 2019-01-08 23:43+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -30,6 +28,11 @@ msgstr "" msgid "Clear all" msgstr "" +#. module: stock_move_location +#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_picking_id +msgid "Connected Picking" +msgstr "" + #. module: stock_move_location #: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_create_uid #: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_create_uid @@ -42,6 +45,11 @@ msgstr "" msgid "Created on" msgstr "" +#. module: stock_move_location +#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_custom +msgid "Custom line" +msgstr "" + #. module: stock_move_location #: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_destination_location_id #: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_destination_location_id @@ -61,8 +69,8 @@ msgid "ID" msgstr "" #. module: stock_move_location -#: model:ir.model,name:stock_move_location.model_stock_inventory -msgid "Inventory" +#: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location +msgid "Immediate Transfer" msgstr "" #. module: stock_move_location @@ -70,11 +78,6 @@ msgstr "" msgid "Inventory Details" msgstr "" -#. module: stock_move_location -#: model:ir.model,name:stock_move_location.model_stock_inventory_line -msgid "Inventory Line" -msgstr "" - #. module: stock_move_location #: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location___last_update #: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line___last_update @@ -103,11 +106,6 @@ msgstr "" msgid "Maximum available quantity" msgstr "" -#. module: stock_move_location -#: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location -msgid "Move Location" -msgstr "" - #. module: stock_move_location #: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_stock_move_location_line_ids msgid "Move Location lines" @@ -125,7 +123,7 @@ msgid "Move location Wizard" msgstr "" #. module: stock_move_location -#: code:addons/stock_move_location/wizard/stock_move_location_line.py:56 +#: code:addons/stock_move_location/wizard/stock_move_location_line.py:70 #, python-format msgid "Move quantity can not exceed max quantity or be negative" msgstr "" @@ -136,6 +134,16 @@ msgstr "" msgid "Origin Location" msgstr "" +#. module: stock_move_location +#: model:ir.model.fields,field_description:stock_move_location.field_stock_move_location_move +msgid "Part of move location" +msgstr "" + +#. module: stock_move_location +#: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location +msgid "Planned Transfer" +msgstr "" + #. module: stock_move_location #: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_product_id msgid "Product" @@ -151,11 +159,21 @@ msgstr "" msgid "Quantity to move" msgstr "" +#. module: stock_move_location +#: model:ir.model,name:stock_move_location.model_stock_move +msgid "Stock Move" +msgstr "" + #. module: stock_move_location #: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location msgid "UoM" msgstr "" +#. module: stock_move_location +#: model:ir.model.fields,help:stock_move_location.field_stock_move_location_move +msgid "Whether this move is a part of stock_location moves" +msgstr "" + #. module: stock_move_location #: model:ir.model,name:stock_move_location.model_wiz_stock_move_location msgid "wiz.stock.move.location" diff --git a/stock_move_location/models/stock_move.py b/stock_move_location/models/stock_move.py index b9c4883ea..c8f252f16 100644 --- a/stock_move_location/models/stock_move.py +++ b/stock_move_location/models/stock_move.py @@ -9,12 +9,11 @@ class StockMove(models.Model): location_move = fields.Boolean( string="Part of move location", - help="Wether this move is a part of stock_location moves", + help="Whether this move is a part of stock_location moves", ) @api.depends("location_move") def _compute_show_details_visible(self): super()._compute_show_details_visible() for move in self: - if move.location_move: - move.show_details_visible = True + move.show_details_visible = move.location_move diff --git a/stock_move_location/readme/ROADMAP.rst b/stock_move_location/readme/ROADMAP.rst new file mode 100644 index 000000000..0b83df767 --- /dev/null +++ b/stock_move_location/readme/ROADMAP.rst @@ -0,0 +1,6 @@ +Change the current implementation (suggested by Denis Roussel from ACSONE): + +* A new parameter on stock picking types : 'Product Change Location' (with a little help). +* With this, go to the dashboard, create a picking with that type. +* Add a button on the picking form which is visible with that type that fill in the picking as now +* Nice to have: add a magic button on locations that with context creates a new picking of that type with the origin location already filled in. diff --git a/stock_move_location/readme/USAGE.rst b/stock_move_location/readme/USAGE.rst index 60efc93b1..19e071798 100644 --- a/stock_move_location/readme/USAGE.rst +++ b/stock_move_location/readme/USAGE.rst @@ -4,7 +4,7 @@ * Press `ADD ALL` button to add all products available * Those lines can be edited. Move quantity can't be more than a max available quantity * Move doesn't care about the reservations and will move stuff anyway -* If during you operation with the wizard the real quantity will change +* If during your operation with the wizard the real quantity will change it will move only the available quantity at the button press * Products will be moved and a form view of picking that did that will show up * If "PLANNED TRANSFER" is used - the picking won't be validated automatically diff --git a/stock_move_location/static/description/index.html b/stock_move_location/static/description/index.html new file mode 100644 index 000000000..85911da44 --- /dev/null +++ b/stock_move_location/static/description/index.html @@ -0,0 +1,447 @@ + + + + + + +Move Stock Location + + + +
+

Move Stock Location

+ + +

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

This module allows to move entire location of products from one place to another

+

Table of contents

+ +
+

Usage

+
    +
  • A new menuitem Stock > Move from location… opens a wizard +where 2 location ca be specified.
  • +
  • Select origin and destination locations and press “IMMEDIATE TRANSFER” or “PLANNED TRANSFER”
  • +
  • Press ADD ALL button to add all products available
  • +
  • Those lines can be edited. Move quantity can’t be more than a max available quantity
  • +
  • Move doesn’t care about the reservations and will move stuff anyway
  • +
  • If during your operation with the wizard the real quantity will change +it will move only the available quantity at the button press
  • +
  • Products will be moved and a form view of picking that did that will show up
  • +
  • If “PLANNED TRANSFER” is used - the picking won’t be validated automatically
  • +
+
+
+

Known issues / Roadmap

+

Change the current implementation (suggested by Denis Roussel from ACSONE):

+
    +
  • A new parameter on stock picking types : ‘Product Change Location’ (with a little help).
  • +
  • With this, go to the dashboard, create a picking with that type.
  • +
  • Add a button on the picking form which is visible with that type that fill in the picking as now
  • +
  • Nice to have: add a magic button on locations that with context creates a new picking of that type with the origin location already filled in.
  • +
+
+
+

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

+
    +
  • Julius Network Solutions
  • +
+
+
+

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/stock-logistics-warehouse project on GitHub.

+

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

+
+
+
+ + diff --git a/stock_move_location/tests/test_move_location.py b/stock_move_location/tests/test_move_location.py index 2dec616e1..128a771fb 100644 --- a/stock_move_location/tests/test_move_location.py +++ b/stock_move_location/tests/test_move_location.py @@ -8,6 +8,10 @@ from odoo.exceptions import ValidationError class TestMoveLocation(TestsCommon): + def setUp(self): + super().setUp() + self.setup_product_amounts() + def _create_wizard(self, origin_location, destination_location): return self.wizard_obj.create({ "origin_location_id": origin_location.id, @@ -15,9 +19,7 @@ class TestMoveLocation(TestsCommon): }) def test_move_location_wizard(self): - """Test a simple move. - """ - self.setup_product_amounts() + """Test a simple move.""" wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2) wizard.add_lines() wizard.action_move_location() @@ -47,18 +49,14 @@ class TestMoveLocation(TestsCommon): ) def test_move_location_wizard_amount(self): - """Can't move more than exists - """ - self.setup_product_amounts() + """Can't move more than exists.""" wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2) wizard.add_lines() with self.assertRaises(ValidationError): wizard.stock_move_location_line_ids[0].move_quantity += 1 def test_move_location_wizard_ignore_reserved(self): - """Can't move more than exists - """ - self.setup_product_amounts() + """Can't move more than exists.""" wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2) wizard.add_lines() # reserve some quants @@ -86,9 +84,7 @@ class TestMoveLocation(TestsCommon): ) def test_wizard_clear_lines(self): - """Test lines getting cleared properly - """ - self.setup_product_amounts() + """Test lines getting cleared properly.""" wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2) wizard.add_lines() self.assertEqual(len(wizard.stock_move_location_line_ids), 4) @@ -96,9 +92,7 @@ class TestMoveLocation(TestsCommon): self.assertEqual(len(wizard.stock_move_location_line_ids), 0) def test_planned_transfer(self): - """Test planned transfer - """ - self.setup_product_amounts() + """Test planned transfer.""" wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2) wizard.add_lines() wizard.with_context({'planned': True}).action_move_location() diff --git a/stock_move_location/wizard/stock_move_location.py b/stock_move_location/wizard/stock_move_location.py index 65c39134b..d4a56f37c 100644 --- a/stock_move_location/wizard/stock_move_location.py +++ b/stock_move_location/wizard/stock_move_location.py @@ -36,19 +36,6 @@ class StockMoveLocationWizard(models.TransientModel): def _onchange_locations(self): self._clear_lines() - @api.onchange("stock_move_location_line_ids") - def _onchange_stock_move_location_line_ids(self): - lines_to_update = self.stock_move_location_line_ids.filtered( - lambda x: x.custom is True and - not all([x.origin_location_id, x.destination_location_id]) - ) - lines_to_update.update({ - "origin_location_id": self.origin_location_id, - "destination_location_id": self.destination_location_id, - }) - # for an easier extension of this function - return lines_to_update - def _clear_lines(self): origin = self.origin_location_id destination = self.destination_location_id @@ -141,29 +128,27 @@ class StockMoveLocationWizard(models.TransientModel): }) return action - def _get_group_quants_sql(self): + def _get_group_quants(self): location_id = self.origin_location_id.id company = self.env['res.company']._company_default_get( 'stock.inventory', ) - return """ - SELECT product_id, lot_id, SUM(quantity) - FROM stock_quant - WHERE location_id = {location_id} AND company_id = {company_id} - GROUP BY product_id, lot_id - """.format( - location_id=location_id, - company_id=company.id, - ) + # Using sql as search_group doesn't support aggregation functions + # leading to overhead in queries to DB + query = """ + SELECT product_id, lot_id, SUM(quantity) + FROM stock_quant + WHERE location_id = %s + AND company_id = %s + GROUP BY product_id, lot_id + """ + self.env.cr.execute(query, (location_id, company.id)) + return self.env.cr.dictfetchall() def _get_stock_move_location_lines_values(self): product_obj = self.env['product.product'] - - # Using sql as search_group doesn't support aggregation functions - # leading to overhead in queries to DB - self.env.cr.execute(self._get_group_quants_sql()) product_data = [] - for group in self.env.cr.dictfetchall(): + for group in self._get_group_quants(): product = product_obj.browse(group.get("product_id")).exists() product_data.append({ 'product_id': product.id, @@ -181,11 +166,13 @@ class StockMoveLocationWizard(models.TransientModel): def add_lines(self): self.ensure_one() + line_model = self.env["wiz.stock.move.location.line"] if not self.stock_move_location_line_ids: for line_val in self._get_stock_move_location_lines_values(): if line_val.get('max_quantity') <= 0: continue - self.env["wiz.stock.move.location.line"].create(line_val) + line = line_model.create(line_val) + line.onchange_product_id() return { "type": "ir.actions.do_nothing", } diff --git a/stock_move_location/wizard/stock_move_location.xml b/stock_move_location/wizard/stock_move_location.xml index 6ae9831ed..cb5af3692 100755 --- a/stock_move_location/wizard/stock_move_location.xml +++ b/stock_move_location/wizard/stock_move_location.xml @@ -20,8 +20,8 @@ - - + + diff --git a/stock_move_location/wizard/stock_move_location_line.py b/stock_move_location/wizard/stock_move_location_line.py index 02abadacc..3cc1f8c27 100644 --- a/stock_move_location/wizard/stock_move_location_line.py +++ b/stock_move_location/wizard/stock_move_location_line.py @@ -52,22 +52,43 @@ class StockMoveLocationWizardLine(models.TransientModel): default=True, ) - @api.model - def get_rounding(self): - return self.env.ref("product.decimal_product_uom").digits or 3 + @staticmethod + def _compare(qty1, qty2, precision_rounding): + return float_compare( + qty1, qty2, + precision_rounding=precision_rounding) @api.constrains("max_quantity", "move_quantity") def _constraint_max_move_quantity(self): for record in self: - if (float_compare( - record.move_quantity, - record.max_quantity, self.get_rounding()) == 1 or - float_compare(record.move_quantity, 0.0, - self.get_rounding()) == -1): + rounding = record.product_uom_id.rounding + move_qty_gt_max_qty = self._compare( + record.move_quantity, record.max_quantity, rounding) == 1 + move_qty_lt_0 = self._compare( + record.move_quantity, 0.0, rounding) == -1 + if (move_qty_gt_max_qty or move_qty_lt_0): raise ValidationError(_( "Move quantity can not exceed max quantity or be negative" )) + @api.onchange('product_id', 'lot_id') + def onchange_product_id(self): + self.product_uom_id = self.product_id.uom_id + wiz = self.move_location_wizard_id + search_args = [ + ('location_id', '=', wiz.origin_location_id.id), + ('product_id', '=', self.product_id.id), + ] + if self.lot_id: + search_args.append(('lot_id', '=', self.lot_id.id)) + else: + search_args.append(('lot_id', '=', False)) + res = self.env['stock.quant'].read_group(search_args, ['quantity'], []) + max_quantity = res[0]['quantity'] + self.max_quantity = max_quantity + self.origin_location_id = wiz.origin_location_id + self.destination_location_id = wiz.destination_location_id + def create_move_lines(self, picking, move): for line in self: values = line._get_move_line_values(picking, move) @@ -103,48 +124,23 @@ class StockMoveLocationWizardLine(models.TransientModel): if self.env.context.get("planned"): # for planned transfer we don't care about the amounts at all return self.move_quantity - # switched to sql here to improve performance and lower db queries - self.env.cr.execute(self._get_specific_quants_sql()) - available_qty = self.env.cr.fetchone() + search_args = [ + ('location_id', '=', self.origin_location_id.id), + ('product_id', '=', self.product_id.id), + ] + if self.lot_id: + search_args.append(('lot_id', '=', self.lot_id.id)) + else: + search_args.append(('lot_id', '=', False)) + res = self.env['stock.quant'].read_group(search_args, ['quantity'], []) + available_qty = res[0]['quantity'] if not available_qty: # if it is immediate transfer and product doesn't exist in that # location -> make the transfer of 0. return 0 - available_qty = available_qty[0] - if float_compare( - available_qty, - self.move_quantity, self.get_rounding()) == -1: + rounding = self.product_uom_id.rounding + available_qty_lt_move_qty = self._compare( + available_qty, self.move_quantity, rounding) == -1 + if available_qty_lt_move_qty: return available_qty return self.move_quantity - - def _get_specific_quants_sql(self): - self.ensure_one() - lot = "AND lot_id = {}".format(self.lot_id.id) - if not self.lot_id: - lot = "AND lot_id is null" - return """ - SELECT sum(quantity) - FROM stock_quant - WHERE location_id = {location} - {lot} - AND product_id = {product} - GROUP BY location_id, product_id, lot_id - """.format( - location=self.origin_location_id.id, - product=self.product_id.id, - lot=lot, - ) - - @api.model - def create(self, vals): - res = super().create(vals) - # update of wizard lines is extremely buggy - # so i have to handle this additionally in create - if not all([res.origin_location_id, res.destination_location_id]): - or_loc_id = res.move_location_wizard_id.origin_location_id.id - des_loc_id = res.move_location_wizard_id.destination_location_id.id - res.write({ - "origin_location_id": or_loc_id, - "destination_location_id": des_loc_id, - }) - return res