diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 3e927a14fd7884c07ea4f0ed8a6ec6c26b5e9a9b..f610fdbf7f4bcdc370515b8deace2fce2a3c37b7 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -554,6 +554,8 @@ class StockMove(models.Model): # messages according to the state of the stock.move records. receipt_moves_to_reassign = self.env['stock.move'] move_to_recompute_state = self.env['stock.move'] + if 'product_uom' in vals and any(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 ef92f758aaeadaa28db049032d72da3a016f402f..d0277281cdc2287a2a1b816144382135205a5548 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -271,7 +271,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: @@ -332,7 +333,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, @@ -347,7 +348,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 ml._should_bypass_reservation(location_id): 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 eb52068ee8b0d1ede292d474d53db1179690b62e..ba3e27a39b4076cbba75c59ed05638538bc0f6cf 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -3379,6 +3379,39 @@ class StockMove(SavepointCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot1, package_id=package1), 0.0) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, 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.product.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.product, self.stock_location), 12.0) + + move1.move_line_ids.qty_done = 2 + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, 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.product, 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 e85a6f3758328529fd6d94ad34a684e3a55a27b7..fd34f2e3ada0855215facbd2131b29dc9f89a7b6 100644 --- a/addons/stock/views/stock_move_views.xml +++ b/addons/stock/views/stock_move_views.xml @@ -75,7 +75,7 @@ <field name="product_uom_qty" string="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> @@ -234,7 +234,10 @@ <field name="is_locked" invisible="1"/> <field name="picking_code" invisible="1"/> <field name="qty_done" attrs="{'readonly': ['|', '&', ('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> @@ -263,7 +266,7 @@ <field name="product_uom_qty" readonly="1" attrs="{'column_invisible': ['|',('parent.immediate_transfer', '=', True),('parent.picking_type_code','=','incoming')]}" optional="show"/> <field name="is_locked" invisible="1"/> <field name="qty_done" attrs="{'readonly': [('state', 'in', ('done', 'cancel')), ('is_locked', '=', True)]}" force_save="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"/> </tree> </field> </record> diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index 749b2d919cdfce3df531f37d45f438d6ae9a3332..28d3304c5ad597a3f9c72d924579d320bc655c66 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -383,7 +383,7 @@ <field name="forecast_expected_date" invisible="1" /> <field name="forecast_availability" string="Reserved" attrs="{'column_invisible': ['|', ('parent.picking_type_code', '!=', 'outgoing'), ('parent.state','=', 'done')]}" widget="forecast_widget"/> <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"/> <field name="lot_ids" widget="many2many_tags" groups="stock.group_production_lot" attrs="{'invisible': ['|', ('show_details_visible', '=', False), ('has_tracking', '!=', 'serial')]}" @@ -418,7 +418,7 @@ <field name="forecast_expected_date" invisible="1"/> <field name="forecast_availability" string="Reserved" attrs="{'invisible': ['|', ('parent.picking_type_code', '!=', 'outgoing'), ('parent.state','=', 'done')]}" widget="forecast_widget"/> <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"/> <field name="description_picking" string="Description"/> </group> </form>