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/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