From 94b673dd293c3889c41b026e98dcab5033a72250 Mon Sep 17 00:00:00 2001
From: Josse Colpaert <jco@odoo.com>
Date: Tue, 19 Sep 2017 15:56:28 +0200
Subject: [PATCH] [IMP] mrp: lock/unlock mode and show final lot

We apply a lock/unlock mode for the mo as was done for the picking.
In locked mode, the user can do all the regular things, but not change
the quantities done given by the work order or produce wizard.
In unlocked mode, only allowed by the mrp manager, the user can change
those quantities.

We opted to remove the 4 lines and just use a check box to indicate
there are lots behind a line.

For the finished products tab, we opted to show the move lines instead
of the moves as this way, you don't need to click further.  If nothing
has been produced yet, a message indicates to produce something first.
---
 addons/mrp/models/mrp_production.py       | 42 +++++++++++++---
 addons/mrp/models/stock_move.py           | 60 ++++++++++-------------
 addons/mrp/views/mrp_production_views.xml | 56 +++++++++++----------
 addons/mrp/views/stock_move_views.xml     | 30 +++++++-----
 addons/stock/models/stock_move.py         |  9 +++-
 5 files changed, 117 insertions(+), 80 deletions(-)

diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 9d2076596a03..34878e9cdb62 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -106,6 +106,9 @@ class MrpProduction(models.Model):
         'stock.move', 'production_id', 'Finished Products',
         copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, 
         domain=[('scrapped', '=', False)])
+    finished_move_line_ids = fields.One2many(
+        'stock.move.line', compute='_compute_lines', inverse='_inverse_lines',
+        )
     workorder_ids = fields.One2many(
         'mrp.workorder', 'production_id', 'Work Orders',
         copy=False, oldname='workcenter_lines', readonly=True)
@@ -157,6 +160,23 @@ class MrpProduction(models.Model):
     scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
     priority = fields.Selection([('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')], 'Priority',
                                 readonly=True, states={'confirmed': [('readonly', False)]}, default='1')
+    is_locked = fields.Boolean('Is Locked', default=True)
+    show_final_lots = fields.Boolean('Show Final Lots', compute='_compute_show_lots')
+    production_location_id = fields.Many2one('stock.location', "Production Location", related='product_id.property_stock_production')
+
+    @api.depends('product_id.tracking')
+    def _compute_show_lots(self):
+        for production in self:
+            production.show_final_lots = production.product_id.tracking != 'none'
+
+    def _inverse_lines(self):
+        """ Little hack to make sure that when you change something on these objects, it gets saved"""
+        pass
+
+    @api.depends('move_finished_ids.move_line_ids')
+    def _compute_lines(self):
+        for production in self:
+            production.finished_move_line_ids = production.move_finished_ids.mapped('move_line_ids')
 
     @api.multi
     @api.depends('bom_id.routing_id', 'bom_id.routing_id.operation_ids')
@@ -199,18 +219,21 @@ class MrpProduction(models.Model):
                 assigned_list = [x.state in ('assigned', 'done', 'cancel') for x in order.move_raw_ids]
                 order.availability = (all(assigned_list) and 'assigned') or (any(partial_list) and 'partially_available') or 'waiting'
 
+    @api.depends('move_raw_ids', 'is_locked', 'state', 'move_raw_ids.quantity_done')
     def _compute_unreserve_visible(self):
-        return True
+        for order in self:
+            already_reserved = order.is_locked and order.state not in ('done', 'cancel') and order.mapped('move_raw_ids.move_line_ids')
+            any_quantity_done = any([m.quantity_done > 0 for m in order.move_raw_ids])
+            order.unreserve_visible = not any_quantity_done and already_reserved
 
     @api.multi
-    @api.depends('move_raw_ids.quantity_done', 'move_finished_ids.quantity_done')
+    @api.depends('move_raw_ids.quantity_done', 'move_finished_ids.quantity_done', 'is_locked')
     def _compute_post_visible(self):
         for order in self:
             if order.product_tmpl_id._is_cost_method_standard():
