diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index f416614f01fe67b55415569f68e122282b2b2897..99a53d5e5ee5e32be2593ed711be6602150e1f1b 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -392,6 +392,8 @@ class StockMove(models.Model): # FIXME: pim fix your crap receipt_moves_to_reassign = self.env['stock.move'] move_to_recompute_state = self.env['stock.move'] + if 'product_uom' in vals and any(move.product_uom.id != vals['product_uom'] and move.state == 'done' for move in self): + raise UserError(_('You cannot change the UoM for a stock move that has been set to \'Done\'.')) if 'product_uom_qty' in vals: move_to_unreserve = self.env['stock.move'] for move in self.filtered(lambda m: m.state not in ('done', 'draft') and m.picking_id): diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index c54f50f1fd1bec7f644223feb122a30f3a5e38a1..6d8cbf9c920fbd626d97dc727a55c1fc9004956f 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -230,7 +230,8 @@ class StockMoveLine(models.Model): ('lot_id', 'stock.production.lot'), ('package_id', 'stock.quant.package'), ('result_package_id', 'stock.quant.package'), - ('owner_id', 'res.partner') + ('owner_id', 'res.partner'), + ('product_uom_id', 'uom.uom') ] updates = {} for key, model in triggers: @@ -290,7 +291,7 @@ class StockMoveLine(models.Model): mls = mls.filtered(lambda ml: not float_is_zero(ml.qty_done - vals['qty_done'], precision_rounding=ml.product_uom_id.rounding)) for ml in mls: # undo the original move line - qty_done_orig = ml.move_id.product_uom._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP') + qty_done_orig = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP') in_date = Quant._update_available_quantity(ml.product_id, ml.location_dest_id, -qty_done_orig, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id)[1] Quant._update_available_quantity(ml.product_id, ml.location_id, qty_done_orig, lot_id=ml.lot_id, @@ -305,7 +306,8 @@ class StockMoveLine(models.Model): package_id = updates.get('package_id', ml.package_id) result_package_id = updates.get('result_package_id', ml.result_package_id) owner_id = updates.get('owner_id', ml.owner_id) - quantity = ml.move_id.product_uom._compute_quantity(qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP') + product_uom_id = updates.get('product_uom_id', ml.product_uom_id) + quantity = product_uom_id._compute_quantity(qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP') if not location_id.should_bypass_reservation(): ml._free_reservation(product_id, location_id, quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id) if not float_is_zero(quantity, precision_digits=precision): diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 1c2fc3684ec9d4308d9c96e5faee1150b0c5079c..a826794a81c1e94560e9ffd3424313d0a9505e1b 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -3166,6 +3166,39 @@ class StockMove(TransactionCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location, lot_id=lot1, package_id=package1), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location, lot_id=lot2, package_id=package1), 1.0) + def test_edit_done_move_line_14(self): + """ Test that editing a done stock move line with a different UoM from its stock move correctly + updates the quant when its qty and/or its UoM is edited. Also check that we don't allow editing + a done stock move's UoM. + """ + move1 = self.env['stock.move'].create({ + 'name': 'test_edit_moveline', + 'location_id': self.supplier_location.id, + 'location_dest_id': self.stock_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 12.0, + }) + move1._action_confirm() + move1._action_assign() + move1.move_line_ids.product_uom_id = self.uom_dozen + move1.move_line_ids.qty_done = 1 + move1._action_done() + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 12.0) + + move1.move_line_ids.qty_done = 2 + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 24.0) + self.assertEqual(move1.product_uom_qty, 24.0) + self.assertEqual(move1.product_qty, 24.0) + + move1.move_line_ids.product_uom_id = self.uom_unit + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0) + self.assertEqual(move1.product_uom_qty, 2.0) + self.assertEqual(move1.product_qty, 2.0) + + with self.assertRaises(UserError): + move1.product_uom = self.uom_dozen + def test_immediate_validate_1(self): """ In a picking with a single available move, clicking on validate without filling any quantities should open a wizard asking to process all the reservation (so, the whole move). diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml index 8ce8af6bdff253f036f56e85984f6f07f1846f3e..b5770581151872a5fc94260a7e99154deecd0625 100644 --- a/addons/stock/views/stock_move_views.xml +++ b/addons/stock/views/stock_move_views.xml @@ -93,7 +93,7 @@ <field name="product_uom_qty" string="Initial Demand" attrs="{'readonly': [('is_initial_demand_editable', '=', False)]}"/> <field name="reserved_availability" string="Reserved"/> <field name="quantity_done" string="Done" attrs="{'readonly': [('is_quantity_done_editable', '=', False)]}"/> - <field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft'), ('additional', '=', False)]}" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="uom.group_uom"/> + <field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft'), ('id', '!=', False)]}" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="uom.group_uom"/> </tree> </field> </record> @@ -233,7 +233,10 @@ <field name="state" invisible="1"/> <field name="is_locked" invisible="1"/> <field name="qty_done" attrs="{'readonly': ['|', '|', ('is_initial_demand_editable', '=', True), '&', ('state', '=', 'done'), ('is_locked', '=', True), '&', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}"/> - <field name="product_uom_id" options="{'no_open': True, 'no_create': True}" attrs="{'readonly': ['|', ('product_uom_qty', '!=', 0.0), '&', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}" string="Unit of Measure" groups="uom.group_uom"/> + <field name="product_uom_id" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="uom.group_uom" + attrs="{'readonly': ['|', '|', ('product_uom_qty', '!=', 0.0), + '&', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True), + '&', ('state', '=', 'done'), ('id', '!=', False)]}"/> </tree> </field> </record> diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index b4ad110ec9a9545a3e5610f175520998a24b393f..1f4b1675dbb4f1a52a47e97fb609dfdf02d25c87 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -282,7 +282,7 @@ <tree editable="bottom" decoration-muted="(state == 'done' and is_locked == True)" decoration-danger="qty_done>product_uom_qty and state!='done'" decoration-success="qty_done==product_uom_qty and state!='done' and not result_package_id"> <field name="product_id" required="1" attrs="{'readonly': ['|', ('state', '=', 'done'), ('move_id', '!=', False)]}"/> <field name="move_id" invisible="1"/> - <field name="product_uom_id" force_save="1" attrs="{'readonly': [('state', '!=', 'draft')]}" groups="uom.group_uom"/> + <field name="product_uom_id" force_save="1" attrs="{'readonly': [('state', '!=', 'draft'), ('id', '!=', False)]}" groups="uom.group_uom"/> <field name="location_id" attrs="{'column_invisible': [('parent.picking_type_code', '=', 'incoming')]}" groups="stock.group_stock_multi_locations" domain="[('id', 'child_of', parent.location_id)]"/> <field name="location_dest_id" attrs="{'column_invisible': [('parent.picking_type_code', '=', 'outgoing')]}" groups="stock.group_stock_multi_locations" domain="[('id', 'child_of', parent.location_dest_id)]"/> <field name="package_id" groups="stock.group_tracking_lot" domain="[('location_id', '=', location_id)]"/> @@ -327,7 +327,7 @@ <field name="product_uom_qty" string="Initial Demand" attrs="{'column_invisible': ['&',('parent.immediate_transfer', '=', True), ('parent.is_locked', '=', True)], 'readonly': [('is_initial_demand_editable', '=', False)]}"/> <field name="reserved_availability" string="Reserved" attrs="{'column_invisible': (['|','|', ('parent.state','=', 'done'), ('parent.picking_type_code', '=', 'incoming'),'&',('parent.immediate_transfer', '=', True), ('parent.is_locked', '=', True)])}"/> <field name="quantity_done" string="Done" attrs="{'readonly': [('is_quantity_done_editable', '=', False)]}"/> - <field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft'), ('additional', '=', False)]}" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="uom.group_uom"/> + <field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft'), ('id', '!=', False)]}" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="uom.group_uom"/> <button name="action_show_details" string="Register lots, packs, location" type="object" icon="fa-list" attrs="{'invisible': [('show_details_visible', '=', False)]}" options='{"warn": true}'/> </tree> </field>