diff --git a/addons/mrp_subcontracting/__init__.py b/addons/mrp_subcontracting/__init__.py
index cde864bae21..35e7c9600c5 100644
--- a/addons/mrp_subcontracting/__init__.py
+++ b/addons/mrp_subcontracting/__init__.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import models
+from . import wizard
diff --git a/addons/mrp_subcontracting/__manifest__.py b/addons/mrp_subcontracting/__manifest__.py
index 6c34d2ba291..146a620edac 100644
--- a/addons/mrp_subcontracting/__manifest__.py
+++ b/addons/mrp_subcontracting/__manifest__.py
@@ -12,6 +12,7 @@
'data': [
'data/mrp_subcontracting_data.xml',
'views/stock_warehouse_views.xml',
+ 'views/stock_move_views.xml',
'views/stock_picking_views.xml',
'views/mrp_bom_views.xml',
'views/res_partner_views.xml',
diff --git a/addons/mrp_subcontracting/models/__init__.py b/addons/mrp_subcontracting/models/__init__.py
index 98238bd9a1b..3e55733a730 100644
--- a/addons/mrp_subcontracting/models/__init__.py
+++ b/addons/mrp_subcontracting/models/__init__.py
@@ -4,6 +4,7 @@ from . import mrp_bom
from . import res_company
from . import res_partner
from . import stock_move
+from . import stock_move_line
from . import stock_picking
from . import stock_warehouse
diff --git a/addons/mrp_subcontracting/models/stock_move.py b/addons/mrp_subcontracting/models/stock_move.py
index 8e149dd2cbe..eba26222c8b 100644
--- a/addons/mrp_subcontracting/models/stock_move.py
+++ b/addons/mrp_subcontracting/models/stock_move.py
@@ -3,13 +3,36 @@
from collections import defaultdict
-from odoo import models, _
-from odoo.exceptions import UserError
+from odoo import fields, models
class StockMove(models.Model):
_inherit = 'stock.move'
+ is_subcontract = fields.Boolean(copy=False)
+ subcontract_components_ids = fields.One2many('stock.move.line',
+ compute='_compute_subcontract_move_line_ids',
+ inverse='_inverse_subcontract_move_line_ids',
+ string='Subcontracted Components', readonly=False)
+
+ def action_show_details(self):
+ action = super(StockMove, self).action_show_details()
+ action['context'].update({
+ 'show_lots_m2o': True,
+ 'show_lots_text': False,
+ })
+ return action
+
+ def _compute_subcontract_move_line_ids(self):
+ for move in self:
+ if move.is_subcontract:
+ move.subcontract_components_ids = move.move_orig_ids.production_id.move_raw_ids.move_line_ids
+
+ def _inverse_subcontract_move_line_ids(self):
+ for move in self:
+ if move.is_subcontract:
+ (move.move_orig_ids.production_id.move_raw_ids.move_line_ids - move.subcontract_components_ids).unlink()
+
def _get_subcontract_bom(self):
self.ensure_one()
bom = self.env['mrp.bom']._bom_subcontract_find(
@@ -35,6 +58,9 @@ class StockMove(models.Model):
error_message += _('If there is well a BoM of type subcontracting defined, check if you have set the correct subcontractors on it.')
raise UserError(error_message % (move.product_id.name, move.product_id.name))
subcontract_details_per_picking[move.picking_id].append((move, bom))
+ move.write({
+ 'is_subcontract': True,
+ })
for picking, subcontract_details in subcontract_details_per_picking.items():
picking._subcontracted_produce(subcontract_details)
return super(StockMove, self)._action_confirm(merge=merge, merge_into=merge_into)
diff --git a/addons/mrp_subcontracting/models/stock_move_line.py b/addons/mrp_subcontracting/models/stock_move_line.py
new file mode 100644
index 00000000000..32ddade892e
--- /dev/null
+++ b/addons/mrp_subcontracting/models/stock_move_line.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class StockMoveLine(models.Model):
+ _inherit = 'stock.move.line'
+
+ def write(self, values):
+ move_lines_subcontracted = self.filtered(lambda ml: ml.move_id.is_subcontract)
+ if not move_lines_subcontracted:
+ return super(StockMoveLine, self).write(values)
+ for ml in move_lines_subcontracted:
+ candidates = ml.move_id.move_orig_ids.move_line_ids
+ candidate = candidates.filtered(lambda c:
+ c.qty_done == ml.qty_done and ml.product_uom_qty == c.product_uom_qty and
+ ml.product_uom_id == c.product_uom_id and c.lot_id == ml.lot_id)
+ candidate = candidate and candidate[0] or self.env['stock.move.line']
+ candidate.write(values)
+ return super(StockMoveLine, self).write(values)
diff --git a/addons/mrp_subcontracting/models/stock_picking.py b/addons/mrp_subcontracting/models/stock_picking.py
index b08eaf6d78a..4ab048ad799 100644
--- a/addons/mrp_subcontracting/models/stock_picking.py
+++ b/addons/mrp_subcontracting/models/stock_picking.py
@@ -8,7 +8,6 @@ class StockPicking(models.Model):
_inherit = 'stock.picking'
display_action_record_components = fields.Boolean(compute='_compute_display_action_record_components')
- display_view_subcontracted_move_lines = fields.Boolean(compute='_compute_display_view_subcontracted_move_lines')
@api.depends('state')
def _compute_display_action_record_components(self):
@@ -29,26 +28,6 @@ class StockPicking(models.Model):
continue
picking.display_action_record_components = True
- @api.depends('state')
- def _compute_display_view_subcontracted_move_lines(self):
- for picking in self:
- # Hide if not encoding state
- if picking.state in ('draft', 'cancel'):
- continue
- if not picking._is_subcontract():
- continue
- # Hide until state done if no move is tracked, if tracked until something was produced
- subcontracted_productions = picking._get_subcontracted_productions()
- subcontracted_moves = subcontracted_productions.mapped('move_raw_ids')
- subcontracted_moves |= subcontracted_productions.mapped('move_finished_ids')
- if all(subcontracted_move.has_tracking == 'none' for subcontracted_move in subcontracted_moves):
- if picking.state != 'done':
- continue
- # Hide if nothing was produced
- if all(subcontracted_move.quantity_done == 0 for subcontracted_move in subcontracted_moves):
- continue
- picking.display_view_subcontracted_move_lines = True
-
# -------------------------------------------------------------------------
# Action methods
# -------------------------------------------------------------------------
@@ -60,29 +39,18 @@ class StockPicking(models.Model):
subcontracted_production.button_mark_done()
return res
- def action_view_subcontracted_move_lines(self):
- """ Returns a list view with the move lines of the subcontracted products. To find them, we
- look on the origin moves of the move lines of the picking if there is a manufacturing order.
- """
- self.ensure_one()
- subcontracted_productions = self._get_subcontracted_productions()
- subcontracted_move_lines = self.env['stock.move.line']
- for subcontracted_production in subcontracted_productions:
- subcontracted_move_lines |= subcontracted_production.move_raw_ids.mapped('move_line_ids')
- subcontracted_move_lines |= subcontracted_production.move_finished_ids.mapped('move_line_ids')
- action = self.env.ref('stock.stock_move_line_action').read()[0]
- action['context'] = {}
- action['domain'] = [('id', 'in', subcontracted_move_lines.ids)]
- return action
-
def action_record_components(self):
self.ensure_one()
- subcontracted_productions = self._get_subcontracted_productions()
- to_register = subcontracted_productions.filtered(lambda mo: mo.state not in ('to_close', 'done'))
- if to_register:
- mo = to_register[0]
+ for move in self.move_lines:
+ production = move.move_orig_ids.production_id
+ if not production or production.state in ('done', 'to_close'):
+ continue
action = self.env.ref('mrp.act_mrp_product_produce').read()[0]
- action['context'] = dict(self.env.context, active_id=mo.id)
+ action['context'] = dict(
+ self.env.context,
+ active_id=production.id,
+ default_subcontract_move_id=move.id
+ )
return action
# -------------------------------------------------------------------------
diff --git a/addons/mrp_subcontracting/tests/test_subcontracting.py b/addons/mrp_subcontracting/tests/test_subcontracting.py
index f715f1466e6..58ad3928d44 100644
--- a/addons/mrp_subcontracting/tests/test_subcontracting.py
+++ b/addons/mrp_subcontracting/tests/test_subcontracting.py
@@ -188,7 +188,6 @@ class TestSubcontractingFlows(SavepointCase):
# Nothing should be tracked
self.assertFalse(picking_receipt.display_action_record_components)
- self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
# Check the created manufacturing order
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
@@ -218,9 +217,6 @@ class TestSubcontractingFlows(SavepointCase):
picking_receipt.button_validate()
self.assertEquals(mo.state, 'done')
- # Now that the picking is done, the details stat button should be visible
- self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
-
# Available quantities should be negative at the subcontracting location for each components
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
@@ -250,7 +246,6 @@ class TestSubcontractingFlows(SavepointCase):
# Nothing should be tracked
self.assertFalse(picking_receipt.display_action_record_components)
- self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
# Pickings should directly be created
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
@@ -275,9 +270,6 @@ class TestSubcontractingFlows(SavepointCase):
picking_receipt.button_validate()
self.assertEquals(mo.state, 'done')
- # Now that the picking is done, the details stat button should be visible
- self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
-
# Available quantities should be negative at the subcontracting location for each components
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
@@ -314,7 +306,6 @@ class TestSubcontractingFlows(SavepointCase):
# Nothing should be tracked
self.assertFalse(picking_receipt.display_action_record_components)
- self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
# Pickings should directly be created
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
@@ -336,9 +327,6 @@ class TestSubcontractingFlows(SavepointCase):
picking_receipt.button_validate()
self.assertEquals(mo.state, 'done')
- # Now that the picking is done, the details stat button should be visible
- self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
-
# Available quantities should be negative at the subcontracting location for each components
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_supplier, allow_negative=True)
@@ -524,7 +512,6 @@ class TestSubcontractingTracking(TransactionCase):
# We should be able to call the 'record_components' button
self.assertTrue(picking_receipt.display_action_record_components)
- self.assertFalse(picking_receipt.display_view_subcontracted_move_lines)
# Check the created manufacturing order
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_tracked.id)])
@@ -571,8 +558,6 @@ class TestSubcontractingTracking(TransactionCase):
# We should not be able to call the 'record_components' button
self.assertFalse(picking_receipt.display_action_record_components)
- # We should see the move lines stat button
- self.assertTrue(picking_receipt.display_view_subcontracted_move_lines)
picking_receipt.move_lines.quantity_done = 1
picking_receipt.move_lines.move_line_ids.lot_name = 'lot00001test'
diff --git a/addons/mrp_subcontracting/views/stock_move_views.xml b/addons/mrp_subcontracting/views/stock_move_views.xml
new file mode 100644
index 00000000000..0f1f7a8801d
--- /dev/null
+++ b/addons/mrp_subcontracting/views/stock_move_views.xml
@@ -0,0 +1,26 @@
+
+
+
+ stock.move.operations.form.inherit.mrp.subcontracting
+ stock.move
+
+
+
+
+
Components
+
+
+
+
+
+
+
+
+
+
+
+
Subcontracted Product
+
+
+
+
\ No newline at end of file
diff --git a/addons/mrp_subcontracting/views/stock_picking_views.xml b/addons/mrp_subcontracting/views/stock_picking_views.xml
index 23145eeabbf..e5b87e618eb 100644
--- a/addons/mrp_subcontracting/views/stock_picking_views.xml
+++ b/addons/mrp_subcontracting/views/stock_picking_views.xml
@@ -5,14 +5,6 @@
stock.picking
-
-
-
-
-
diff --git a/addons/mrp_subcontracting/wizard/__init__.py b/addons/mrp_subcontracting/wizard/__init__.py
new file mode 100644
index 00000000000..153ced71cbe
--- /dev/null
+++ b/addons/mrp_subcontracting/wizard/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import mrp_product_produce
diff --git a/addons/mrp_subcontracting/wizard/mrp_product_produce.py b/addons/mrp_subcontracting/wizard/mrp_product_produce.py
new file mode 100644
index 00000000000..06965a54e90
--- /dev/null
+++ b/addons/mrp_subcontracting/wizard/mrp_product_produce.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+from odoo.tools.float_utils import float_is_zero
+
+class MrpProductProduce(models.TransientModel):
+ _inherit = 'mrp.product.produce'
+
+ subcontract_move_id = fields.Many2one('stock.move', 'stock move from the subcontract picking')
+
+ def continue_production(self):
+ action = super(MrpProductProduce, self).continue_production()
+ action['context'] = dict(action['context'], default_subcontract_move_id=self.subcontract_move_id.id)
+ return action
+
+ def _update_finished_move(self):
+ """ After producing, set the move line on the subcontract picking. """
+ res = super(MrpProductProduce, self)._update_finished_move()
+ if self.subcontract_move_id:
+ self.env['stock.move.line'].create({
+ 'move_id': self.subcontract_move_id.id,
+ 'picking_id': self.subcontract_move_id.picking_id.id,
+ 'product_id': self.product_id.id,
+ 'location_id': self.subcontract_move_id.location_id.id,
+ 'location_dest_id': self.subcontract_move_id.location_dest_id.id,
+ 'product_uom_qty': 0,
+ 'product_uom_id': self.product_uom_id.id,
+ 'qty_done': self.qty_producing,
+ 'lot_id': self.finished_lot_id and self.finished_lot_id.id,
+ })
+ if not self._get_todo(self.production_id):
+ ml_reserved = self.subcontract_move_id.move_line_ids.filtered(lambda ml:
+ float_is_zero(ml.qty_done, precision_rounding=ml.product_uom_id.rounding) and
+ not float_is_zero(ml.product_uom_qty, precision_rounding=ml.product_uom_id.rounding))
+ ml_reserved.unlink()
+ for ml in self.subcontract_move_id.move_line_ids:
+ ml.product_uom_qty = ml.qty_done
+ self.subcontract_move_id._recompute_state()
+ return res