-                order.post_visible = any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_raw_ids) or \
-                    any((x.quantity_done > 0 and x.state not in ['done' 'cancel']) for x in order.move_finished_ids)
+                order.post_visible = order.is_locked and any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_raw_ids | order.move_finished_ids)
             else:
-                order.post_visible = any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_finished_ids)
+                order.post_visible = order.is_locked and any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_finished_ids)
 
     @api.multi
     @api.depends('move_raw_ids.quantity_done', 'move_raw_ids.product_uom_qty')
@@ -223,7 +246,7 @@ class MrpProduction(models.Model):
             )
 
     @api.multi
-    @api.depends('workorder_ids.state', 'move_finished_ids')
+    @api.depends('workorder_ids.state', 'move_finished_ids', 'is_locked')
     def _get_produced_qty(self):
         for production in self:
             done_moves = production.move_finished_ids.filtered(lambda x: x.state != 'cancel' and x.product_id.id == production.product_id.id)
@@ -231,7 +254,7 @@ class MrpProduction(models.Model):
             wo_done = True
             if any([x.state not in ('done', 'cancel') for x in production.workorder_ids]):
                 wo_done = False
-            production.check_to_done = done_moves and (qty_produced >= production.product_qty) and (production.state not in ('done', 'cancel')) and wo_done
+            production.check_to_done = production.is_locked and done_moves and (qty_produced >= production.product_qty) and (production.state not in ('done', 'cancel')) and wo_done
             production.qty_produced = qty_produced
         return True
 
@@ -293,6 +316,11 @@ class MrpProduction(models.Model):
             raise UserError(_('Cannot delete a manufacturing order not in cancel state'))
         return super(MrpProduction, self).unlink()
 
+    def action_toggle_is_locked(self):
+        self.ensure_one()
+        self.is_locked = not self.is_locked
+        return True
+
     @api.multi
     def _generate_moves(self):
         for production in self:
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index c7d7111fd1bd..6fddb2f1f922 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -66,7 +66,31 @@ class StockMove(models.Model):
         'Done', compute='_compute_is_done',
         store=True,
         help='Technical Field to order moves')
-    
+    needs_lots = fields.Boolean('Tracking', compute='_compute_needs_lots')
+    order_finished_lot_ids = fields.Many2many('stock.production.lot', compute='_compute_order_finished_lot_ids')
+
+    @api.depends('active_move_line_ids.qty_done', 'active_move_line_ids.product_uom_id')
+    def _compute_done_quantity(self):
+        super(StockMove, self)._compute_done_quantity()
+
+    @api.depends('raw_material_production_id.move_finished_ids.move_line_ids.lot_id')
+    def _compute_order_finished_lot_ids(self):
+        for move in self:
+            if move.product_id.tracking != 'none' and move.raw_material_production_id:
+                move.order_finished_lot_ids = move.raw_material_production_id.move_finished_ids.mapped('move_line_ids.lot_id').ids
+
+    @api.depends('product_id.tracking')
+    def _compute_needs_lots(self):
+        for move in self:
+            move.needs_lots = move.product_id.tracking != 'none'
+
+    @api.depends('raw_material_production_id.is_locked', 'picking_id.is_locked')
+    def _compute_is_locked(self):
+        super(StockMove, self)._compute_is_locked()
+        for move in self:
+            if move.raw_material_production_id:
+                move.is_locked = move.raw_material_production_id.is_locked
+
     def _get_move_lines(self):
         self.ensure_one()
         if self.raw_material_production_id:
@@ -93,40 +117,6 @@ class StockMove(models.Model):
              If you want to cancel this MO, please change the consumed quantities to 0.'))
         return super(StockMove, self)._action_cancel()
 
