diff --git a/addons/mrp_subcontracting/__manifest__.py b/addons/mrp_subcontracting/__manifest__.py
index 1d390e86d1c..7aef066dbce 100644
--- a/addons/mrp_subcontracting/__manifest__.py
+++ b/addons/mrp_subcontracting/__manifest__.py
@@ -18,6 +18,7 @@
'views/stock_picking_views.xml',
'views/supplier_info_views.xml',
'views/product_views.xml',
+ 'views/mrp_production_views.xml',
'wizard/stock_picking_return_views.xml',
],
'demo': [
diff --git a/addons/mrp_subcontracting/models/__init__.py b/addons/mrp_subcontracting/models/__init__.py
index 41a12adb994..7b4d6067559 100644
--- a/addons/mrp_subcontracting/models/__init__.py
+++ b/addons/mrp_subcontracting/models/__init__.py
@@ -9,4 +9,4 @@ from . import stock_move_line
from . import stock_picking
from . import stock_rule
from . import stock_warehouse
-
+from . import mrp_production
diff --git a/addons/mrp_subcontracting/wizard/mrp_product_produce.py b/addons/mrp_subcontracting/models/mrp_production.py
similarity index 55%
rename from addons/mrp_subcontracting/wizard/mrp_product_produce.py
rename to addons/mrp_subcontracting/models/mrp_production.py
index 0dd804ad9f4..fcc77b32e63 100644
--- a/addons/mrp_subcontracting/wizard/mrp_product_produce.py
+++ b/addons/mrp_subcontracting/models/mrp_production.py
@@ -1,20 +1,64 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import fields, models
+from collections import defaultdict
+from odoo import fields, models, _, api
+from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare, float_is_zero
class MrpProduction(models.Model):
_inherit = 'mrp.production'
- def write(self, vals):
- res = super().write(vals)
- if self.env.context.get('subcontract_move_id'):
- for production in self:
- if production.state not in ('cancel', 'done') and \
- ('lot_producing_id' in vals or 'qty_producing' in vals):
- production._update_finished_move()
+ move_line_raw_ids = fields.One2many(
+ 'stock.move.line', string="Detail Component", readonly=False,
+ inverse='_inverse_move_line_raw_ids', compute='_compute_move_line_raw_ids'
+ )
+
+ @api.depends('move_raw_ids.move_line_ids')
+ def _compute_move_line_raw_ids(self):
+ for production in self:
+ production.move_line_raw_ids = production.move_raw_ids.move_line_ids
+
+ def _inverse_move_line_raw_ids(self):
+ for production in self:
+ line_by_product = defaultdict(lambda: self.env['stock.move.line'])
+ for line in production.move_line_raw_ids:
+ line_by_product[line.product_id] |= line
+ for move in production.move_raw_ids:
+ move.move_line_ids = line_by_product.pop(move.product_id, self.env['stock.move.line'])
+ for product_id, lines in line_by_product.items():
+ qty = sum(line.product_uom_id._compute_quantity(line.qty_done, product_id.uom_id) for line in lines)
+ move = production._get_move_raw_values(product_id, qty, product_id.uom_id)
+ move['additional'] = True
+ production.move_raw_ids = [(0, 0, move)]
+ production.move_raw_ids.filtered(lambda m: m.product_id == product_id)[:1].move_line_ids = lines
+
+ def subcontracting_record_component(self):
+ self.ensure_one()
+ assert self.env.context.get('subcontract_move_id')
+ if float_is_zero(self.qty_producing, precision_rounding=self.product_uom_id.rounding):
+ return {'type': 'ir.actions.act_window_close'}
+ for sml in self.move_raw_ids.move_line_ids:
+ if sml.tracking != 'none' and not sml.lot_id:
+ raise UserError(_('You must enter a serial number for each line of %s') % sml.product_id.name)
+ self._update_finished_move()
+ quantity_issues = self._get_quantity_produced_issues()
+ if quantity_issues:
+ backorder = self._generate_backorder_productions(close_mo=False)
+ # No qty to consume to avoid propagate additional move
+ # TODO avoid : stock move created in backorder with 0 as qty
+ backorder.move_raw_ids.filtered(lambda m: m.additional).product_uom_qty = 0.0
+
+ backorder.qty_producing = backorder.product_qty
+ backorder._set_qty_producing()
+
+ self.product_qty = self.qty_producing
+ subcontract_move_id = self.env['stock.move'].browse(self.env.context.get('subcontract_move_id'))
+ action = subcontract_move_id._action_record_components()
+ action.update({'res_id': backorder.id})
+ return action
+ return {'type': 'ir.actions.act_window_close'}
def _pre_button_mark_done(self):
if self.env.context.get('subcontract_move_id'):
@@ -23,8 +67,8 @@ class MrpProduction(models.Model):
def _update_finished_move(self):
""" After producing, set the move line on the subcontract picking. """
+ self.ensure_one()
subcontract_move_id = self.env.context.get('subcontract_move_id')
- res = None
if subcontract_move_id:
subcontract_move_id = self.env['stock.move'].browse(subcontract_move_id)
quantity = self.qty_producing
@@ -73,8 +117,7 @@ class MrpProduction(models.Model):
'qty_done': quantity,
'lot_id': self.lot_producing_id and self.lot_producing_id.id,
})
-
- if not self._get_todo():
+ if not self._get_quantity_to_backorder():
ml_reserved = 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))
@@ -82,11 +125,3 @@ class MrpProduction(models.Model):
for ml in subcontract_move_id.move_line_ids:
ml.product_uom_qty = ml.qty_done
subcontract_move_id._recompute_state()
- return res
-
- def _get_todo(self):
- """ This method will return remaining todo quantity of production. """
- main_product_moves = self.move_finished_ids.filtered(lambda x: x.product_id.id == self.product_id.id)
- todo_quantity = self.product_qty - sum(main_product_moves.mapped('quantity_done'))
- todo_quantity = todo_quantity if (todo_quantity > 0) else 0
- return todo_quantity
diff --git a/addons/mrp_subcontracting/models/stock_move.py b/addons/mrp_subcontracting/models/stock_move.py
index e33f5377c0b..6854e8f0bff 100644
--- a/addons/mrp_subcontracting/models/stock_move.py
+++ b/addons/mrp_subcontracting/models/stock_move.py
@@ -54,8 +54,7 @@ class StockMove(models.Model):
if 'product_uom_qty' in values:
if self.env.context.get('cancel_backorder') is False:
return super(StockMove, self).write(values)
- self.filtered(lambda m: m.is_subcontract and
- m.state not in ['draft', 'cancel', 'done'])._update_subcontract_order_qty(values['product_uom_qty'])
+ self.filtered(lambda m: m.is_subcontract and m.state not in ['draft', 'cancel', 'done'])._update_subcontract_order_qty(values['product_uom_qty'])
return super(StockMove, self).write(values)
def action_show_details(self):
@@ -65,7 +64,7 @@ class StockMove(models.Model):
self.ensure_one()
if self.is_subcontract:
rounding = self.product_uom.rounding
- production = self.move_orig_ids.production_id
+ production = self.move_orig_ids.production_id[-1:]
if self._has_tracked_subcontract_components() and\
float_compare(production.qty_produced, production.product_uom_qty, precision_rounding=rounding) < 0 and\
float_compare(self.quantity_done, self.product_uom_qty, precision_rounding=rounding) < 0:
@@ -84,6 +83,7 @@ class StockMove(models.Model):
moves = self.move_orig_ids.production_id.move_raw_ids
tree_view = self.env.ref('mrp_subcontracting.mrp_subcontracting_move_tree_view')
form_view = self.env.ref('mrp_subcontracting.mrp_subcontracting_move_form_view')
+ ctx = dict(self._context, search_default_by_product=True, subcontract_move_id=self.id)
return {
'name': _('Raw Materials for %s') % (self.product_id.display_name),
'type': 'ir.actions.act_window',
@@ -91,6 +91,7 @@ class StockMove(models.Model):
'views': [(tree_view.id, 'list'), (form_view.id, 'form')],
'target': 'current',
'domain': [('id', 'in', moves.ids)],
+ 'context': ctx
}
def _action_cancel(self):
@@ -127,8 +128,8 @@ class StockMove(models.Model):
def _action_record_components(self):
self.ensure_one()
- production = self.move_orig_ids.production_id
- view = self.env.ref('mrp.mrp_production_form_view')
+ production = self.move_orig_ids.production_id[-1:]
+ view = self.env.ref('mrp_subcontracting.mrp_production_subcontracting_form_view')
return {
'name': _('Subcontract'),
'type': 'ir.actions.act_window',
@@ -141,32 +142,6 @@ class StockMove(models.Model):
'context': dict(self.env.context, subcontract_move_id=self.id),
}
-
- def _check_overprocessed_subcontract_qty(self):
- """ If a subcontracted move use tracked components. Do not allow to add
- quantity without the produce wizard. Instead update the initial demand
- and use the register component button. Split or correct a lot/sn is
- possible.
- """
- overprocessed_moves = self.env['stock.move']
- for move in self:
- if not move.is_subcontract:
- continue
- # Extra quantity is allowed when components do not need to be register
- if not move._has_tracked_subcontract_components():
- continue
- rounding = move.product_uom.rounding
-# if float_compare(move.quantity_done, move.move_orig_ids.production_id.qty_produced, precision_rounding=rounding) > 0:
-# overprocessed_moves |= move
- if overprocessed_moves:
- raise UserError(_("""
-You have to use 'Records Components' button in order to register quantity for a
-subcontracted product(s) with tracked component(s):
- %s.
-If you want to process more than initially planned, you
-can use the edit + unlock buttons in order to adapt the initial demand on the
-operations.""") % ('\n'.join(overprocessed_moves.mapped('product_id.display_name'))))
-
def _get_subcontract_bom(self):
self.ensure_one()
bom = self.env['mrp.bom'].sudo()._bom_subcontract_find(
@@ -192,6 +167,11 @@ operations.""") % ('\n'.join(overprocessed_moves.mapped('product_id.display_name
vals['location_id'] = self.location_id.id
return vals
+ def _should_bypass_set_qty_producing(self):
+ if self.env.context.get('subcontract_move_id'):
+ return False
+ return super()._should_bypass_set_qty_producing()
+
def _should_bypass_reservation(self):
""" If the move is subcontracted then ignore the reservation. """
should_bypass_reservation = super(StockMove, self)._should_bypass_reservation()
@@ -199,12 +179,19 @@ operations.""") % ('\n'.join(overprocessed_moves.mapped('product_id.display_name
return True
return should_bypass_reservation
- def _update_subcontract_order_qty(self, quantity):
+ def _update_subcontract_order_qty(self, new_quantity):
for move in self:
- quantity_change = quantity - move.product_uom_qty
- production = move.move_orig_ids.production_id
- if production:
- self.env['change.production.qty'].with_context(skip_activity=True).create({
- 'mo_id': production.id,
- 'product_qty': production.product_uom_qty + quantity_change
- }).change_prod_qty()
+ quantity_to_remove = move.product_uom_qty - new_quantity
+ productions = move.move_orig_ids.production_id.filtered(lambda p: p.state not in ('done', 'cancel'))[::-1]
+ # Cancel productions until reach new_quantity
+ for production in productions:
+ if quantity_to_remove <= 0.0:
+ break
+ if quantity_to_remove >= production.product_qty:
+ quantity_to_remove -= production.product_qty
+ production.action_cancel()
+ else:
+ self.env['change.production.qty'].with_context(skip_activity=True).create({
+ 'mo_id': production.id,
+ 'product_qty': production.product_uom_qty - quantity_to_remove
+ }).change_prod_qty()
diff --git a/addons/mrp_subcontracting/models/stock_move_line.py b/addons/mrp_subcontracting/models/stock_move_line.py
index 3a18f76cadd..6dfb789b041 100644
--- a/addons/mrp_subcontracting/models/stock_move_line.py
+++ b/addons/mrp_subcontracting/models/stock_move_line.py
@@ -1,23 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, models
+from odoo import models, fields
class StockMoveLine(models.Model):
_inherit = 'stock.move.line'
- @api.model_create_multi
- def create(self, vals_list):
- records = super(StockMoveLine, self).create(vals_list)
- records.filtered(lambda ml: ml.move_id.is_subcontract).move_id._check_overprocessed_subcontract_qty()
- return records
-
- def write(self, values):
- res = super(StockMoveLine, self).write(values)
- self.filtered(lambda ml: ml.move_id.is_subcontract).move_id._check_overprocessed_subcontract_qty()
- return res
-
def _should_bypass_reservation(self, location):
""" If the move line is subcontracted then ignore the reservation. """
should_bypass_reservation = super(StockMoveLine, self)._should_bypass_reservation(location)
diff --git a/addons/mrp_subcontracting/models/stock_picking.py b/addons/mrp_subcontracting/models/stock_picking.py
index 3af0d45b1ff..6faebd8a809 100644
--- a/addons/mrp_subcontracting/models/stock_picking.py
+++ b/addons/mrp_subcontracting/models/stock_picking.py
@@ -4,7 +4,7 @@
from datetime import timedelta
from odoo import api, fields, models
-from odoo.tools import float_is_zero
+from odoo.tools.float_utils import float_is_zero
class StockPicking(models.Model):
@@ -24,12 +24,14 @@ class StockPicking(models.Model):
continue
# Hide if no components are track
subcontracted_productions = picking._get_subcontracted_productions()
- subcontracted_moves = subcontracted_productions.mapped('move_raw_ids')
- if all(subcontracted_move.has_tracking == 'none' for subcontracted_move in subcontracted_moves):
+ component_sub_moves = subcontracted_productions.mapped('move_raw_ids')
+ if all(subcontracted_move.has_tracking == 'none' for subcontracted_move in component_sub_moves):
picking.display_action_record_components = False
continue
- # Hide if the production is to close
- if not subcontracted_productions.filtered(lambda mo: mo.state not in ('to_close', 'done')):
+ # Hide if all tracked product move line have a lot and all productions are in rigth state
+ tracked_move_line = component_sub_moves.filtered(lambda sm: sm.has_tracking != 'none').move_line_ids
+ if all(sub_mo.state in ('to_close', 'done') for sub_mo in subcontracted_productions)\
+ and all(sub_sml.lot_id for sub_sml in tracked_move_line):
picking.display_action_record_components = False
continue
picking.display_action_record_components = True
@@ -39,29 +41,12 @@ class StockPicking(models.Model):
# -------------------------------------------------------------------------
def _action_done(self):
res = super(StockPicking, self)._action_done()
- productions = self.env['mrp.production']
for picking in self:
- for move in picking.move_lines:
- if not move.is_subcontract:
- continue
- production = move.move_orig_ids.production_id.filtered(lambda p: p.state not in ('done', 'cancel'))
- if len(production) > 1:
- production = production[-1]
- if move._has_tracked_subcontract_components():
- move.move_orig_ids.filtered(lambda m: m.state not in ('done', 'cancel')).move_line_ids.unlink()
- move_finished_ids = move.move_orig_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
- for ml in move.move_line_ids:
- ml.copy({
- 'picking_id': False,
- 'production_id': move_finished_ids.production_id.id,
- 'move_id': move_finished_ids.id,
- 'qty_done': ml.qty_done,
- 'result_package_id': False,
- 'location_id': move_finished_ids.location_id.id,
- 'location_dest_id': move_finished_ids.location_dest_id.id,
- })
- else:
- if not move.move_line_ids.lot_id:
+ # Auto set qty_producing if not tracked component
+ for move in picking.move_lines.filtered(lambda move: move.is_subcontract):
+ if not move._has_tracked_subcontract_components():
+ production = move.move_orig_ids.production_id.filtered(lambda p: p.state not in ('done', 'cancel'))
+ if production:
qty_done_production_uom = move.product_uom._compute_quantity(move.quantity_done, production.product_uom_id)
production.qty_producing += qty_done_production_uom
for move in (production.move_raw_ids | production.move_finished_ids.filtered(lambda m: m.product_id != production.product_id)):
@@ -74,12 +59,15 @@ class StockPicking(models.Model):
rounding_method='HALF-UP')
move.move_line_ids.filtered(lambda ml: ml.state not in ('done', 'cancel')).qty_done = 0
move.move_line_ids = move._set_quantity_done_prepare_vals(new_qty)
- productions |= production
- for subcontracted_production in productions:
+
+ productions_to_done = picking.move_lines.move_orig_ids.production_id.filtered(lambda p: p.state not in ('done', 'cancel'))
+ # TODO : to change in master with field on mrp
+ productions_to_done = productions_to_done.filtered(lambda pro: all(line.lot_id for line in pro.move_raw_ids.filtered(lambda sm: sm.has_tracking != 'none').move_line_ids))
+ for subcontracted_production in productions_to_done:
if subcontracted_production.state == 'progress':
subcontracted_production._post_inventory()
else:
- subcontracted_production.button_mark_done()
+ subcontracted_production.with_context(subcontract_move_id=True).button_mark_done()
# For concistency, set the date on production move before the date
# on picking. (Tracability report + Product Moves menu item)
minimum_date = min(picking.move_line_ids.mapped('date'))
@@ -93,8 +81,8 @@ class StockPicking(models.Model):
for move in self.move_lines:
if not move._has_tracked_subcontract_components():
continue
- production = move.move_orig_ids.production_id
- if not production or production.state in ('done', 'to_close'):
+ production = move.move_orig_ids.production_id[-1:]
+ if not production:
continue
return move._action_record_components()
@@ -145,3 +133,7 @@ class StockPicking(models.Model):
finished_move = mo.move_finished_ids.filtered(lambda m: m.product_id == move.product_id)
finished_move.write({'move_dest_ids': [(4, move.id, False)]})
mo.action_assign()
+
+ if move._has_tracked_subcontract_components():
+ mo.qty_producing = move.product_uom_qty
+ mo.with_context(subcontract_move_id=True)._set_qty_producing()
diff --git a/addons/mrp_subcontracting/tests/test_subcontracting.py b/addons/mrp_subcontracting/tests/test_subcontracting.py
index c282950ee97..22cea05aed4 100644
--- a/addons/mrp_subcontracting/tests/test_subcontracting.py
+++ b/addons/mrp_subcontracting/tests/test_subcontracting.py
@@ -402,87 +402,6 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
self.assertEqual(sum(move_finished.mapped('product_uom_qty')), 3.0)
self.assertEqual(sum(move_finished.mapped('quantity_done')), 3.0)
- def test_flow_7(self):
- """ Process a subcontracting receipt with tracked component and
- finished product. Simulate the regiter components button.
- Once the components are registered, try to do a correction on exisiting
- move lines and check that the subcontracting document is updated.
- """
- # Create a receipt picking from the subcontractor
- (self.comp1 | self.comp2 | self.finished).write({'tracking': 'lot'})
- picking_form = Form(self.env['stock.picking'])
- picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
- picking_form.partner_id = self.subcontractor_partner1
- with picking_form.move_ids_without_package.new() as move:
- move.product_id = self.finished
- move.product_uom_qty = 3 # FIXME sle: need to handle the backorder in subcontract
- picking_receipt = picking_form.save()
- picking_receipt.action_confirm()
- mo = picking_receipt.move_lines.move_orig_ids.production_id
- move_comp1 = mo.move_raw_ids.filtered(lambda m: m.product_id == self.comp1)
- move_comp2 = mo.move_raw_ids.filtered(lambda m: m.product_id == self.comp2)
- # move_finished is linked to receipt and not MO finished move.
- move_finished = picking_receipt.move_lines
-
- self.assertEqual(move_comp1.quantity_done, 0)
- self.assertEqual(move_comp2.quantity_done, 0)
-
- lot_c1 = self.env['stock.production.lot'].create({
- 'name': 'LOT C1',
- 'product_id': self.comp1.id,
- 'company_id': self.env.company.id,
- })
- lot_c2 = self.env['stock.production.lot'].create({
- 'name': 'LOT C2',
- 'product_id': self.comp2.id,
- 'company_id': self.env.company.id,
- })
- lot_f1 = self.env['stock.production.lot'].create({
- 'name': 'LOT F1',
- 'product_id': self.finished.id,
- 'company_id': self.env.company.id,
- })
- mo_form = Form(picking_receipt._get_subcontracted_productions().with_context(subcontract_move_id=picking_receipt.move_lines.id))
- mo_form.qty_producing = 3
- mo_form.lot_producing_id = lot_f1
- mo = mo_form.save()
- details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
- with details_operation_form.move_line_ids.new() as ml:
- ml.lot_id = lot_c1
- ml.qty_done = 3
- details_operation_form.save()
- details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
- with details_operation_form.move_line_ids.new() as ml:
- ml.lot_id = lot_c2
- ml.qty_done = 3
- details_operation_form.save()
-
- self.assertEqual(move_comp1.quantity_done, 3)
- self.assertEqual(move_comp1.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_id.name, 'LOT C1')
- self.assertEqual(move_comp2.quantity_done, 3)
- self.assertEqual(move_comp2.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_id.name, 'LOT C2')
- self.assertEqual(move_finished.quantity_done, 3)
- self.assertEqual(move_finished.move_line_ids.filtered(lambda ml: ml.product_uom_qty).lot_id.name, 'LOT F1')
-
- corrected_final_lot = self.env['stock.production.lot'].create({
- 'name': 'LOT F2',
- 'product_id': self.finished.id,
- 'company_id': self.env.company.id,
- })
-
- details_operation_form = Form(picking_receipt.move_lines, view=self.env.ref('stock.view_stock_move_operations'))
- with details_operation_form.move_line_ids.edit(0) as ml:
- ml.lot_id = corrected_final_lot
- details_operation_form.save()
- move_raw_comp_1 = picking_receipt.move_lines.move_orig_ids.production_id.move_raw_ids.filtered(lambda m: m.product_id == self.comp1)
- move_raw_comp_2 = picking_receipt.move_lines.move_orig_ids.production_id.move_raw_ids.filtered(lambda m: m.product_id == self.comp2)
-
- picking_receipt.move_lines.move_orig_ids.production_id.lot_producing_id = corrected_final_lot
- picking_receipt.move_lines.move_orig_ids.production_id.button_mark_done()
-
- self.assertEqual(move_comp1.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).produce_line_ids.lot_id.name, 'LOT F2')
- self.assertEqual(move_comp2.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).produce_line_ids.lot_id.name, 'LOT F2')
-
def test_flow_8(self):
resupply_sub_on_order_route = self.env['stock.location.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
(self.comp1 + self.comp2).write({'route_ids': [(4, resupply_sub_on_order_route.id, None)]})
@@ -596,7 +515,7 @@ class TestSubcontractingTracking(TransactionCase):
})
# 2.2. Finished prodcut has tracking by serial number
- self.finished_lot = self.env['product.product'].create({
+ self.finished_product = self.env['product.product'].create({
'name': 'finished',
'type': 'product',
'categ_id': self.env.ref('product.product_category_all').id,
@@ -605,7 +524,7 @@ class TestSubcontractingTracking(TransactionCase):
bom_form = Form(self.env['mrp.bom'])
bom_form.type = 'subcontract'
bom_form.subcontractor_ids.add(self.subcontractor_partner1)
- bom_form.product_tmpl_id = self.finished_lot.product_tmpl_id
+ bom_form.product_tmpl_id = self.finished_product.product_tmpl_id
with bom_form.bom_line_ids.new() as bom_line:
bom_line.product_id = self.comp1_sn
bom_line.product_qty = 1
@@ -622,7 +541,7 @@ class TestSubcontractingTracking(TransactionCase):
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move:
- move.product_id = self.finished_lot
+ move.product_id = self.finished_product
move.product_uom_qty = 1
picking_receipt = picking_form.save()
picking_receipt.action_confirm()
@@ -633,7 +552,6 @@ class TestSubcontractingTracking(TransactionCase):
# Check the created manufacturing order
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_tracked.id)])
self.assertEqual(len(mo), 1)
- self.assertEqual(mo.state, 'confirmed')
self.assertEqual(len(mo.picking_ids), 0)
wh = picking_receipt.picking_type_id.warehouse_id
self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
@@ -658,7 +576,7 @@ class TestSubcontractingTracking(TransactionCase):
lot_id = self.env['stock.production.lot'].create({
'name': 'lot1',
- 'product_id': self.finished_lot.id,
+ 'product_id': self.finished_product.id,
'company_id': self.env.company.id,
})
serial_id = self.env['stock.production.lot'].create({
@@ -667,15 +585,15 @@ class TestSubcontractingTracking(TransactionCase):
'company_id': self.env.company.id,
})
- mo_form = Form(mo.with_context(subcontract_move_id=picking_receipt.move_lines.id))
+ action = picking_receipt.action_record_components()
+ mo = self.env['mrp.production'].browse(action['res_id'])
+ mo_form = Form(mo.with_context(**action['context']), view=action['view_id'])
mo_form.qty_producing = 1
mo_form.lot_producing_id = lot_id
- mo = mo_form.save()
- details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
- with details_operation_form.move_line_ids.new() as ml:
- ml.qty_done = 1
+ with mo_form.move_line_raw_ids.edit(0) as ml:
ml.lot_id = serial_id
- details_operation_form.save()
+ mo = mo_form.save()
+ mo.subcontracting_record_component()
# We should not be able to call the 'record_components' button
self.assertFalse(picking_receipt.display_action_record_components)
@@ -686,7 +604,84 @@ class TestSubcontractingTracking(TransactionCase):
# Available quantities should be negative at the subcontracting location for each components
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1_sn, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
- avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished_lot, wh.lot_stock_id)
+ avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished_product, wh.lot_stock_id)
self.assertEqual(avail_qty_comp1, -1)
self.assertEqual(avail_qty_comp2, -1)
self.assertEqual(avail_qty_finished, 1)
+
+ def test_flow_tracked_backorder(self):
+ """ This test uses tracked (serial and lot) component and tracked (serial) finished product """
+ todo_nb = 4
+ self.comp2.tracking = 'lot'
+ self.finished_product.tracking = 'serial'
+
+ # Create a receipt picking from the subcontractor
+ picking_form = Form(self.env['stock.picking'])
+ picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
+ picking_form.partner_id = self.subcontractor_partner1
+ with picking_form.move_ids_without_package.new() as move:
+ move.product_id = self.finished_product
+ move.product_uom_qty = todo_nb
+ picking_receipt = picking_form.save()
+ picking_receipt.action_confirm()
+
+ # We should be able to call the 'record_components' button
+ self.assertTrue(picking_receipt.display_action_record_components)
+
+ # Check the created manufacturing order
+ mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_tracked.id)])
+ self.assertEqual(len(mo), 1)
+ self.assertEqual(len(mo.picking_ids), 0)
+ wh = picking_receipt.picking_type_id.warehouse_id
+ self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
+ self.assertFalse(mo.picking_type_id.active)
+
+ lot_comp2 = self.env['stock.production.lot'].create({
+ 'name': 'lot_comp2',
+ 'product_id': self.comp2.id,
+ 'company_id': self.env.company.id,
+ })
+ serials_finished = []
+ serials_comp1 = []
+ for i in range(todo_nb):
+ serials_finished.append(self.env['stock.production.lot'].create({
+ 'name': 'serial_fin_%s' % i,
+ 'product_id': self.finished_product.id,
+ 'company_id': self.env.company.id,
+ }))
+ serials_comp1.append(self.env['stock.production.lot'].create({
+ 'name': 'serials_comp1_%s' % i,
+ 'product_id': self.comp1_sn.id,
+ 'company_id': self.env.company.id,
+ }))
+
+ for i in range(todo_nb):
+ action = picking_receipt.action_record_components()
+ mo = self.env['mrp.production'].browse(action['res_id'])
+ mo_form = Form(mo.with_context(**action['context']), view=action['view_id'])
+ mo_form.lot_producing_id = serials_finished[i]
+ with mo_form.move_line_raw_ids.edit(0) as ml:
+ self.assertEqual(ml.product_id, self.comp1_sn)
+ ml.lot_id = serials_comp1[i]
+ with mo_form.move_line_raw_ids.edit(1) as ml:
+ self.assertEqual(ml.product_id, self.comp2)
+ ml.lot_id = lot_comp2
+ mo = mo_form.save()
+ mo.subcontracting_record_component()
+
+ # We should not be able to call the 'record_components' button
+ self.assertFalse(picking_receipt.display_action_record_components)
+
+ picking_receipt.button_validate()
+ self.assertEqual(mo.state, 'done')
+ self.assertEqual(mo.procurement_group_id.mrp_production_ids.mapped("state"), ['done'] * todo_nb)
+ self.assertEqual(len(mo.procurement_group_id.mrp_production_ids), todo_nb)
+ self.assertEqual(mo.procurement_group_id.mrp_production_ids.mapped("qty_produced"), [1] * todo_nb)
+
+ # Available quantities should be negative at the subcontracting location for each components
+ avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1_sn, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
+ avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
+ avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished_product, wh.lot_stock_id)
+ self.assertEqual(avail_qty_comp1, -todo_nb)
+ self.assertEqual(avail_qty_comp2, -todo_nb)
+ self.assertEqual(avail_qty_finished, todo_nb)
diff --git a/addons/mrp_subcontracting/views/mrp_production_views.xml b/addons/mrp_subcontracting/views/mrp_production_views.xml
new file mode 100644
index 00000000000..657bcaaf7e1
--- /dev/null
+++ b/addons/mrp_subcontracting/views/mrp_production_views.xml
@@ -0,0 +1,54 @@
+
+
+
+ mrp.production.subcontracting.form.view
+ mrp.production
+ primary
+ 1000
+
+
+
+
+
+
+
+ 1
+
+
+ 1
+
+
+ {'invisible': [('product_tracking', 'in', ('none', False))], 'required': [('product_tracking', 'not in', ('none', False))]}
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mrp_subcontracting/views/stock_move_views.xml b/addons/mrp_subcontracting/views/stock_move_views.xml
index 58159cc81c9..e2c52941da4 100644
--- a/addons/mrp_subcontracting/views/stock_move_views.xml
+++ b/addons/mrp_subcontracting/views/stock_move_views.xml
@@ -1,41 +1,109 @@
+
+
+ mrp.subcontracting.stock.move.line.tree.view
+ stock.move.line
+ 1000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
mrp.subcontracting.move.form.view
stock.move
1000
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ mrp.subcontracting.move.tree.view
+ stock.move
+ 1000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mrp_subcontracting/wizard/__init__.py b/addons/mrp_subcontracting/wizard/__init__.py
index ee7f13005bc..0441b01a991 100644
--- a/addons/mrp_subcontracting/wizard/__init__.py
+++ b/addons/mrp_subcontracting/wizard/__init__.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import mrp_product_produce
from . import stock_picking_return
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index a10d4feb0fb..c5342b4b5a5 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -1093,6 +1093,7 @@ class StockMove(models.Model):
'location_id': self.location_id.id,
'location_dest_id': location_dest_id,
'picking_id': self.picking_id.id,
+ 'company_id': self.company_id.id,
}
if quantity:
uom_quantity = self.product_id.uom_id._compute_quantity(quantity, self.product_uom, rounding_method='HALF-UP')
diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml
index 153843af2a0..dc20bbfac1e 100644
--- a/addons/stock/views/stock_move_views.xml
+++ b/addons/stock/views/stock_move_views.xml
@@ -175,6 +175,7 @@
1000