-    @api.multi
-    # Could use split_move_operation from stock here
-    def split_move_lot(self):
-        ctx = dict(self.env.context)
-        self.ensure_one()
-        view = self.env.ref('mrp.view_stock_move_lots')
-        serial = (self.has_tracking == 'serial')
-        only_create = False  # Check operation type in theory
-        show_reserved = any([x for x in self.move_line_ids if x.product_qty > 0.0])
-        ctx.update({
-            'serial': serial,
-            'only_create': only_create,
-            'create_lots': True,
-            'state_done': self.is_done,
-            'show_reserved': show_reserved,
-        })
-        if ctx.get('w_production'):
-            action = self.env.ref('mrp.act_mrp_product_produce').read()[0]
-            action['context'] = ctx
-            return action
-        result = {
-            'name': _('Register Lots'),
-            'type': 'ir.actions.act_window',
-            'view_type': 'form',
-            'view_mode': 'form',
-            'res_model': 'stock.move',
-            'views': [(view.id, 'form')],
-            'view_id': view.id,
-            'target': 'new',
-            'res_id': self.id,
-            'context': ctx,
-        }
-        return result
-
     def _action_confirm(self, merge=True):
         moves = self
         for move in self.filtered(lambda m: m.production_id):
diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml
index d0415dc2fc39..9e27498caaa0 100644
--- a/addons/mrp/views/mrp_production_views.xml
+++ b/addons/mrp/views/mrp_production_views.xml
@@ -38,18 +38,21 @@
                     <field name="consumed_less_than_planned" invisible="1"/>
                     <button name="button_mark_done" attrs="{'invisible': ['|', ('check_to_done', '=', False), ('consumed_less_than_planned', '=', True)]}" string="Mark as Done" type="object" class="oe_highlight"/>
                     <button name="button_mark_done" attrs="{'invisible': ['|', ('check_to_done', '=', False), ('consumed_less_than_planned', '=', False)]}" string="Mark as Done" type="object" class="oe_highlight" confirm="You have consumed less material than what was planned. Are you sure you want to close this MO?"/>
-                    <button name="action_assign" attrs="{'invisible': [('availability', 'in', ('assigned', 'none'))]}" string="Check availability" type="object" class="oe_highlight"/>
-                    <button name="button_plan" attrs="{'invisible': ['|', ('state', '!=', 'confirmed'), ('routing_id', '=', False)]}" type="object" string="Create Workorders" class="oe_highlight"/>
-                    <button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', ('check_to_done', '=', True), ('availability', 'not in', ['partially_available', 'assigned']), ('state', 'not in', ('confirmed','progress')), ('routing_id', '!=', False)]}" string="Produce" type="object" class="oe_highlight"/>
-                    <button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', ('check_to_done', '=', True), ('availability', '!=', 'waiting'), ('state', 'not in', ('confirmed','progress')), ('routing_id', '!=', False)]}" string="Produce" type="object"/>
+                    <button name="action_assign" attrs="{'invisible': ['|', ('availability', 'in', ('assigned', 'none')), ('is_locked', '=', False)]}" string="Check availability" type="object" class="oe_highlight"/>
+                    <button name="button_plan" attrs="{'invisible': ['|', '|', ('state', '!=', 'confirmed'), ('routing_id', '=', False), ('is_locked', '=', False)]}" type="object" string="Create Workorders" class="oe_highlight"/>
+                    <button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', '|', ('is_locked', '=', False), ('check_to_done', '=', True), ('availability', 'not in', ['partially_available', 'assigned']), ('state', 'not in', ('confirmed','progress')), ('routing_id', '!=', False)]}" string="Produce" type="object" class="oe_highlight"/>
+                    <button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', '|', ('is_locked', '=', False), ('check_to_done', '=', True), ('availability', '!=', 'waiting'), ('state', 'not in', ('confirmed','progress')), ('routing_id', '!=', False)]}" string="Produce" type="object"/>
                     <button name="post_inventory" string="Post Inventory" type="object" attrs="{'invisible': [('post_visible', '=', False)]}" groups="base.group_no_one"/>
-                    <button name="action_cancel" type="object" string="Cancel" attrs="{'invisible': ['|', ('state', 'in', ('done','cancel')), ('check_to_done', '=', True)]}"/>
-                    <button name="button_scrap" type="object" string="Scrap" attrs="{'invisible': ['|', ('availability', '=', 'none'), ('state', 'in', ('cancel'))]}"/>
+                    <button name="action_cancel" type="object" string="Cancel" attrs="{'invisible': ['|', '|', ('is_locked', '=', False), ('state', 'in', ('done','cancel')), ('check_to_done', '=', True)]}"/>
+                    <button name="button_scrap" type="object" string="Scrap" attrs="{'invisible': ['|', '|', ('availability', '=', 'none'), ('state', 'in', ('cancel')), ('is_locked', '=', False)]}"/>
                     <button name="button_unreserve" type="object" string="Unreserve" attrs="{'invisible': [('unreserve_visible', '=', False)]}"/>
-                    <span class="label label-danger" attrs="{'invisible': ['|', ('availability', 'in', ('assigned', 'none')), ('state', 'not in', ('confirmed','progress'))]}">Raw materials not available!</span>
+                    <span class="label label-danger" attrs="{'invisible': ['|',('availability', 'in', ('assigned', 'none')), ('state', 'not in', ('confirmed','progress'))]}">Raw materials not available!</span>
                     <field name="state" widget="statusbar" statusbar_visible="confirmed,progress,done"/>
+                    <button name="action_toggle_is_locked" attrs="{'invisible': ['|', ('id', '=', False), ('is_locked', '=', False)]}" string="Unlock" groups="mrp.group_mrp_manager" type="object" help="If the manufacturing order is unlocked you can add to the initial demand."/>
+                    <button name="action_toggle_is_locked" attrs="{'invisible': [('is_locked', '=', True)]}" string="Lock" class="oe_highlight" groups="mrp.group_mrp_manager" type="object"/>
                 </header>
                 <sheet>
+                    <field name="is_locked" invisible="1"/>
                     <field name="post_visible" invisible="1"/>
                     <field name="unreserve_visible" invisible="1"/>
                     <div class="oe_button_box" name="button_box">
@@ -102,45 +105,46 @@
                             <field name="user_id"/>
                             <field name="origin" groups="base.group_no_one"/>
                             <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" attrs="{'readonly': [('has_moves', '=', True)]}"/>
+                            <field name="show_final_lots" invisible="1"/>
+                            <field name="production_location_id" invisible="1" readonly="1"/>
                         </group>
                     </group>
                     <notebook>
                         <page string="Consumed Materials">
-                            <field name="move_raw_ids" options="{'reload_on_button': True}" context="{'default_location_id': location_src_id, 'default_location_dest_id': location_dest_id}">
-                                <tree editable="bottom" delete="0" default_order="is_done desc,sequence" decoration-muted="is_done" decoration-warning="quantity_done&gt;product_uom_qty" decoration-success="quantity_done==product_uom_qty" decoration-danger="reserved_availability &lt; product_uom_qty" create="0">
+                            <field name="move_raw_ids" context="{'final_lots': show_final_lots, 'form_view_ref': 'mrp.view_stock_move_lots', 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'confirmed', 'default_raw_material_production_id': id}" attrs="{'readonly': [('is_locked', '=', True)]}">
+                                <tree delete="0" default_order="is_done,sequence" decoration-muted="is_done" decoration-warning="quantity_done&gt;product_uom_qty" decoration-success="quantity_done==product_uom_qty" decoration-danger="reserved_availability &lt; product_uom_qty">
                                     <field name="product_id" required="1"/>
+                                    <field name="name" invisible="1"/>
+                                    <field name="unit_factor" invisible="1"/>
                                     <field name="product_uom" groups="product.group_uom"/>
                                     <field name="has_tracking" invisible="1"/>
+                                    <field name="needs_lots" readonly="1" groups="stock.group_production_lot"/>
                                     <field name="is_done" invisible="1"/>
                                     <field name="sequence" invisible="1"/>
                                     <field name="location_id" domain="[('id', 'child_of', parent.location_id)]" invisible="1"/>
                                     <field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/>
                                     <field name="state" invisible="1"/>
-                                    <field name="product_uom_qty" readonly="1" attrs="{'required': [('product_id', '!=', False)]}" string="To Consume"/>
+                                    <field name="product_uom_qty" string="To Consume"/>
                                     <field name="reserved_availability" attrs="{'invisible': [('is_done', '=', True)]}" string="Reserved"/>
-                                    <field name="quantity_done" attrs="{'readonly': ['|', ('is_done', '=', True), ('has_tracking', 'in', ['lot','serial'])]}" string="Consumed"/>
-                                    <button name="split_move_lot" string="Register lots" type="object" icon="fa-list"
-                                        attrs="{'invisible': [('has_tracking', 'not in', ['lot','serial'])]}"/>
+                                    <field name="quantity_done" string="Consumed"/>
                                 </tree>
                             </field>
                         </page>
                         <page string="Finished Products">
-                            <field name="move_finished_ids" options="{'reload_on_button': True}" context="{'default_location_id': location_src_id, 'default_location_dest_id': location_dest_id}">
-                                <tree editable="bottom" delete="0" default_order="is_done desc,sequence" decoration-muted="is_done" decoration-warning="quantity_done&gt;product_uom_qty" decoration-success="quantity_done==product_uom_qty" create="0">
-                                    <field name="product_id" required="1"/>
-                                    <field name="product_uom" groups="product.group_uom"/>
-                                    <field name="is_done" invisible="1"/>
-                                    <field name="sequence" invisible="1"/>
-                                    <field name="has_tracking" invisible="1"/>
-                                    <field name="location_id" domain="[('id', 'child_of', parent.location_id)]" invisible="1"/>
-                                    <field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/>
+                            <field name="finished_move_line_ids" attrs="{'readonly': [('is_locked', '=', True)], 'invisible': [('finished_move_line_ids', '=', [])]}">
+                                 <tree default_order="done_move" editable="bottom" create="0" delete="0" decoration-muted="state in ('done', 'cancel')">
+                                    <field name="product_id" readonly="1"/>
+                                    <field name="lot_id" groups="stock.group_production_lot" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" attrs="{'invisible': [('lots_visible', '=', False)]}"/>
+                                    <field name="product_uom_id" groups="product.group_uom"/>
+                                    <field name="qty_done"/>
+                                    <field name="lots_visible" invisible="1"/>
+                                    <field name="done_move" invisible="1"/>
                                     <field name="state" invisible="1"/>
-                                    <field name="product_uom_qty" readonly="1" attrs="{'required': [('product_id', '!=', False)]}" string="To Produce"/>
-                                    <field name="quantity_done" attrs="{'readonly': ['|', ('is_done', '=', True), ('has_tracking', 'in', ['lot','serial'])]}" string="Produced"/>
-                                    <button name="split_move_lot" string="Register lots" type="object" icon="fa-list"
-                                        attrs="{'invisible': [('has_tracking', 'not in', ['lot','serial'])]}"/>
                                 </tree>
                             </field>
+                            <p attrs="{'invisible': [('finished_move_line_ids', '!=', [])]}">
+                                Use the Produce button or process the work orders to create some finished products. 
+                            </p>
                         </page>
                         <page string="Miscellaneous">
                             <group>
diff --git a/addons/mrp/views/stock_move_views.xml b/addons/mrp/views/stock_move_views.xml
index db9623bc67fd..ea74de051b1a 100644
--- a/addons/mrp/views/stock_move_views.xml
+++ b/addons/mrp/views/stock_move_views.xml
@@ -18,11 +18,18 @@
                     <field name="state" invisible="1"/>
                     <group>
                         <group>
-                            <field name="product_id" readonly="1"/>
+                            <field name="product_id" attrs="{'readonly': [('id', '!=', False)]}"/>
+                            <label for="product_uom_qty"/>
+                            <div class="o_row">
+                                <span><field name="product_uom_qty" readonly="1" nolabel="1"/></span>
+                                <span><field name="product_uom" attrs="{'readonly': [('id', '!=', False)]}" nolabel="1"/></span>
+                            </div>
                             <label for="quantity_done"/>
-                            <div>
-                                <field name="quantity_done" readonly="1" class="oe_inline"/>
-                                <field name="product_uom" readonly="1" class="oe_inline" groups="product.group_uom"/>
+                            <div class="o_row">
+                                <span><field name="quantity_done" attrs="{'readonly': ['|', ('is_locked', '=', True), ('has_tracking', '!=', 'none')]}" nolabel="1"/></span>
+                                <span> / </span>
+                                <span><field name="reserved_availability" nolabel="1"/></span>
+                                <!-- <span><field name="product_uom" readonly="1" nolabel="1"/></span>-->
                             </div>
                             <field name="is_done" invisible="1"/>
                             <field name="workorder_id" invisible="1"/>
@@ -30,12 +37,17 @@
                             <field name="location_dest_id" invisible="1"/>
                             <field name="production_id" invisible="1"/>
                             <field name="raw_material_production_id" invisible="1"/>
+                            <field name="is_locked" invisible="1"/>
+                            <field name="name" invisible="1"/>
+                            <field name="has_tracking" invisible="1"/>
+                            <field name="order_finished_lot_ids" invisible="1"/>
                         </group>
                     </group>
-                    <field name="active_move_line_ids" attrs="{'readonly': [('is_done', '=', True)]}" context="{'default_workorder_id': workorder_id, 'default_product_uom_id': product_uom, 'default_product_id': product_id,  'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_production_id': production_id or raw_material_production_id}">
+                    <field name="active_move_line_ids" attrs="{'readonly': [('is_locked', '=', True)], 'invisible': [('has_tracking', '=', 'none')]}" context="{'default_workorder_id': workorder_id, 'default_product_uom_id': product_uom, 'default_product_id': product_id,  'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_production_id': production_id or raw_material_production_id}">
                         <tree editable="bottom" decoration-success="product_qty==qty_done" decoration-danger="(product_qty &gt; 0) and (qty_done&gt;product_qty)">
                             <field name="lot_id" domain="[('product_id', '=', parent.product_id)]" context="{'default_product_id': parent.product_id}"/>
-                            <field name="product_qty" invisible="not context.get('show_reserved') or context.get('serial') or context.get('state_done')" readonly="1"/>
+                            <field name="lot_produced_id" options="{'no_open': True, 'no_create': True}" domain="[('id', 'in', parent.order_finished_lot_ids)]" invisible="not context.get('final_lots')"/>
+                            <field name="product_qty" string="Reserved" readonly="1"/>
                             <field name="qty_done"/>
                             <field name="workorder_id" invisible="1"/>
                             <field name="product_id" invisible="1"/>
@@ -43,13 +55,9 @@
                             <field name="location_id" invisible="1"/>
                             <field name="location_dest_id" invisible="1"/>
                             <field name="done_wo" invisible="1"/>
-                            <field name="production_id" invisible="1" />
+                            <field name="production_id" invisible="1"/>
                         </tree>
                     </field>
-                    <footer class="oe_edit_only">
-                        <button special="save" type="object" string="Save" class="oe_highlight"/>
-                        <button string="Discard" special="cancel"/>
-                    </footer>
                 </form>
             </field>
         </record>
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 1ee84e00d9a6..b1221f1424ed 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -154,11 +154,18 @@ class StockMove(models.Model):
     picking_code = fields.Selection(related='picking_id.picking_type_id.code', readonly=True)
     product_type = fields.Selection(related='product_id.type', readonly=True)
     additional = fields.Boolean("Whether the move was added after the picking's confirmation", default=False)
-    is_locked = fields.Boolean(related='picking_id.is_locked', readonly=True)
+    is_locked = fields.Boolean(compute='_compute_is_locked', readonly=True)
     is_initial_demand_editable = fields.Boolean('Is initial demand editable', compute='_compute_is_initial_demand_editable')
     is_quantity_done_editable = fields.Boolean('Is quantity done editable', compute='_compute_is_quantity_done_editable')
     reference = fields.Char(compute='_compute_reference', string="Reference", store=True)
 
+    @api.depends('picking_id.is_locked')
+    def _compute_is_locked(self):
+        for move in self:
+            if move.picking_id:
+                move.is_locked = move.picking_id.is_locked
+
+
     @api.depends('product_id', 'has_tracking', 'move_line_ids', 'location_id', 'location_dest_id')
     def _compute_show_details_visible(self):
         """ According to this field, the button that calls `action_show_details` will be displayed
-- 
GitLab