summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorArnold Moyaux <arm@odoo.com>2020-06-09 10:37:11 +0200
committerSimon Lejeune <sle@openerp.com>2020-06-15 16:59:41 +0200
commited7012e8fd73d772bab5ecddd7781f888d4661f5 (patch)
treebf7b677aaec499b62695f61e6cdd1dae958a4fbb /addons
parent40603879886399a142dfc5dcf1f702fbb5ab4681 (diff)
[REF] mrp: workorders for enterprise
Removed abstract workorder Removed the integration of expiry wizard in tablet view since it should be moved in enterprise in the tablet view implementation (not possible to set consumed lot on workorders on community anymore) Refactor _set_quantity_done to use ORM command in order to not return a dict with vals to_create/to_write task-2241471
Diffstat (limited to 'addons')
-rw-r--r--addons/mrp/models/__init__.py1
-rw-r--r--addons/mrp/models/mrp_production.py79
-rw-r--r--addons/mrp/models/mrp_workorder.py399
-rw-r--r--addons/mrp/models/stock_move.py33
-rw-r--r--addons/mrp/security/ir.model.access.csv2
-rw-r--r--addons/mrp/tests/__init__.py1
-rw-r--r--addons/mrp/tests/test_backorder.py38
-rw-r--r--addons/mrp/tests/test_cancel_mo.py67
-rw-r--r--addons/mrp/tests/test_procurement.py2
-rw-r--r--addons/mrp/tests/test_stock.py41
-rw-r--r--addons/mrp/tests/test_workorder_operation.py1785
-rw-r--r--addons/mrp/views/mrp_production_views.xml38
-rw-r--r--addons/mrp/views/mrp_workorder_views.xml2
-rw-r--r--addons/mrp/wizard/change_production_qty.py13
-rw-r--r--addons/mrp_account/tests/test_mrp_account.py111
-rw-r--r--addons/mrp_product_expiry/models/__init__.py2
-rw-r--r--addons/mrp_product_expiry/models/mrp_workorder.py38
-rw-r--r--addons/mrp_product_expiry/tests/test_mrp_product_expiry.py68
-rw-r--r--addons/mrp_subcontracting/models/stock_picking.py8
-rw-r--r--addons/stock/models/stock_move.py16
20 files changed, 284 insertions, 2460 deletions
diff --git a/addons/mrp/models/__init__.py b/addons/mrp/models/__init__.py
index 237e0273151..c6b32ff80fc 100644
--- a/addons/mrp/models/__init__.py
+++ b/addons/mrp/models/__init__.py
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mrp_document
-from . import mrp_abstract_workorder
from . import res_config_settings
from . import mrp_bom
from . import mrp_routing
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index f45f5e65373..44d3589b113 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -539,7 +539,6 @@ class MrpProduction(models.Model):
@api.onchange('product_qty', 'product_uom_id')
def _onchange_product_qty(self):
- product_qty = self.product_uom_id._compute_quantity(self.product_qty, self.product_uom_id)
for workorder in self.workorder_ids:
if not workorder.product_uom_id:
workorder.product_uom_id = self.product_uom_id
@@ -601,25 +600,7 @@ class MrpProduction(models.Model):
@api.onchange('qty_producing', 'lot_producing_id')
def _onchange_producing(self):
- if self.product_id.tracking == 'serial':
- qty_producing_uom = self.product_uom_id._compute_quantity(self.qty_producing, self.product_id.uom_id, rounding_method='HALF-UP')
- if qty_producing_uom != 1:
- self.qty_producing = self.product_id.uom_id._compute_quantity(1, self.product_uom_id, rounding_method='HALF-UP')
- for move in (self.move_raw_ids | self.move_finished_ids.filtered(lambda m: m.product_id != self.product_id)):
- if move.state in ('done', 'cancel'):
- continue
- # Do not update extra product quantities
- if float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding):
- continue
- if move.has_tracking != 'none' or move.state == 'done':
- continue
- vals = move._update_quantity_done(self)
- if vals.get('to_create'):
- for res in vals['to_create']:
- move.move_line_ids.new(res)
- if vals.get('to_write'):
- for move_line, res in vals['to_write']:
- move_line.update(res)
+ self._set_qty_producing()
@api.onchange('bom_id')
def _onchange_workorder_ids(self):
@@ -723,7 +704,6 @@ class MrpProduction(models.Model):
'product_uom_id': production.product_id.uom_id.id,
'operation_id': operation.id,
'state': 'pending',
- 'qty_producing': production.product_id.tracking == 'serial' and 1.0 or product_qty,
'consumption': production.consumption,
}]
production.workorder_ids = [(5, 0)] + [(0, 0, value) for value in workorders_values]
@@ -826,6 +806,19 @@ class MrpProduction(models.Model):
}
return data
+ def _set_qty_producing(self):
+ if self.product_id.tracking == 'serial':
+ qty_producing_uom = self.product_uom_id._compute_quantity(self.qty_producing, self.product_id.uom_id, rounding_method='HALF-UP')
+ if qty_producing_uom != 1:
+ self.qty_producing = self.product_id.uom_id._compute_quantity(1, self.product_uom_id, rounding_method='HALF-UP')
+
+ for move in (self.move_raw_ids | self.move_finished_ids.filtered(lambda m: m.product_id != self.product_id)):
+ if move._should_bypass_set_qty_producing():
+ continue
+ new_qty = self.product_uom_id._compute_quantity((self.qty_producing - self.qty_produced) * move.unit_factor, self.product_uom_id, 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)
+
def _update_raw_move(self, bom_line, line_data):
""" :returns update_move, old_quantity, new_quantity """
quantity = line_data['qty']
@@ -981,24 +974,7 @@ class MrpProduction(models.Model):
if self.move_finished_ids.filtered(lambda m: m.product_id == self.product_id).move_line_ids:
self.move_finished_ids.filtered(lambda m: m.product_id == self.product_id).move_line_ids.lot_id = self.lot_producing_id
if self.product_id.tracking == 'serial':
- # copy/paste from _onchange_producing
- qty_producing_uom = self.product_uom_id._compute_quantity(self.qty_producing, self.product_id.uom_id, rounding_method='HALF-UP')
- if qty_producing_uom != 1:
- self.qty_producing = self.product_id.uom_id._compute_quantity(1, self.product_uom_id, rounding_method='HALF-UP')
- for move in (self.move_raw_ids | self.move_finished_ids.filtered(lambda m: m.product_id != self.product_id)):
- if move.state in ('done', 'cancel'):
- continue
- if float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding):
- continue
- if move.has_tracking != 'none':
- continue
- vals = move._update_quantity_done(self)
- if vals.get('to_create'):
- for res in vals['to_create']:
- move.move_line_ids.create(res)
- if vals.get('to_write'):
- for move_line, res in vals['to_write']:
- move_line.write(res)
+ self._set_qty_producing()
def action_confirm(self):
self._check_company()
@@ -1265,8 +1241,6 @@ class MrpProduction(models.Model):
moves_to_finish = order.move_finished_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
moves_to_finish = moves_to_finish._action_done(cancel_backorder=cancel_backorder)
- order.workorder_ids.mapped('raw_workorder_line_ids').unlink()
- order.workorder_ids.mapped('finished_workorder_line_ids').unlink()
order.action_assign()
consume_move_lines = moves_to_do.mapped('move_line_ids')
order.move_finished_ids.move_line_ids.consume_line_ids = [(6, 0, consume_move_lines.ids)]
@@ -1294,7 +1268,7 @@ class MrpProduction(models.Model):
'origin': mo_source.origin
}
- def _generate_backorder_productions(self):
+ def _generate_backorder_productions(self, close_mo=True):
backorders = self.env['mrp.production']
for production in self:
if production.backorder_sequence == 0: # Activate backorder naming
@@ -1307,17 +1281,26 @@ class MrpProduction(models.Model):
wo.qty_producing = 1
else:
wo.qty_producing = wo.qty_remaining
-
- production.move_raw_ids.filtered(lambda m: m.state not in ('done', 'cancel')).write({
- 'raw_material_production_id': backorder_mo.id,
- })
+ if close_mo:
+ production.move_raw_ids.filtered(lambda m: m.state not in ('done', 'cancel')).write({
+ 'raw_material_production_id': backorder_mo.id,
+ })
+ production.move_finished_ids.filtered(lambda m: m.state not in ('done', 'cancel')).write({
+ 'production_id': backorder_mo.id,
+ })
+ else:
+ for move in production.move_raw_ids | production.move_finished_ids:
+ new_move = self.env['stock.move'].browse(move._split(move.unit_factor * production.qty_producing))
+ if move.raw_material_production_id:
+ new_move.raw_material_production_id = backorder_mo.id
+ else:
+ new_move.production_id = backorder_mo.id
backorders |= backorder_mo
production.name = self._get_name_backorder(production.name, production.backorder_sequence)
for wo in backorder_mo.workorder_ids:
- wo.duration_expected = wo._get_duration_expected()
-
+ wo.duration_expected = wo._get_duration_expected(wo.workcenter_id)
backorders.action_confirm()
# Remove the serial move line without reserved quantity. Post inventory will assigned all the non done moves
# So those move lines are duplicated.
diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py
index 1542c480e90..5272acd24b3 100644
--- a/addons/mrp/models/mrp_workorder.py
+++ b/addons/mrp/models/mrp_workorder.py
@@ -14,7 +14,7 @@ from odoo.tools import float_compare, float_round, format_datetime
class MrpWorkorder(models.Model):
_name = 'mrp.workorder'
_description = 'Work Order'
- _inherit = ['mail.thread', 'mail.activity.mixin', 'mrp.abstract.workorder']
+ _inherit = ['mail.thread', 'mail.activity.mixin']
def _read_group_workcenter_id(self, workcenters, domain, order):
workcenter_ids = self.env.context.get('default_workcenter_id')
@@ -25,10 +25,6 @@ class MrpWorkorder(models.Model):
name = fields.Char(
'Work Order', required=True,
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
- company_id = fields.Many2one(
- 'res.company', 'Company',
- default=lambda self: self.env.company,
- required=True, index=True, readonly=True)
workcenter_id = fields.Many2one(
'mrp.workcenter', 'Work Center', required=True,
states={'done': [('readonly', True)], 'cancel': [('readonly', True)], 'progress': [('readonly', True)]},
@@ -36,6 +32,11 @@ class MrpWorkorder(models.Model):
working_state = fields.Selection(
string='Workcenter Status', related='workcenter_id.working_state', readonly=False,
help='Technical: used in views only')
+ product_id = fields.Many2one(related='production_id.product_id', readonly=True, store=True, check_company=True)
+ product_tracking = fields.Selection(related="product_id.tracking")
+ product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure', required=True, readonly=True)
+ use_create_components_lots = fields.Boolean(related="production_id.picking_type_id.use_create_components_lots")
+ production_id = fields.Many2one('mrp.production', 'Manufacturing Order', required=True, check_company=True)
production_availability = fields.Selection(
string='Stock Availability', readonly=True,
related='production_id.reservation_state', store=True,
@@ -45,6 +46,10 @@ class MrpWorkorder(models.Model):
related='production_id.state',
help='Technical: used in views only.')
qty_production = fields.Float('Original Production Quantity', readonly=True, related='production_id.product_qty')
+ company_id = fields.Many2one(related='production_id.company_id')
+ qty_producing = fields.Float(
+ compute='_compute_qty_producing', inverse='_set_qty_producing',
+ string='Currently Produced Quantity', digits='Product Unit of Measure')
qty_remaining = fields.Float('Quantity To Be Produced', compute='_compute_qty_remaining', digits='Product Unit of Measure')
qty_produced = fields.Float(
'Quantity', default=0.0,
@@ -118,8 +123,9 @@ class MrpWorkorder(models.Model):
'stock.move.line', 'workorder_id', 'Moves to Track',
help="Inventory moves for which you must scan a lot number at this work order")
finished_lot_id = fields.Many2one(
- 'stock.production.lot', 'Lot/Serial Number', domain="[('id', 'in', allowed_lots_domain)]",
- states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, check_company=True)
+ 'stock.production.lot', string='Lot/Serial Number', compute='_compute_finished_lot_id',
+ inverse='_set_finished_lot_id', domain="[('product_id', '=', product_id), ('company_id', '=', company_id)]",
+ check_company=True)
time_ids = fields.One2many(
'mrp.workcenter.productivity', 'workorder_id', copy=False)
is_user_working = fields.Boolean(
@@ -132,14 +138,15 @@ class MrpWorkorder(models.Model):
scrap_ids = fields.One2many('stock.scrap', 'workorder_id')
scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
production_date = fields.Datetime('Production Date', related='production_id.date_planned_start', store=True, readonly=False)
- raw_workorder_line_ids = fields.One2many('mrp.workorder.line',
- 'raw_workorder_id', string='Components')
- finished_workorder_line_ids = fields.One2many('mrp.workorder.line',
- 'finished_workorder_id', string='By-products')
- allowed_lots_domain = fields.One2many(comodel_name='stock.production.lot', compute="_compute_allowed_lots_domain")
is_finished_lines_editable = fields.Boolean(compute='_compute_is_finished_lines_editable')
json_popover = fields.Char('Popover Data JSON', compute='_compute_json_popover')
show_json_popover = fields.Boolean('Show Popover?', compute='_compute_json_popover')
+ consumption = fields.Selection([
+ ('strict', 'Strict'),
+ ('warning', 'Warning'),
+ ('flexible', 'Flexible')],
+ required=True,
+ )
@api.depends('production_state', 'date_planned_start', 'date_planned_finished')
def _compute_json_popover(self):
@@ -197,6 +204,25 @@ class MrpWorkorder(models.Model):
'replan': color_icon not in [False, 'text-primary']
})
+ @api.depends('production_id.lot_producing_id')
+ def _compute_finished_lot_id(self):
+ for workorder in self:
+ workorder.finished_lot_id = workorder.production_id.lot_producing_id
+
+ def _set_finished_lot_id(self):
+ for workorder in self:
+ workorder.production_id.lot_producing_id = workorder.finished_lot_id
+
+ @api.depends('production_id.qty_producing')
+ def _compute_qty_producing(self):
+ for workorder in self:
+ workorder.qty_producing = workorder.production_id.qty_producing
+
+ def _set_qty_producing(self):
+ for workorder in self:
+ workorder.production_id.qty_producing = workorder.qty_producing
+ workorder.production_id._set_qty_producing()
+
# Both `date_planned_start` and `date_planned_finished` are related fields on `leave_id`. Let's say
# we slide a workorder on a gantt view, a single call to write is made with both
# fields Changes. As the ORM doesn't batch the write on related fields and instead
@@ -224,53 +250,6 @@ class MrpWorkorder(models.Model):
workorder.is_finished_lines_editable = True
else:
workorder.is_finished_lines_editable = False
-
- @api.depends('production_id.workorder_ids.finished_workorder_line_ids',
- 'production_id.workorder_ids.finished_workorder_line_ids.qty_done',
- 'production_id.workorder_ids.finished_workorder_line_ids.lot_id')
- def _compute_allowed_lots_domain(self):
- """ Check if all the finished products has been assigned to a serial
- number or a lot in other workorders. If yes, restrict the selectable lot
- to the lot/sn used in other workorders.
- """
- productions = self.mapped('production_id')
- treated = self.browse()
- for production in productions:
- if production.state == 'draft':
- continue
- if production.product_id.tracking == 'none':
- continue
-
- rounding = production.product_uom_id.rounding
- finished_workorder_lines = production.workorder_ids.mapped('finished_workorder_line_ids').filtered(lambda wl: wl.product_id == production.product_id)
- qties_done_per_lot = defaultdict(list)
- for finished_workorder_line in finished_workorder_lines:
- # It is possible to have finished workorder lines without a lot (eg using the dummy
- # test type). Ignore them when computing the allowed lots.
- if finished_workorder_line.lot_id:
- qties_done_per_lot[finished_workorder_line.lot_id.id].append(finished_workorder_line.qty_done)
-
- qty_to_produce = production.product_qty
- allowed_lot_ids = self.env['stock.production.lot']
- qty_produced = sum([max(qty_dones) for qty_dones in qties_done_per_lot.values()])
- if float_compare(qty_produced, qty_to_produce, precision_rounding=rounding) < 0:
- # If we haven't produced enough, all lots are available
- allowed_lot_ids = self.env['stock.production.lot'].search([
- ('product_id', '=', production.product_id.id),
- ('company_id', '=', production.company_id.id),
- ])
- else:
- # If we produced enough, only the already produced lots are available
- allowed_lot_ids = self.env['stock.production.lot'].browse(qties_done_per_lot.keys())
- workorders = production.workorder_ids.filtered(lambda wo: wo.state not in ('done', 'cancel'))
- for workorder in workorders:
- if workorder.product_tracking == 'serial':
- workorder.allowed_lots_domain = allowed_lot_ids - workorder.finished_workorder_line_ids.filtered(lambda wl: wl.product_id == production.product_id).mapped('lot_id')
- else:
- workorder.allowed_lots_domain = allowed_lot_ids
- treated |= workorder
- (self - treated).allowed_lots_domain = False
-
def name_get(self):
res = []
for wo in self:
@@ -339,18 +318,6 @@ class MrpWorkorder(models.Model):
for workorder in self:
workorder.scrap_count = count_data.get(workorder.id, 0)
- @api.onchange('finished_lot_id')
- def _onchange_finished_lot_id(self):
- """When the user changes the lot being currently produced, suggest
- a quantity to produce consistent with the previous workorders. """
- previous_wo = self.env['mrp.workorder'].search([
- ('next_work_order_id', '=', self.id)
- ])
- if previous_wo:
- line = previous_wo.finished_workorder_line_ids.filtered(lambda line: line.product_id == self.product_id and line.lot_id == self.finished_lot_id)
- if line:
- self.qty_producing = line.qty_done
-
@api.onchange('date_planned_finished')
def _onchange_date_planned_finished(self):
if self.date_planned_start and self.date_planned_finished:
@@ -453,196 +420,15 @@ class MrpWorkorder(models.Model):
'workorder_id': workorders_by_bom[production.bom_id][-1:].id
})
- for workorder in workorders:
- workorder._apply_update_workorder_lines()
-
for workorders in workorders_by_bom.values():
if workorders[0].state == 'pending':
workorders[0].state = 'ready'
for workorder in workorders:
workorder._start_nextworkorder()
- def _generate_wo_lines(self):
- """ Generate workorder line """
- self.ensure_one()
- moves = (self.move_raw_ids | self.move_finished_ids).filtered(
- lambda move: move.state not in ('done', 'cancel')
- )
- for move in moves:
- qty_to_consume = self._prepare_component_quantity(move, self.qty_producing)
- line_values = self._generate_lines_values(move, qty_to_consume)
- self.env['mrp.workorder.line'].create(line_values)
-
- def _apply_update_workorder_lines(self):
- """ update existing line on the workorder. It could be trigger manually
- after a modification of qty_producing.
- """
- self.ensure_one()
- line_values = self._update_workorder_lines()
- self.env['mrp.workorder.line'].create(line_values['to_create'])
- if line_values['to_delete']:
- line_values['to_delete'].unlink()
- for line, vals in line_values['to_update'].items():
- line.write(vals)
-
- def _refresh_wo_lines(self):
- """ Modify exisiting workorder line in order to match the reservation on
- stock move line. The strategy is to remove the line that were not
- processed yet then call _generate_lines_values that recreate workorder
- line depending the reservation.
- """
- for workorder in self:
- raw_moves = workorder.move_raw_ids.filtered(
- lambda move: move.state not in ('done', 'cancel')
- )
- wl_to_unlink = self.env['mrp.workorder.line']
- for move in raw_moves:
- rounding = move.product_uom.rounding
- qty_already_consumed = 0.0
- workorder_lines = workorder.raw_workorder_line_ids.filtered(lambda w: w.move_id == move)
- for wl in workorder_lines:
- if not wl.qty_done:
- wl_to_unlink |= wl
- continue
-
- qty_already_consumed += wl.qty_done
- qty_to_consume = self._prepare_component_quantity(move, workorder.qty_producing)
- wl_to_unlink.unlink()
- if float_compare(qty_to_consume, qty_already_consumed, precision_rounding=rounding) > 0:
- line_values = workorder._generate_lines_values(move, qty_to_consume - qty_already_consumed)
- self.env['mrp.workorder.line'].create(line_values)
-
- def _defaults_from_finished_workorder_line(self, reference_lot_lines):
- for r_line in reference_lot_lines:
- # see which lot we could suggest and its related qty_producing
- if not r_line.lot_id:
- continue
- candidates = self.finished_workorder_line_ids.filtered(lambda line: line.lot_id == r_line.lot_id)
- rounding = self.product_uom_id.rounding
- if not candidates:
- self.write({
- 'finished_lot_id': r_line.lot_id.id,
- 'qty_producing': r_line.qty_done,
- })
- return True
- elif float_compare(candidates.qty_done, r_line.qty_done, precision_rounding=rounding) < 0:
- self.write({
- 'finished_lot_id': r_line.lot_id.id,
- 'qty_producing': r_line.qty_done - candidates.qty_done,
- })
- return True
- return False
-
- def record_production(self):
- if not self:
- return True
-
- self.ensure_one()
- self._check_sn_uniqueness()
- self._check_company()
- if float_compare(self.qty_producing, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
- raise UserError(_('Please set the quantity you are currently producing. It should be different from zero.'))
- if self.production_id.product_id.tracking != 'none' and not self.finished_lot_id and self.move_raw_ids:
- raise UserError(_('You should provide a lot for the final product'))
- if 'check_ids' not in self:
- for line in self.raw_workorder_line_ids | self.finished_workorder_line_ids:
- line._check_line_sn_uniqueness()
- # If last work order, then post lots used
- if not self.next_work_order_id:
- self._update_finished_move()
-
- # Transfer quantities from temporary to final move line or make them final
- self._update_moves()
-
- # Transfer lot (if present) and quantity produced to a finished workorder line
- if self.product_tracking != 'none':
- self._create_or_update_finished_line()
-
- # Update workorder quantity produced
- self.qty_produced += self.qty_producing
-
- # Suggest a finished lot on the next workorder
- if self.next_work_order_id and self.product_tracking != 'none' and (not self.next_work_order_id.finished_lot_id or self.next_work_order_id.finished_lot_id == self.finished_lot_id):
- self.next_work_order_id._defaults_from_finished_workorder_line(self.finished_workorder_line_ids)
- # As we may have changed the quantity to produce on the next workorder,
- # make sure to update its wokorder lines
- self.next_work_order_id._apply_update_workorder_lines()
-
- # One a piece is produced, you can launch the next work order
- self._start_nextworkorder()
-
- # Test if the production is done
- rounding = self.production_id.product_uom_id.rounding
- if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) < 0:
- previous_wo = self.env['mrp.workorder']
- if self.product_tracking != 'none':
- previous_wo = self.env['mrp.workorder'].search([
- ('next_work_order_id', '=', self.id)
- ])
- candidate_found_in_previous_wo = False
- if previous_wo:
- candidate_found_in_previous_wo = self._defaults_from_finished_workorder_line(previous_wo.finished_workorder_line_ids)
- if not candidate_found_in_previous_wo:
- # self is the first workorder
- self.qty_producing = self.qty_remaining
- self.finished_lot_id = False
- if self.product_tracking == 'serial':
- self.qty_producing = 1
-
- self._apply_update_workorder_lines()
- else:
- self.qty_producing = 0
- self.button_finish()
- return True
-
def _get_byproduct_move_to_update(self):
return self.production_id.move_finished_ids.filtered(lambda x: (x.product_id.id != self.production_id.product_id.id) and (x.state not in ('done', 'cancel')))
- def _create_or_update_finished_line(self):
- """
- 1. Check that the final lot and the quantity producing is valid regarding
- other workorders of this production
- 2. Save final lot and quantity producing to suggest on next workorder
- """
- self.ensure_one()
- final_lot_quantity = self.qty_production
- rounding = self.product_uom_id.rounding
- # Get the max quantity possible for current lot in other workorders
- for workorder in (self.production_id.workorder_ids - self):
- # We add the remaining quantity to the produced quantity for the
- # current lot. For 5 finished products: if in the first wo it
- # creates 4 lot A and 1 lot B and in the second it create 3 lot A
- # and it remains 2 units to product, it could produce 5 lot A.
- # In this case we select 4 since it would conflict with the first
- # workorder otherwise.
- line = workorder.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.finished_lot_id)
- line_without_lot = workorder.finished_workorder_line_ids.filtered(lambda line: line.product_id == workorder.product_id and not line.lot_id)
- quantity_remaining = workorder.qty_remaining + line_without_lot.qty_done
- quantity = line.qty_done + quantity_remaining
- if line and float_compare(quantity, final_lot_quantity, precision_rounding=rounding) <= 0:
- final_lot_quantity = quantity
- elif float_compare(quantity_remaining, final_lot_quantity, precision_rounding=rounding) < 0:
- final_lot_quantity = quantity_remaining
-
- # final lot line for this lot on this workorder.
- current_lot_lines = self.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.finished_lot_id)
-
- # this lot has already been produced
- if float_compare(final_lot_quantity, current_lot_lines.qty_done + self.qty_producing, precision_rounding=rounding) < 0:
- raise UserError(_('You have produced %s %s of lot %s in the previous workorder. You are trying to produce %s in this one') %
- (final_lot_quantity, self.product_id.uom_id.name, self.finished_lot_id.name, current_lot_lines.qty_done + self.qty_producing))
-
- # Update workorder line that regiter final lot created
- if not current_lot_lines:
- current_lot_lines = self.env['mrp.workorder.line'].create({
- 'finished_workorder_id': self.id,
- 'product_id': self.product_id.id,
- 'lot_id': self.finished_lot_id.id,
- 'qty_done': self.qty_producing,
- })
- else:
- current_lot_lines.qty_done += self.qty_producing
-
def _start_nextworkorder(self):
rounding = self.product_id.uom_id.rounding
if self.next_work_order_id.state == 'pending' and (
@@ -910,46 +696,75 @@ class MrpWorkorder(models.Model):
res[wo1].append(wo2)
return res
-
-class MrpWorkorderLine(models.Model):
- _name = 'mrp.workorder.line'
- _inherit = ["mrp.abstract.workorder.line"]
- _description = "Workorder move line"
-
- raw_workorder_id = fields.Many2one('mrp.workorder', 'Component for Workorder',
- ondelete='cascade')
- finished_workorder_id = fields.Many2one('mrp.workorder', 'Finished Product for Workorder',
- ondelete='cascade')
-
- @api.onchange('qty_to_consume')
- def _onchange_qty_to_consume(self):
- # Update qty_done for products added in ready state
- wo = self.raw_workorder_id or self.finished_workorder_id
- if wo.state == 'ready':
- self.qty_done = self.qty_to_consume
-
- @api.model_create_multi
- def create(self, vals_list):
- res = super().create(vals_list)
- for line in res:
- wo = line.raw_workorder_id
- if wo and\
- wo.consumption == 'strict' and\
- wo.state == 'progress' and\
- line.product_id.id not in wo.production_id.bom_id.bom_line_ids.product_id.ids:
- raise UserError(_('You cannot consume additional component as the consumption defined on the Bill of Material is set to "strict"'))
- return res
-
@api.model
- def _get_raw_workorder_inverse_name(self):
- return 'raw_workorder_id'
-
- @api.model
- def _get_finished_workoder_inverse_name(self):
- return 'finished_workorder_id'
+ def _prepare_component_quantity(self, move, qty_producing):
+ """ helper that computes quantity to consume (or to create in case of byproduct)
+ depending on the quantity producing and the move's unit factor"""
+ if move.product_id.tracking == 'serial':
+ uom = move.product_id.uom_id
+ else:
+ uom = move.product_uom
+ return move.product_uom._compute_quantity(
+ qty_producing * move.unit_factor,
+ uom,
+ round=False
+ )
- def _get_final_lots(self):
- return (self.raw_workorder_id or self.finished_workorder_id).finished_lot_id
+ def _update_finished_move(self):
+ """ Update the finished move & move lines in order to set the finished
+ product lot on it as well as the produced quantity. This method get the
+ information either from the last workorder or from the Produce wizard."""
+ production_move = self.production_id.move_finished_ids.filtered(
+ lambda move: move.product_id == self.product_id and
+ move.state not in ('done', 'cancel')
+ )
+ if production_move and production_move.product_id.tracking != 'none':
+ if not self.finished_lot_id:
+ raise UserError(_('You need to provide a lot for the finished product.'))
+ move_line = production_move.move_line_ids.filtered(
+ lambda line: line.lot_id.id == self.finished_lot_id.id
+ )
+ if move_line:
+ if self.product_id.tracking == 'serial':
+ raise UserError(_('You cannot produce the same serial number twice.'))
+ move_line.product_uom_qty += self.qty_producing
+ move_line.qty_done += self.qty_producing
+ else:
+ location_dest_id = production_move.location_dest_id._get_putaway_strategy(self.product_id).id or production_move.location_dest_id.id
+ move_line.create({
+ 'move_id': production_move.id,
+ 'product_id': production_move.product_id.id,
+ 'lot_id': self.finished_lot_id.id,
+ 'product_uom_qty': self.qty_producing,
+ 'product_uom_id': self.product_uom_id.id,
+ 'qty_done': self.qty_producing,
+ 'location_id': production_move.location_id.id,
+ 'location_dest_id': location_dest_id,
+ })
+ else:
+ rounding = production_move.product_uom.rounding
+ production_move._set_quantity_done(
+ float_round(self.qty_producing, precision_rounding=rounding)
+ )
- def _get_production(self):
- return (self.raw_workorder_id or self.finished_workorder_id).production_id
+ def _strict_consumption_check(self):
+ if self.consumption == 'strict':
+ for move in self.move_raw_ids:
+ qty_done = 0.0
+ for line in move.move_line_ids:
+ qty_done += line.product_uom_id._compute_quantity(line.qty_done, move.product_uom)
+ rounding = move.product_uom_id.rounding
+ if float_compare(qty_done, move.product_uom_qty, precision_rounding=rounding) != 0:
+ raise UserError(_('You should consume the quantity of %s defined in the BoM. If you want to consume more or less components, change the consumption setting on the BoM.') % move.product_id.name)
+
+ def _check_sn_uniqueness(self):
+ """ Alert the user if the serial number as already been produced """
+ if self.product_tracking == 'serial' and self.finished_lot_id:
+ sml = self.env['stock.move.line'].search_count([
+ ('lot_id', '=', self.finished_lot_id.id),
+ ('location_id.usage', '=', 'production'),
+ ('qty_done', '=', 1),
+ ('state', '=', 'done')
+ ])
+ if sml:
+ raise UserError(_('This serial number for product %s has already been produced') % self.product_id.name)
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index d711b6561c1..808c2aa3ddd 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -3,7 +3,7 @@
from odoo import api, exceptions, fields, models, _
from odoo.exceptions import UserError
-from odoo.tools import float_compare, float_round, float_repr, float_is_zero
+from odoo.tools import float_compare, float_round, float_is_zero
class StockMoveLine(models.Model):
@@ -188,12 +188,6 @@ class StockMove(models.Model):
if self.raw_material_production_id and self.has_tracking == 'none':
mo = self.raw_material_production_id
vals = self._update_quantity_done(mo)
- if vals.get('to_create'):
- for res in vals['to_create']:
- self.move_line_ids.new(res)
- if vals.get('to_write'):
- for move_line, res in vals['to_write']:
- move_line.update(res)
@api.model
def default_get(self, fields_list):
@@ -277,6 +271,11 @@ class StockMove(models.Model):
production._action_cancel()
return res
+ def _prepare_move_split_vals(self, qty):
+ defaults = super()._prepare_move_split_vals(qty)
+ defaults['workorder_id'] = False
+ return defaults
+
def _prepare_phantom_move_values(self, bom_line, product_qty, quantity_done):
return {
'picking_id': self.picking_id.id if self.picking_id else False,
@@ -298,10 +297,10 @@ class StockMove(models.Model):
return vals
def _get_upstream_documents_and_responsibles(self, visited):
- if self.production_id and self.production_id.state not in ('done', 'cancel'):
- return [(self.production_id, self.production_id.user_id, visited)]
- else:
- return super(StockMove, self)._get_upstream_documents_and_responsibles(visited)
+ if self.production_id and self.production_id.state not in ('done', 'cancel'):
+ return [(self.production_id, self.production_id.user_id, visited)]
+ else:
+ return super(StockMove, self)._get_upstream_documents_and_responsibles(visited)
def _delay_alert_get_documents(self):
res = super(StockMove, self)._delay_alert_get_documents()
@@ -312,6 +311,16 @@ class StockMove(models.Model):
res = super(StockMove, self)._should_be_assigned()
return bool(res and not (self.production_id or self.raw_material_production_id))
+ def _should_bypass_set_qty_producing(self):
+ if self.state in ('done', 'cancel'):
+ return True
+ # Do not update extra product quantities
+ if float_is_zero(self.product_uom_qty, precision_rounding=self.product_uom.rounding):
+ return True
+ if self.has_tracking != 'none' or self.state == 'done':
+ return True
+ return False
+
def _should_bypass_reservation(self):
res = super(StockMove, self)._should_bypass_reservation()
return bool(res and not self.production_id)
@@ -390,7 +399,7 @@ class StockMove(models.Model):
new_qty = mo.product_uom_id._compute_quantity((mo.qty_producing - mo.qty_produced) * self.unit_factor, mo.product_uom_id, rounding_method='HALF-UP')
if not self.is_quantity_done_editable:
self.move_line_ids.filtered(lambda ml: ml.state not in ('done', 'cancel')).qty_done = 0
- ml_values = self._set_quantity_done_prepare_vals(new_qty)
+ self.move_line_ids = self._set_quantity_done_prepare_vals(new_qty)
else:
self.quantity_done = new_qty
return ml_values
diff --git a/addons/mrp/security/ir.model.access.csv b/addons/mrp/security/ir.model.access.csv
index ab13940651c..73d4f9c3888 100644
--- a/addons/mrp/security/ir.model.access.csv
+++ b/addons/mrp/security/ir.model.access.csv
@@ -26,8 +26,6 @@ access_product_supplierinfo_user,product.supplierinfo user,product.model_product
access_res_partner,res.partner,base.model_res_partner,mrp.group_mrp_user,1,0,0,0
access_mrp_workorder_mrp_user,mrp.workorder.user,model_mrp_workorder,mrp.group_mrp_user,1,1,1,1
access_mrp_workorder_mrp_manager,mrp.workorder,model_mrp_workorder,mrp.group_mrp_manager,1,1,1,1
-access_mrp_workorder_line_mrp_user,mrp.workorder.user,model_mrp_workorder_line,mrp.group_mrp_user,1,1,1,1
-access_mrp_workorder_line_mrp_manager,mrp.workorder,model_mrp_workorder_line,mrp.group_mrp_manager,1,1,1,1
access_resource_calendar_leaves_user,mrp.resource.calendar.leaves.user,resource.model_resource_calendar_leaves,mrp.group_mrp_user,1,1,1,1
access_resource_calendar_leaves_manager,mrp.resource.calendar.leaves.manager,resource.model_resource_calendar_leaves,mrp.group_mrp_manager,1,0,0,0
access_resource_calendar_attendance_mrp_user,mrp.resource.calendar.attendance.mrp.user,resource.model_resource_calendar_attendance,mrp.group_mrp_user,1,1,1,1
diff --git a/addons/mrp/tests/__init__.py b/addons/mrp/tests/__init__.py
index ec5322adba3..2af9d670b87 100644
--- a/addons/mrp/tests/__init__.py
+++ b/addons/mrp/tests/__init__.py
@@ -6,7 +6,6 @@ from . import test_cancel_mo
from . import test_order
from . import test_stock
from . import test_warehouse_multistep_manufacturing
-from . import test_workorder_operation
from . import test_procurement
from . import test_unbuild
from . import test_oee
diff --git a/addons/mrp/tests/test_backorder.py b/addons/mrp/tests/test_backorder.py
index 0c3361781ad..a55b957184e 100644
--- a/addons/mrp/tests/test_backorder.py
+++ b/addons/mrp/tests/test_backorder.py
@@ -327,41 +327,3 @@ class TestMrpWorkorderBackorder(SavepointCase):
})
cls.bom_finished1.bom_line_ids[0].operation_id = cls.bom_finished1.operation_ids[0].id
cls.bom_finished1.bom_line_ids[1].operation_id = cls.bom_finished1.operation_ids[1].id
-
- def test_1(self):
- """Operations are set on `self.bom_kit1` but none on `self.bom_finished1`."""
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.finished1
- mo_form.bom_id = self.bom_finished1
- mo_form.product_qty = 2.0
- mo = mo_form.save()
-
- mo.action_confirm()
- mo.button_plan()
-
- self.assertEqual(len(mo.workorder_ids), 2)
-
- workorder1 = mo.workorder_ids[0]
- workorder2 = mo.workorder_ids[1]
-
- self.assertEqual(workorder1.raw_workorder_line_ids.product_id, self.compfinished1)
- self.assertEqual(workorder1.raw_workorder_line_ids.qty_to_consume, 2)
-
- workorder1.button_start()
- workorder1.button_finish()
-
- self.assertEqual(workorder2.raw_workorder_line_ids.product_id, self.compfinished2)
- self.assertEqual(workorder2.raw_workorder_line_ids.qty_to_consume, 2)
-
- workorder2.button_start()
- workorder2.button_finish()
-
- action = mo.with_context(debug=True).button_mark_done()
- backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
- backorder.save().action_backorder()
- mo_backorder = mo.procurement_group_id.mrp_production_ids[-1]
- mo_backorder.button_plan()
- self.assertEqual(len(mo_backorder.workorder_ids), 2)
-
- self.assertEqual(workorder1.state, 'done')
- self.assertEqual(workorder2.state, 'done')
diff --git a/addons/mrp/tests/test_cancel_mo.py b/addons/mrp/tests/test_cancel_mo.py
index 20f0e9457c7..d305571c8b9 100644
--- a/addons/mrp/tests/test_cancel_mo.py
+++ b/addons/mrp/tests/test_cancel_mo.py
@@ -76,73 +76,6 @@ class TestMrpCancelMO(TestMrpCommon):
self.assertEqual(manufacturing_order.move_finished_ids[1].state, 'cancel',
"The other move finished is cancelled like its MO.")
- def test_cancel_mo_with_routing(self):
- """ Cancel a Manufacturing Order with routing (so generate a Work Order)
- and produce some quantities. When cancelled, the MO must be marked as
- done and the WO must be cancelled.
- """
- # Create MO
- product_to_build = self.env['product.product'].create({
- 'name': 'Young Tom',
- 'type': 'product',
- })
- product_to_use_1 = self.env['product.product'].create({
- 'name': 'Botox',
- 'type': 'product',
- })
- product_to_use_2 = self.env['product.product'].create({
- 'name': 'Old Tom',
- 'type': 'product',
- })
- bom = self.env['mrp.bom'].create({
- 'product_id': product_to_build.id,
- 'product_tmpl_id': product_to_build.product_tmpl_id.id,
- 'product_uom_id': self.uom_unit.id,
- 'product_qty': 1.0,
- 'consumption': 'strict',
- 'type': 'normal',
- 'bom_line_ids': [
- (0, 0, {'product_id': product_to_use_2.id, 'product_qty': 4}),
- (0, 0, {'product_id': product_to_use_1.id, 'product_qty': 1})
- ],
- 'operation_ids': [
- (0, 0, {'name': 'Gift Wrap Maching', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 15, 'sequence': 1}),
- ]
- })
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = product_to_build
- mo_form.bom_id = bom
- mo_form.product_qty = 5.0
- manufacturing_order = mo_form.save()
- manufacturing_order.action_confirm()
-
- manufacturing_order.button_plan()
- workorder = manufacturing_order.workorder_ids
- # Produce some quantity
- workorder.button_start()
- workorder.qty_producing = 2
- workorder._apply_update_workorder_lines()
- workorder.record_production()
- # Post Inventory
- manufacturing_order._post_inventory()
- # Cancel it
- manufacturing_order.action_cancel()
- # Check MO is done, WO is cancelled and its SML are done or cancelled
- self.assertEqual(manufacturing_order.state, 'done', "MO should be in done state.")
- self.assertEqual(workorder.state, 'cancel', "WO should be cancelled.")
- self.assertEqual(manufacturing_order.move_raw_ids[0].state, 'done',
- "Due to 'post_inventory', some move raw must stay in done state")
- self.assertEqual(manufacturing_order.move_raw_ids[1].state, 'done',
- "Due to 'post_inventory', some move raw must stay in done state")
- self.assertEqual(manufacturing_order.move_raw_ids[2].state, 'cancel',
- "The other move raw are cancelled like their MO.")
- self.assertEqual(manufacturing_order.move_raw_ids[3].state, 'cancel',
- "The other move raw are cancelled like their MO.")
- self.assertEqual(manufacturing_order.move_finished_ids[0].state, 'done',
- "Due to 'post_inventory', a move finished must stay in done state")
- self.assertEqual(manufacturing_order.move_finished_ids[1].state, 'cancel',
- "The other move finished is cancelled like its MO.")
-
def test_unlink_mo(self):
""" Try to unlink a Manufacturing Order, and check it's possible or not
depending of the MO state (must be in cancel state to be unlinked, but
diff --git a/addons/mrp/tests/test_procurement.py b/addons/mrp/tests/test_procurement.py
index 89be67074bd..3d1281f013b 100644
--- a/addons/mrp/tests/test_procurement.py
+++ b/addons/mrp/tests/test_procurement.py
@@ -39,7 +39,7 @@ class TestProcurement(TestMrpCommon):
production_product_6.action_assign()
# check production state is Confirmed
- self.assertEqual(production_product_6.state, 'confirmed')
+ self.assertEqual(production_product_6.state, 'to_close')
# Check procurement for product 4 created or not.
# Check it created a purchase order
diff --git a/addons/mrp/tests/test_stock.py b/addons/mrp/tests/test_stock.py
index 7ca4b2493d3..64587e1cde9 100644
--- a/addons/mrp/tests/test_stock.py
+++ b/addons/mrp/tests/test_stock.py
@@ -158,47 +158,6 @@ class TestWarehouse(common.TestMrpCommon):
# scrap_move = production_3.move_raw_ids.filtered(lambda x: x.product_id == self.product_2 and x.scrapped)
# self.assertTrue(scrap_move, "There are no any scrap move created for production order.")
- def test_putaway_after_manufacturing_1(self):
- """ This test checks a manufactured product without tracking will go to
- location defined in putaway strategy.
- """
- mo_laptop = self.new_mo_laptop()
-
- mo_laptop.button_plan()
- workorder = mo_laptop.workorder_ids[0]
-
- workorder.button_start()
- workorder.record_production()
- mo_laptop.button_mark_done()
-
- # We check if the laptop go in the depot and not in the stock
- move = mo_laptop.move_finished_ids
- location_dest = move.move_line_ids.location_dest_id
- self.assertEqual(location_dest.id, self.depot_location.id)
- self.assertNotEqual(location_dest.id, self.stock_location.id)
-
- def test_putaway_after_manufacturing_2(self):
- """ This test checks a tracked manufactured product will go to location
- defined in putaway strategy.
- """
- self.laptop.tracking = 'serial'
- mo_laptop = self.new_mo_laptop()
-
- mo_laptop.button_plan()
- workorder = mo_laptop.workorder_ids[0]
-
- workorder.button_start()
- serial = self.env['stock.production.lot'].create({'product_id': self.laptop.id, 'company_id': self.env.company.id})
- workorder.finished_lot_id = serial
- workorder.record_production()
- mo_laptop.button_mark_done()
-
- # We check if the laptop go in the depot and not in the stock
- move = mo_laptop.move_finished_ids
- location_dest = move.move_line_ids.location_dest_id
- self.assertEqual(location_dest.id, self.depot_location.id)
- self.assertNotEqual(location_dest.id, self.stock_location.id)
-
def test_putaway_after_manufacturing_3(self):
""" This test checks a tracked manufactured product will go to location
defined in putaway strategy when the production is recorded with
diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py
deleted file mode 100644
index 190768adc9f..00000000000
--- a/addons/mrp/tests/test_workorder_operation.py
+++ /dev/null
@@ -1,1785 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from datetime import datetime, timedelta
-from odoo.tests import Form
-from odoo.tests.common import SavepointCase
-from odoo.addons.mrp.tests.common import TestMrpCommon
-from odoo.exceptions import ValidationError, UserError
-
-
-class TestWorkOrderProcessCommon(TestMrpCommon):
-
- @classmethod
- def setUpClass(cls):
- super(TestWorkOrderProcessCommon, cls).setUpClass()
- cls.source_location_id = cls.stock_location_14.id
- cls.warehouse = cls.env.ref('stock.warehouse0')
- # setting up alternative workcenters
- cls.wc_alt_1 = cls.env['mrp.workcenter'].create({
- 'name': 'Nuclear Workcenter bis',
- 'capacity': 3,
- 'time_start': 9,
- 'time_stop': 5,
- 'time_efficiency': 80,
- })
- cls.wc_alt_2 = cls.env['mrp.workcenter'].create({
- 'name': 'Nuclear Workcenter ter',
- 'capacity': 1,
- 'time_start': 10,
- 'time_stop': 5,
- 'time_efficiency': 85,
- })
- cls.product_4.uom_id = cls.uom_unit
- cls.planning_bom = cls.env['mrp.bom'].create({
- 'product_id': cls.product_4.id,
- 'product_tmpl_id': cls.product_4.product_tmpl_id.id,
- 'product_uom_id': cls.uom_unit.id,
- 'product_qty': 4.0,
- 'consumption': 'flexible',
- 'operation_ids': [
- (0, 0, {'name': 'Gift Wrap Maching', 'workcenter_id': cls.workcenter_1.id, 'time_cycle': 15, 'sequence': 1}),
- ],
- 'type': 'normal',
- 'bom_line_ids': [
- (0, 0, {'product_id': cls.product_2.id, 'product_qty': 2}),
- (0, 0, {'product_id': cls.product_1.id, 'product_qty': 4})
- ]})
- cls.dining_table = cls.env['product.product'].create({
- 'name': 'Table (MTO)',
- 'type': 'product',
- 'tracking': 'serial',
- })
- cls.product_table_sheet = cls.env['product.product'].create({
- 'name': 'Table Top',
- 'type': 'product',
- 'tracking': 'serial',
- })
- cls.product_table_leg = cls.env['product.product'].create({
- 'name': 'Table Leg',
- 'type': 'product',
- 'tracking': 'lot',
- })
- cls.product_bolt = cls.env['product.product'].create({
- 'name': 'Bolt',
- 'type': 'product',
- })
- cls.product_screw = cls.env['product.product'].create({
- 'name': 'Screw',
- 'type': 'product',
- })
-
- cls.mrp_workcenter = cls.env['mrp.workcenter'].create({
- 'name': 'Assembly Line 1',
- 'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id,
- })
- cls.mrp_bom_desk = cls.env['mrp.bom'].create({
- 'product_tmpl_id': cls.dining_table.product_tmpl_id.id,
- 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
- 'sequence': 3,
- 'consumption': 'flexible',
- 'operation_ids': [
- (0, 0, {'workcenter_id': cls.mrp_workcenter.id, 'name': 'Manual Assembly'}),
- ],
- })
- cls.mrp_bom_desk.write({
- 'bom_line_ids': [
- (0, 0, {
- 'product_id': cls.product_table_sheet.id,
- 'product_qty': 1,
- 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
- 'sequence': 1,
- 'operation_id': cls.mrp_bom_desk.operation_ids.id}),
- (0, 0, {
- 'product_id': cls.product_table_leg.id,
- 'product_qty': 4,
- 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
- 'sequence': 2,
- 'operation_id': cls.mrp_bom_desk.operation_ids.id}),
- (0, 0, {
- 'product_id': cls.product_bolt.id,
- 'product_qty': 4,
- 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
- 'sequence': 3,
- 'operation_id': cls.mrp_bom_desk.operation_ids.id}),
- (0, 0, {
- 'product_id': cls.product_screw.id,
- 'product_qty': 10,
- 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
- 'sequence': 4,
- 'operation_id': cls.mrp_bom_desk.operation_ids.id}),
- ]
- })
- cls.mrp_workcenter_1 = cls.env['mrp.workcenter'].create({
- 'name': 'Drill Station 1',
- 'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id,
- })
- cls.mrp_workcenter_3 = cls.env['mrp.workcenter'].create({
- 'name': 'Assembly Line 1',
- 'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id,
- })
-
-
-class TestWorkOrderProcess(TestWorkOrderProcessCommon):
- def full_availability(self):
- """set full availability for all calendars"""
- calendar = self.env['resource.calendar'].search([])
- calendar.write({'attendance_ids': [(5, 0, 0)]})
- calendar.write({'attendance_ids': [
- (0, 0, {'name': 'Monday', 'dayofweek': '0', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}),
- (0, 0, {'name': 'Tuesday', 'dayofweek': '1', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}),
- (0, 0, {'name': 'Wednesday', 'dayofweek': '2', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}),
- (0, 0, {'name': 'Thursday', 'dayofweek': '3', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}),
- (0, 0, {'name': 'Friday', 'dayofweek': '4', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}),
- (0, 0, {'name': 'Saturday', 'dayofweek': '5', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}),
- (0, 0, {'name': 'Sunday', 'dayofweek': '6', 'hour_from': 0, 'hour_to': 24, 'day_period': 'morning'}),
- ]})
-
- def test_00_workorder_process(self):
- """ Testing consume quants and produced quants with workorder """
- dining_table = self.dining_table
- product_table_sheet = self.product_table_sheet
- product_table_leg = self.product_table_leg
- product_bolt = self.product_bolt
- product_screw = self.product_screw
- mrp_bom_desk = self.mrp_bom_desk
-
- self.env['stock.move'].search([('product_id', 'in', [product_bolt.id, product_screw.id])])._do_unreserve()
-
- production_table_form = Form(self.env['mrp.production'])
- production_table_form.product_id = dining_table
- production_table_form.bom_id = mrp_bom_desk
- production_table_form.product_qty = 1.0
- production_table_form.product_uom_id = dining_table.uom_id
- production_table = production_table_form.save()
-
- # Set tracking lot on finish and consume products.
- dining_table.tracking = 'lot'
- product_table_sheet.tracking = 'lot'
- product_table_leg.tracking = 'lot'
- product_bolt.tracking = "lot"
-
- production_table.action_confirm()
- # Initial inventory of product sheet, lags and bolt
- lot_sheet = self.env['stock.production.lot'].create({'product_id': product_table_sheet.id, 'company_id': self.env.company.id})
- lot_leg = self.env['stock.production.lot'].create({'product_id': product_table_leg.id, 'company_id': self.env.company.id})
- lot_bolt = self.env['stock.production.lot'].create({'product_id': product_bolt.id, 'company_id': self.env.company.id})
-
- # Initialize inventory
- # --------------------
- inventory = self.env['stock.inventory'].create({
- 'name': 'Inventory Product Table',
- 'line_ids': [(0, 0, {
- 'product_id': product_table_sheet.id,
- 'product_uom_id': product_table_sheet.uom_id.id,
- 'product_qty': 20,
- 'prod_lot_id': lot_sheet.id,
- 'location_id': self.source_location_id
- }), (0, 0, {
- 'product_id': product_table_leg.id,
- 'product_uom_id': product_table_leg.uom_id.id,
- 'product_qty': 20,
- 'prod_lot_id': lot_leg.id,
- 'location_id': self.source_location_id
- }), (0, 0, {
- 'product_id': product_bolt.id,
- 'product_uom_id': product_bolt.uom_id.id,
- 'product_qty': 20,
- 'prod_lot_id': lot_bolt.id,
- 'location_id': self.source_location_id
- }), (0, 0, {
- 'product_id': product_screw.id,
- 'product_uom_id': product_screw.uom_id.id,
- 'product_qty': 20,
- 'location_id': self.source_location_id
- })]
- })
- inventory.action_start()
- inventory.action_validate()
-
- # Create work order
- production_table.button_plan()
- # Check Work order created or not
- self.assertEqual(len(production_table.workorder_ids), 1)
-
- # ---------------------------------------------------------
- # Process all workorder and check it state.
- # ----------------------------------------------------------
-
- workorder = production_table.workorder_ids[0]
- self.assertEqual(workorder.state, 'ready', "workorder state should be ready.")
-
- # --------------------------------------------------------------
- # Process assembly line
- # ---------------------------------------------------------
- finished_lot =self.env['stock.production.lot'].create({'product_id': production_table.product_id.id, 'company_id': self.env.company.id})
- workorder.write({'finished_lot_id': finished_lot.id})
- workorder.button_start()
- for workorder_line_id in workorder._workorder_line_ids():
- if workorder_line_id.product_id.id == product_bolt.id:
- workorder_line_id.write({'lot_id': lot_bolt.id, 'qty_done': 1})
- if workorder_line_id.product_id.id == product_table_sheet.id:
- workorder_line_id.write({'lot_id': lot_sheet.id, 'qty_done': 1})
- if workorder_line_id.product_id.id == product_table_leg.id:
- workorder_line_id.write({'lot_id': lot_leg.id, 'qty_done': 1})
- self.assertEqual(workorder.state, 'progress')
-
- workorder.record_production()
- self.assertEqual(workorder.state, 'done')
- move_table_sheet = production_table.move_raw_ids.filtered(lambda x : x.product_id == product_table_sheet)
- self.assertEqual(move_table_sheet.quantity_done, 1)
-
- # ---------------------------------------------------------------
- # Check consume quants and produce quants after posting inventory
- # ---------------------------------------------------------------
- action = production_table.button_mark_done()
- backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
- backorder.save().action_close_mo()
-
- self.assertEqual(product_screw.qty_available, 10)
- self.assertEqual(product_bolt.qty_available, 19)
- self.assertEqual(product_table_leg.qty_available, 19)
- self.assertEqual(product_table_sheet.qty_available, 19)
-
- def test_00b_workorder_process(self):
- """ Testing consume quants and produced quants with workorder """
- dining_table = self.dining_table
- product_table_sheet = self.product_table_sheet
- product_table_leg = self.product_table_leg
- product_bolt = self.product_bolt
- product_screw = self.product_screw
- bom = self.mrp_bom_desk
-
- self.env['stock.move'].search([('product_id', '=', product_bolt.id)])._do_unreserve()
-
- bom.operation_ids = False
- bom.write({
- 'operation_ids': [
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_1.id,
- 'name': 'Packing',
- 'time_cycle': 30,
- 'sequence': 5}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Testing',
- 'time_cycle': 60,
- 'sequence': 10}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Long time assembly',
- 'time_cycle': 180,
- 'sequence': 15}),
- ]
- })
-
- bom.bom_line_ids.filtered(lambda p: p.product_id == product_table_sheet).operation_id = bom.operation_ids[0]
- bom.bom_line_ids.filtered(lambda p: p.product_id == product_table_leg).operation_id = bom.operation_ids[1]
- bom.bom_line_ids.filtered(lambda p: p.product_id == product_bolt).operation_id = bom.operation_ids[2]
-
- production_table_form = Form(self.env['mrp.production'])
- production_table_form.product_id = dining_table
- production_table_form.bom_id = bom
- production_table_form.product_qty = 2.0
- production_table_form.product_uom_id = dining_table.uom_id
- production_table = production_table_form.save()
- # Set tracking lot on finish and consume products.
- dining_table.tracking = 'lot'
- product_table_sheet.tracking = 'lot'
- product_table_leg.tracking = 'lot'
- product_bolt.tracking = "lot"
- production_table.action_confirm()
- # Initial inventory of product sheet, lags and bolt
- lot_sheet = self.env['stock.production.lot'].create({'product_id': product_table_sheet.id, 'company_id': self.env.company.id})
- lot_leg = self.env['stock.production.lot'].create({'product_id': product_table_leg.id, 'company_id': self.env.company.id})
- lot_bolt = self.env['stock.production.lot'].create({'product_id': product_bolt.id, 'company_id': self.env.company.id})
-
- # Initialize inventory
- # --------------------
- inventory = self.env['stock.inventory'].create({
- 'name': 'Inventory Product Table',
- 'line_ids': [(0, 0, {
- 'product_id': product_table_sheet.id,
- 'product_uom_id': product_table_sheet.uom_id.id,
- 'product_qty': 20,
- 'prod_lot_id': lot_sheet.id,
- 'location_id': self.source_location_id
- }), (0, 0, {
- 'product_id': product_table_leg.id,
- 'product_uom_id': product_table_leg.uom_id.id,
- 'product_qty': 20,
- 'prod_lot_id': lot_leg.id,
- 'location_id': self.source_location_id
- }), (0, 0, {
- 'product_id': product_bolt.id,
- 'product_uom_id': product_bolt.uom_id.id,
- 'product_qty': 20,
- 'prod_lot_id': lot_bolt.id,
- 'location_id': self.source_location_id
- })]
- })
- inventory.action_start()
- inventory.action_validate()
-
- # Create work order
- production_table.button_plan()
- # Check Work order created or not
- self.assertEqual(len(production_table.workorder_ids), 3)
-
- # ---------------------------------------------------------
- # Process all workorder and check it state.
- # ----------------------------------------------------------
-
- workorders = production_table.workorder_ids
- self.assertEqual(workorders[0].state, 'ready', "First workorder state should be ready.")
- self.assertEqual(workorders[1].state, 'pending')
- self.assertEqual(workorders[2].state, 'pending')
-
- # --------------------------------------------------------------
- # Process cutting operation...
- # ---------------------------------------------------------
- finished_lot = self.env['stock.production.lot'].create({'product_id': production_table.product_id.id, 'company_id': self.env.company.id})
- workorders[0].write({'finished_lot_id': finished_lot.id, 'qty_producing': 1.0})
- workorders[0].button_start()
- workorders[0]._workorder_line_ids()[0].write({'lot_id': lot_sheet.id, 'qty_done': 1})
- self.assertEqual(workorders[0].state, 'progress')
-
- workorders[0].record_production()
-
- move_table_sheet = production_table.move_raw_ids.filtered(lambda p: p.product_id == product_table_sheet)
- self.assertEqual(move_table_sheet.quantity_done, 1)
-
- # --------------------------------------------------------------
- # Process drilling operation ...
- # ---------------------------------------------------------
- workorders[1].button_start()
- workorders[1].qty_producing = 1.0
- workorders[1]._workorder_line_ids()[0].write({'lot_id': lot_leg.id, 'qty_done': 4})
- workorders[1].record_production()
- move_leg = production_table.move_raw_ids.filtered(lambda p: p.product_id == product_table_leg)
- #self.assertEqual(workorders[1].state, 'done')
- self.assertEqual(move_leg.quantity_done, 4)
-
- # --------------------------------------------------------------
- # Process fitting operation ...
- # ---------------------------------------------------------
- workorders[2].button_start()
- workorders[2].qty_producing = 1.0
- move_lot = workorders[2]._workorder_line_ids()[0]
- move_lot.write({'lot_id': lot_bolt.id, 'qty_done': 4})
- move_table_bolt = production_table.move_raw_ids.filtered(lambda p: p.product_id.id == product_bolt.id)
- workorders[2].record_production()
- self.assertEqual(move_table_bolt.quantity_done, 4)
-
- # Change the quantity of the production order to 1
- wiz = self.env['change.production.qty'].create({'mo_id': production_table.id ,
- 'product_qty': 1.0})
- wiz.change_prod_qty()
- # ---------------------------------------------------------------
- # Check consume quants and produce quants after posting inventory
- # ---------------------------------------------------------------
- production_table._post_inventory()
- self.assertEqual(sum(move_table_sheet.mapped('quantity_done')), 1, "Wrong quantity of consumed product %s" % move_table_sheet.product_id.name)
- self.assertEqual(sum(move_leg.mapped('quantity_done')), 4, "Wrong quantity of consumed product %s" % move_leg.product_id.name)
- self.assertEqual(sum(move_table_bolt.mapped('quantity_done')), 4, "Wrong quantity of consumed product %s" % move_table_bolt.product_id.name)
-
- def test_explode_from_order(self):
- # bom3 produces 2 Dozen of Doors (p6), aka 24
- # To produce 24 Units of Doors (p6)
- # - 2 Units of Tools (p5) -> need 4
- # - 8 Dozen of Sticks (p4) -> need 16
- # - 12 Units of Wood (p2) -> need 24
- # bom2 produces 1 Unit of Sticks (p4)
- # To produce 1 Unit of Sticks (p4)
- # - 2 Dozen of Sticks (p4) -> need 8
- # - 3 Dozen of Stones (p3) -> need 12
-
- # Update capacity, start time, stop time, and time efficiency.
- # ------------------------------------------------------------
- self.workcenter_1.write({'capacity': 1, 'time_start': 0, 'time_stop': 0, 'time_efficiency': 100})
-
- # Set manual time cycle 20 and 10.
- # --------------------------------
- self.bom_3.operation_ids[0].time_cycle_manual = 10
- self.bom_3.operation_ids[1].time_cycle_manual = 10
- self.bom_2.operation_ids.time_cycle_manual = 20
-
- man_order_form = Form(self.env['mrp.production'])
- man_order_form.product_id = self.product_6
- man_order_form.bom_id = self.bom_3
- man_order_form.product_qty = 48
- man_order_form.product_uom_id = self.product_6.uom_id
- man_order = man_order_form.save()
- # reset quantities
- self.product_1.type = "product"
- self.env['stock.quant'].with_context(inventory_mode=True).create({
- 'product_id': self.product_1.id,
- 'inventory_quantity': 0.0,
- 'location_id': self.warehouse_1.lot_stock_id.id,
- })
-
- (self.product_2 | self.product_4).write({
- 'tracking': 'none',
- })
- # assign consume material
- man_order.action_confirm()
- man_order.action_assign()
- self.assertEqual(man_order.reservation_state, 'confirmed', "Production order should be in waiting state.")
-
- # check consume materials of manufacturing order
- self.assertEqual(len(man_order.move_raw_ids), 4, "Consume material lines are not generated proper.")
- product_2_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_2)
- product_3_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_3)
- product_4_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_4)
- product_5_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_5)
- consume_qty_2 = product_2_consume_moves.product_uom_qty
- self.assertEqual(consume_qty_2, 24.0, "Consume material quantity of Wood should be 24 instead of %s" % str(consume_qty_2))
- consume_qty_3 = product_3_consume_moves.product_uom_qty
- self.assertEqual(consume_qty_3, 12.0, "Consume material quantity of Stone should be 12 instead of %s" % str(consume_qty_3))
- self.assertEqual(len(product_4_consume_moves), 2, "Consume move are not generated proper.")
- for consume_moves in product_4_consume_moves:
- consume_qty_4 = consume_moves.product_uom_qty
- self.assertIn(consume_qty_4, [8.0, 16.0], "Consume material quantity of Stick should be 8 or 16 instead of %s" % str(consume_qty_4))
- self.assertFalse(product_5_consume_moves, "Move should not create for phantom bom")
-
- # create required lots
- lot_product_2 = self.env['stock.production.lot'].create({'product_id': self.product_2.id, 'company_id': self.env.company.id})
- lot_product_4 = self.env['stock.production.lot'].create({'product_id': self.product_4.id, 'company_id': self.env.company.id})
-
- # refuel stock
- inventory = self.env['stock.inventory'].create({
- 'name': 'Inventory For Product C',
- 'line_ids': [(0, 0, {
- 'product_id': self.product_2.id,
- 'product_uom_id': self.product_2.uom_id.id,
- 'product_qty': 30,
- 'prod_lot_id': lot_product_2.id,
- 'location_id': self.stock_location_14.id
- }), (0, 0, {
- 'product_id': self.product_3.id,
- 'product_uom_id': self.product_3.uom_id.id,
- 'product_qty': 60,
- 'location_id': self.stock_location_14.id
- }), (0, 0, {
- 'product_id': self.product_4.id,
- 'product_uom_id': self.product_4.uom_id.id,
- 'product_qty': 60,
- 'prod_lot_id': lot_product_4.id,
- 'location_id': self.stock_location_14.id
- })]
- })
- inventory.action_start()
- inventory.action_validate()
-
- # re-assign consume material
- man_order.action_assign()
-
- # Check production order status after assign.
- self.assertEqual(man_order.reservation_state, 'assigned', "Production order should be in assigned state.")
- # Plan production order.
- man_order.button_plan()
-
- # check workorders
- # - main bom: Door: 2 operations
- # operation 1: Cutting
- # operation 2: Welding, waiting for the previous one
- # - kit bom: Stone Tool: 1 operation
- # operation 1: Gift Wrapping
- workorders = man_order.workorder_ids
- kit_wo = man_order.workorder_ids.filtered(lambda wo: wo.operation_id.name == "Gift Wrap Maching")
- door_wo_1 = man_order.workorder_ids.filtered(lambda wo: wo.operation_id.name == "Cutting Machine")
- door_wo_2 = man_order.workorder_ids.filtered(lambda wo: wo.operation_id.name == "Weld Machine")
-
- for workorder in workorders:
- self.assertEqual(workorder.workcenter_id, self.workcenter_1, "Workcenter does not match.")
- self.assertEqual(kit_wo.state, 'ready', "Workorder should be in ready state.")
- self.assertEqual(door_wo_1.state, 'ready', "Workorder should be in ready state.")
- self.assertEqual(door_wo_2.state, 'pending', "Workorder should be in pending state.")
- self.assertEqual(kit_wo.duration_expected, 960, "Workorder duration should be 960 instead of %s." % str(kit_wo.duration_expected))
- self.assertEqual(door_wo_1.duration_expected, 480, "Workorder duration should be 480 instead of %s." % str(door_wo_1.duration_expected))
- self.assertEqual(door_wo_2.duration_expected, 480, "Workorder duration should be 480 instead of %s." % str(door_wo_2.duration_expected))
-
- # subbom: kit for stone tools
- kit_wo.button_start()
- finished_lot = self.env['stock.production.lot'].create({'product_id': man_order.product_id.id, 'company_id': self.env.company.id})
- kit_wo.write({
- 'finished_lot_id': finished_lot.id,
- 'qty_producing': 48
- })
-
- kit_wo.record_production()
-
- self.assertEqual(kit_wo.state, 'done', "Workorder should be in done state.")
-
- # first operation of main bom
- finished_lot = self.env['stock.production.lot'].create({'product_id': man_order.product_id.id, 'company_id': self.env.company.id})
- door_wo_1.button_start()
- door_wo_1.write({
- 'finished_lot_id': finished_lot.id,
- 'qty_producing': 48
- })
- door_wo_1.record_production()
- self.assertEqual(door_wo_1.state, 'done', "Workorder should be in done state.")
-
- # second operation of main bom
- self.assertEqual(door_wo_2.state, 'ready', "Workorder should be in ready state.")
- door_wo_2.button_start()
- door_wo_2.record_production()
- self.assertEqual(door_wo_2.state, 'done', "Workorder should be in done state.")
-
- def test_01_without_workorder(self):
- """ Testing consume quants and produced quants without workorder """
- unit = self.ref("uom.product_uom_unit")
- custom_laptop = self.env['product.product'].create({
- 'name': 'Drawer',
- 'type': 'product',
- 'tracking': 'lot',
- })
-
- # Create new product charger and keybord
- # --------------------------------------
- product_charger = self.env['product.product'].create({
- 'name': 'Charger',
- 'type': 'product',
- 'tracking': 'lot',
- 'uom_id': unit,
- 'uom_po_id': unit})
- product_keybord = self.env['product.product'].create({
- 'name': 'Usb Keybord',
- 'type': 'product',
- 'tracking': 'lot',
- 'uom_id': unit,
- 'uom_po_id': unit})
-
- # Create bill of material for customized laptop.
-
- bom_custom_laptop = self.env['mrp.bom'].create({
- 'product_tmpl_id': custom_laptop.product_tmpl_id.id,
- 'product_qty': 10,
- 'product_uom_id': unit,
- 'consumption': 'flexible',
- 'bom_line_ids': [(0, 0, {
- 'product_id': product_charger.id,
- 'product_qty': 20,
- 'product_uom_id': unit
- }), (0, 0, {
- 'product_id': product_keybord.id,
- 'product_qty': 20,
- 'product_uom_id': unit
- })]
- })
-
- # Create production order for customize laptop.
-
- mo_custom_laptop_form = Form(self.env['mrp.production'])
- mo_custom_laptop_form.product_id = custom_laptop
- mo_custom_laptop_form.bom_id = bom_custom_laptop
- mo_custom_laptop_form.product_qty = 10.0
- mo_custom_laptop_form.product_uom_id = self.env.ref("uom.product_uom_unit")
- mo_custom_laptop = mo_custom_laptop_form.save()
-
- mo_custom_laptop.action_confirm()
- # Assign component to production order.
- mo_custom_laptop.action_assign()
-
- # Check production order status of availablity
-
- self.assertEqual(mo_custom_laptop.reservation_state, 'confirmed')
-
- # --------------------------------------------------
- # Set inventory for rawmaterial charger and keybord
- # --------------------------------------------------
-
- lot_charger = self.env['stock.production.lot'].create({'product_id': product_charger.id, 'company_id': self.env.company.id})
- lot_keybord = self.env['stock.production.lot'].create({'product_id': product_keybord.id, 'company_id': self.env.company.id})
-
- # Initialize Inventory
- # --------------------
- inventory = self.env['stock.inventory'].create({
- 'name': 'Inventory Product Table',
- 'line_ids': [(0, 0, {
- 'product_id': product_charger.id,
- 'product_uom_id': product_charger.uom_id.id,
- 'product_qty': 20,
- 'prod_lot_id': lot_charger.id,
- 'location_id': self.source_location_id
- }), (0, 0, {
- 'product_id': product_keybord.id,
- 'product_uom_id': product_keybord.uom_id.id,
- 'product_qty': 20,
- 'prod_lot_id': lot_keybord.id,
- 'location_id': self.source_location_id
- })]
- })
- inventory.action_start()
- inventory.action_validate()
-
- # Check consumed move status
- mo_custom_laptop.action_assign()
- self.assertEqual(mo_custom_laptop.reservation_state, 'assigned')
-
- # Check current status of raw materials.
- for move in mo_custom_laptop.move_raw_ids:
- self.assertEqual(move.product_uom_qty, 20, "Wrong consume quantity of raw material %s: %s instead of %s" % (move.product_id.name, move.product_uom_qty, 20))
- self.assertEqual(move.quantity_done, 0, "Wrong produced quantity on raw material %s: %s instead of %s" % (move.product_id.name, move.quantity_done, 0))
-
- # -----------------
- # Start production
- # -----------------
-
- # Produce 6 Unit of custom laptop will consume ( 12 Unit of keybord and 12 Unit of charger)
- laptop_lot_001 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id , 'company_id': self.env.company.id})
- mo_form = Form(mo_custom_laptop)
- mo_form.qty_producing = 6
- mo_form.lot_producing_id = laptop_lot_001
- mo_custom_laptop = mo_form.save()
- details_operation_form = Form(mo_custom_laptop.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
- with details_operation_form.move_line_ids.edit(0) as ml:
- ml.qty_done = 12
- details_operation_form.save()
- details_operation_form = Form(mo_custom_laptop.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
- with details_operation_form.move_line_ids.edit(0) as ml:
- ml.qty_done = 12
- details_operation_form.save()
-
- action = mo_custom_laptop.button_mark_done()
- backorder = Form(self.env[action['res_model']].with_context(**action['context']))
- backorder.save().action_backorder()
-
- # Check consumed move after produce 6 quantity of customized laptop.
- for move in mo_custom_laptop.move_raw_ids:
- self.assertEqual(move.quantity_done, 12, "Wrong produced quantity on raw material %s" % (move.product_id.name))
- self.assertEqual(len(mo_custom_laptop.move_raw_ids), 2)
-
- # Check done move and confirmed move quantity.
- charger_done_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_charger.id and x.state == 'done')
- keybord_done_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_keybord.id and x.state == 'done')
- self.assertEqual(charger_done_move.product_uom_qty, 12)
- self.assertEqual(keybord_done_move.product_uom_qty, 12)
-
- # Produce remaining 4 quantity
- # ----------------------------
-
- # Produce 4 Unit of custom laptop will consume ( 8 Unit of keybord and 8 Unit of charger).
- laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id, 'company_id': self.env.company.id})
- mo_custom_laptop = mo_custom_laptop.procurement_group_id.mrp_production_ids[1]
- mo_form = Form(mo_custom_laptop)
- mo_form.qty_producing = 4
- mo_form.lot_producing_id = laptop_lot_002
- mo_custom_laptop = mo_form.save()
- details_operation_form = Form(mo_custom_laptop.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
- with details_operation_form.move_line_ids.edit(0) as ml:
- ml.qty_done = 8
- details_operation_form.save()
- details_operation_form = Form(mo_custom_laptop.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
- with details_operation_form.move_line_ids.edit(0) as ml:
- ml.qty_done = 8
- details_operation_form.save()
-
- charger_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_charger.id and x.state != 'done')
- keybord_move = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_keybord.id and x.state !='done')
- self.assertEqual(charger_move.quantity_done, 8, "Wrong consumed quantity of %s" % charger_move.product_id.name)
- self.assertEqual(keybord_move.quantity_done, 8, "Wrong consumed quantity of %s" % keybord_move.product_id.name)
-
- def test_03_test_serial_number_defaults(self):
- """ Test that the correct serial number is suggested on consecutive work orders. """
- laptop = self.laptop
- graphics_card = self.graphics_card
- unit = self.env.ref("uom.product_uom_unit")
-
- laptop.tracking = 'serial'
-
- bom_laptop = self.env['mrp.bom'].create({
- 'product_tmpl_id': laptop.product_tmpl_id.id,
- 'product_qty': 1,
- 'product_uom_id': unit.id,
- 'consumption': 'flexible',
- 'bom_line_ids': [(0, 0, {
- 'product_id': graphics_card.id,
- 'product_qty': 1,
- 'product_uom_id': unit.id
- })],
- 'operation_ids': [
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_1.id,
- 'name': 'Packing',
- 'time_cycle': 30,
- 'sequence': 5}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Testing',
- 'time_cycle': 60,
- 'sequence': 10}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Long time assembly',
- 'time_cycle': 180,
- 'sequence': 15}),
- ],
- })
-
- mo_laptop_form = Form(self.env['mrp.production'])
- mo_laptop_form.product_id = laptop
- mo_laptop_form.bom_id = bom_laptop
- mo_laptop_form.product_qty = 3
- mo_laptop = mo_laptop_form.save()
-
- mo_laptop.action_confirm()
- mo_laptop.button_plan()
- workorders = mo_laptop.workorder_ids.sorted()
- self.assertEqual(len(workorders), 3)
-
- workorders[0].button_start()
- serial_a = self.env['stock.production.lot'].create({'product_id': laptop.id, 'company_id': self.env.company.id})
- workorders[0].finished_lot_id = serial_a
- workorders[0].record_production()
- serial_b = self.env['stock.production.lot'].create({'product_id': laptop.id, 'company_id': self.env.company.id})
- workorders[0].finished_lot_id = serial_b
- workorders[0].record_production()
- serial_c = self.env['stock.production.lot'].create({'product_id': laptop.id, 'company_id': self.env.company.id})
- workorders[0].finished_lot_id = serial_c
- workorders[0].record_production()
- self.assertEqual(workorders[0].state, 'done')
-
- for workorder in workorders - workorders[0]:
- workorder.button_start()
- self.assertEqual(workorder.finished_lot_id, serial_a)
- workorder.record_production()
- self.assertEqual(workorder.finished_lot_id, serial_b)
- workorder.record_production()
- self.assertEqual(workorder.finished_lot_id, serial_c)
- workorder.record_production()
- self.assertEqual(workorder.state, 'done')
-
- def test_03b_test_serial_number_defaults(self):
- """ Check the constraint on the workorder final_lot. The first workorder
- produces 2/2 units without serial number (serial is only required when
- you register a component) then the second workorder try to register a
- serial number. It should be allowed since the first workorder did not
- specify a seiral number.
- """
- drawer = self.env['product.product'].create({
- 'name': 'Drawer',
- 'type': 'product',
- 'tracking': 'lot',
- })
- drawer_drawer = self.env['product.product'].create({
- 'name': 'Drawer Black',
- 'type': 'product',
- 'tracking': 'lot',
- })
- drawer_case = self.env['product.product'].create({
- 'name': 'Drawer Case Black',
- 'type': 'product',
- 'tracking': 'lot',
- })
- bom = self.env['mrp.bom'].create({
- 'product_tmpl_id': drawer.product_tmpl_id.id,
- 'product_uom_id': self.env.ref('uom.product_uom_unit').id,
- 'consumption': 'flexible',
- 'sequence': 2,
- 'operation_ids': [
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_1.id,
- 'name': 'Packing',
- 'time_cycle': 30,
- 'sequence': 5}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Testing',
- 'time_cycle': 60,
- 'sequence': 10}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Long time assembly',
- 'time_cycle': 180,
- 'sequence': 15}),
- ],
- 'bom_line_ids': [(0, 0, {
- 'product_id': drawer_drawer.id,
- 'product_qty': 1,
- 'product_uom_id': self.env.ref('uom.product_uom_unit').id,
- 'sequence': 1,
- }), (0, 0, {
- 'product_id': drawer_case.id,
- 'product_qty': 1,
- 'product_uom_id': self.env.ref('uom.product_uom_unit').id,
- 'sequence': 2,
- })]
- })
- inventory = self.env['stock.inventory'].create({
- 'name': 'Initial inventory',
- 'line_ids': [(0, 0, {
- 'product_id': drawer_drawer.id,
- 'product_uom_id': drawer_drawer.uom_id.id,
- 'product_qty': 50.0,
- 'location_id': self.stock_location_14.id,
- }), (0, 0, {
- 'product_id': drawer_case.id,
- 'product_uom_id': drawer_case.uom_id.id,
- 'product_qty': 50.0,
- 'location_id': self.stock_location_14.id,
- })]
- })
- inventory.action_start()
- inventory.action_validate()
-
- product = bom.product_tmpl_id.product_variant_id
- product.tracking = 'serial'
-
- lot_1 = self.env['stock.production.lot'].create({
- 'product_id': product.id,
- 'name': 'LOT000001',
- 'company_id': self.env.company.id,
- })
-
- lot_2 = self.env['stock.production.lot'].create({
- 'product_id': product.id,
- 'name': 'LOT000002',
- 'company_id': self.env.company.id,
- })
- self.env['stock.production.lot'].create({
- 'product_id': product.id,
- 'name': 'LOT000003',
- 'company_id': self.env.company.id,
- })
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = product
- mo_form.bom_id = bom
- mo_form.product_qty = 2.0
- mo = mo_form.save()
-
- mo.action_confirm()
- mo.button_plan()
-
- workorder_0 = mo.workorder_ids[0]
- workorder_0.button_start()
- workorder_0.record_production()
- workorder_0.record_production()
-
- workorder_1 = mo.workorder_ids[1]
- workorder_1.button_start()
- with Form(workorder_1) as wo:
- wo.finished_lot_id = lot_1
- workorder_1.record_production()
-
- self.assertTrue(len(workorder_1.allowed_lots_domain) > 1)
- with Form(workorder_1) as wo:
- wo.finished_lot_id = lot_2
- workorder_1.record_production()
-
- workorder_2 = mo.workorder_ids[2]
- self.assertEqual(workorder_2.allowed_lots_domain, lot_1 | lot_2)
-
- self.assertEqual(workorder_0.finished_workorder_line_ids.qty_done, 2)
- self.assertFalse(workorder_0.finished_workorder_line_ids.lot_id)
- self.assertEqual(sum(workorder_1.finished_workorder_line_ids.mapped('qty_done')), 2)
- self.assertEqual(workorder_1.finished_workorder_line_ids.mapped('lot_id'), lot_1 | lot_2)
-
- def test_04_test_planning_date(self):
- """ Test that workorder are planned at the correct time. """
- # The workcenter is working 24/7
- self.full_availability()
-
- dining_table = self.dining_table
-
- production_table_form = Form(self.env['mrp.production'])
- production_table_form.product_id = dining_table
- production_table_form.bom_id = self.mrp_bom_desk
- production_table_form.product_qty = 1.0
- production_table_form.product_uom_id = dining_table.uom_id
- production_table = production_table_form.save()
- production_table.action_confirm()
-
- # Create work order
- production_table.button_plan()
- workorder = production_table.workorder_ids[0]
-
- # Check that the workorder is planned now and that it lasts one hour
- self.assertAlmostEqual(workorder.date_planned_start, datetime.now(), delta=timedelta(seconds=10), msg="Workorder should be planned now.")
- self.assertAlmostEqual(workorder.date_planned_finished, datetime.now() + timedelta(hours=1), delta=timedelta(seconds=10), msg="Workorder should be done in an hour.")
-
- def test_04b_test_planning_date(self):
- """ Test that workorder are planned at the correct time when setting a start date """
- # The workcenter is working 24/7
- self.full_availability()
-
- dining_table = self.dining_table
-
- date_start = datetime.now() + timedelta(days=1)
-
- production_table_form = Form(self.env['mrp.production'])
- production_table_form.product_id = dining_table
- production_table_form.bom_id = self.mrp_bom_desk
- production_table_form.product_qty = 1.0
- production_table_form.product_uom_id = dining_table.uom_id
- production_table_form.date_planned_start = date_start
- production_table = production_table_form.save()
- production_table.action_confirm()
-
- # Create work order
- production_table.button_plan()
- workorder = production_table.workorder_ids[0]
-
- # Check that the workorder is planned now and that it lasts one hour
- self.assertAlmostEqual(workorder.date_planned_start, date_start, delta=timedelta(seconds=1), msg="Workorder should be planned tomorrow.")
- self.assertAlmostEqual(workorder.date_planned_finished, date_start + timedelta(hours=1), delta=timedelta(seconds=1), msg="Workorder should be done one hour later.")
-
- def test_planning_overlaps_wo(self):
- """ Test that workorder doesn't overlaps between then when plan the MO """
- self.full_availability()
-
- dining_table = self.dining_table
-
- # Take between +30min -> +90min
- date_start = datetime.now() + timedelta(minutes=30)
-
- production_table_form = Form(self.env['mrp.production'])
- production_table_form.product_id = dining_table
- production_table_form.bom_id = self.mrp_bom_desk
- production_table_form.product_qty = 1.0
- production_table_form.product_uom_id = dining_table.uom_id
- production_table_form.date_planned_start = date_start
- production_table = production_table_form.save()
- production_table.action_confirm()
-
- # Create work order
- production_table.button_plan()
- workorder_prev = production_table.workorder_ids[0]
-
- # Check that the workorder is planned now and that it lasts one hour
- self.assertAlmostEqual(workorder_prev.date_planned_start, date_start, delta=timedelta(seconds=10), msg="Workorder should be planned in +30min")
- self.assertAlmostEqual(workorder_prev.date_planned_finished, date_start + timedelta(hours=1), delta=timedelta(seconds=10), msg="Workorder should be done in +90min")
-
- # As soon as possible, but because of the first one, it will planned only after +90 min
- date_start = datetime.now()
-
- production_table_form = Form(self.env['mrp.production'])
- production_table_form.product_id = dining_table
- production_table_form.bom_id = self.mrp_bom_desk
- production_table_form.product_qty = 1.0
- production_table_form.product_uom_id = dining_table.uom_id
- production_table_form.date_planned_start = date_start
- production_table = production_table_form.save()
- production_table.action_confirm()
-
- # Create work order
- production_table.button_plan()
- workorder = production_table.workorder_ids[0]
-
- # Check that the workorder is planned now and that it lasts one hour
- self.assertAlmostEqual(workorder.date_planned_start, workorder_prev.date_planned_finished, delta=timedelta(seconds=10), msg="Workorder should be planned after the first one")
- self.assertAlmostEqual(workorder.date_planned_finished, workorder_prev.date_planned_finished + timedelta(hours=1), delta=timedelta(seconds=10), msg="Workorder should be done one hour later.")
-
- def test_change_production_1(self):
- """Change the quantity to produce on the MO while workorders are already planned."""
- dining_table = self.dining_table
- dining_table.tracking = 'lot'
- production_table_form = Form(self.env['mrp.production'])
- production_table_form.product_id = dining_table
- production_table_form.bom_id = self.mrp_bom_desk
- production_table_form.product_qty = 1.0
- production_table_form.product_uom_id = dining_table.uom_id
- production_table = production_table_form.save()
- production_table.action_confirm()
-
- # Create work order
- production_table.button_plan()
-
- context = {'active_id': production_table.id, 'active_model': 'mrp.production'}
- change_qty_form = Form(self.env['change.production.qty'].with_context(context))
- change_qty_form.product_qty = 2.00
- change_qty = change_qty_form.save()
- change_qty.change_prod_qty()
-
- self.assertEqual(production_table.workorder_ids[0].qty_producing, 2, "Quantity to produce not updated")
-
- def test_expected_duration_workorder(self):
- """ Test that the expected_duration is editable and change when other
- workcenter is set or product quantity to produce is increased
- Also this expected duration need to be recalculate correctly during the planning
- if it used a alternative workcenter
- """
- planned_date = datetime(2023, 5, 15, 9, 0)
- self.mrp_workcenter_3.write({'capacity': 1, 'time_start': 0, 'time_stop': 2, 'time_efficiency': 50})
- self.workcenter_1.write({'capacity': 2, 'time_start': 2, 'time_stop': 1, 'time_efficiency': 80, 'alternative_workcenter_ids': [(6, 0, [self.mrp_workcenter_3.id])]})
- self.planning_bom.operation_ids[0].write({'time_cycle_manual': 10})
- mo_form = Form(self.env['mrp.production'])
- mo_form.bom_id = self.planning_bom
- mo_form.date_planned_start = planned_date
- mo = mo_form.save()
-
- # TEST Default/onchange duration_expected
- self.assertEqual(len(mo.workorder_ids), 1)
- self.assertEqual(mo.workorder_ids.workcenter_id, self.workcenter_1, "wrong workcenter")
- # time_cycle = 10, product_qty = 4.0, capacity = 2, time_start = 2, time_stop = 1, time_efficiency = 80%
- # => 2 + 1 + (4 / 2 * 10) * (100 / 80) = 28.0
- self.assertEqual(mo.workorder_ids.duration_expected, 28.0, "wrong duration_expected")
- mo_form = Form(mo)
- with mo_form.workorder_ids.edit(0) as wo_form:
- wo_form.workcenter_id = self.mrp_workcenter_3
- mo = mo_form.save()
- # time_cycle = 10, product_qty = 4.0, capacity = 1, time_start = 0, time_stop = 2, time_efficiency = 50%
- # => 0 + 2 + (4 / 1 * 10) * (100 / 50) = 82.0
- self.assertEqual(mo.workorder_ids.duration_expected, 82.0, "wrong duration_expected")
-
- # TEST Manual change of duration_expected
- with mo_form.workorder_ids.edit(0) as wo_form:
- wo_form.workcenter_id = self.workcenter_1
- self.assertEqual(wo_form.duration_expected, 28.0, "wrong duration_expected")
- wo_form.duration_expected = 40.0
- mo = mo_form.save()
-
- self.assertEqual(mo.workorder_ids.duration_expected, 40.0, "wrong duration_expected")
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
- self.assertEqual(mo.workorder_ids.duration_expected, 40.0, "duration_expected doesn't reset")
-
- self.assertAlmostEqual(mo.date_planned_start, mo.workorder_ids.date_planned_start, delta=timedelta(seconds=10))
- self.assertAlmostEqual(mo.date_planned_finished, mo.workorder_ids.date_planned_finished, delta=timedelta(seconds=10))
- self.assertAlmostEqual(mo.date_planned_start, planned_date, delta=timedelta(seconds=10))
- self.assertAlmostEqual(mo.date_planned_finished, planned_date + timedelta(minutes=40), delta=timedelta(seconds=10))
-
- def test_planning_0(self):
- """ Test alternative conditions
- 1. alternative relation is directionnal
- 2. a workcenter cannot be it's own alternative """
- self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2
- self.assertEqual(self.wc_alt_1.alternative_workcenter_ids, self.env['mrp.workcenter'], "Alternative workcenter is not reciprocal")
- self.assertEqual(self.wc_alt_2.alternative_workcenter_ids, self.env['mrp.workcenter'], "Alternative workcenter is not reciprocal")
- with self.assertRaises(ValidationError):
- self.workcenter_1.alternative_workcenter_ids |= self.workcenter_1
-
- def test_planning_1(self):
- """ Testing planning workorder with alternative workcenters
- Plan 6 times the same MO, the workorders should be split accross workcenters
- The 3 workcenters are free, this test plans 3 workorder in a row then three next.
- The workcenters have not exactly the same parameters (efficiency, start time) so the
- the last 3 workorder are not dispatched like the 3 first.
- At the end of the test, the calendars will look like:
- - calendar wc1 :[mo1][mo4]
- - calendar wc2 :[mo2 ][mo5 ]
- - calendar wc3 :[mo3 ][mo6 ]"""
- planned_date = datetime(2023, 5, 15, 9, 0)
- self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2
- workcenters = [self.wc_alt_2, self.wc_alt_1, self.workcenter_1]
- for i in range(3):
- # Create an MO for product4
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 1
- mo_form.date_planned_start = planned_date
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
- # Check that workcenters change
- self.assertEqual(mo.workorder_ids.workcenter_id, workcenters[i], "wrong workcenter %d" % i)
- self.assertAlmostEqual(mo.date_planned_start, planned_date, delta=timedelta(seconds=10))
- self.assertAlmostEqual(mo.date_planned_start, mo.workorder_ids.date_planned_start, delta=timedelta(seconds=10))
-
- for i in range(3):
- # Planning 3 more should choose workcenters in opposite order as
- # - wc_alt_2 as the best efficiency
- # - wc_alt_1 take a little less start time
- # - workcenter_1 is the worst
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 1
- mo_form.date_planned_start = planned_date
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
- # Check that workcenters change
- self.assertEqual(mo.workorder_ids.workcenter_id, workcenters[i], "wrong workcenter %d" % i)
- self.assertNotEqual(mo.date_planned_start, planned_date)
- self.assertAlmostEqual(mo.date_planned_start, mo.workorder_ids.date_planned_start, delta=timedelta(seconds=10))
-
- def test_planning_2(self):
- """ Plan some manufacturing orders with 2 workorders each
- Batch size of the operation will influence start dates of workorders
- The first unit to be produced can go the second workorder before finishing
- to produce the second unit.
- calendar wc1 : [q1][q2]
- calendar wc2 : [q1][q2]"""
- self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2
- self.planning_bom.operation_ids = False
- self.planning_bom.write({
- 'operation_ids': [
- (0, 0, {'name': 'Cutting Machine', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 12, 'sequence': 1, 'batch': 'yes', 'batch_size': 1}),
- (0, 0, {'name': 'Weld Machine', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 18, 'sequence': 2}),
- ],
- })
- # Allow second workorder to start once the first one is not ended yet
- self.env['mrp.workcenter'].search([]).write({'capacity': 1})
- # workcenters work 24/7
- self.full_availability()
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 2
- mo = mo_form.save()
- mo.action_confirm()
- plan = datetime.now()
- mo.button_plan()
- self.assertEqual(mo.workorder_ids[0].workcenter_id, self.wc_alt_2, "wrong workcenter")
- self.assertEqual(mo.workorder_ids[1].workcenter_id, self.wc_alt_1, "wrong workcenter")
-
- duration1 = self.planning_bom.operation_ids[0].time_cycle * 100.0 / self.wc_alt_2.time_efficiency + self.wc_alt_2.time_start
- duration2 = 2.0 * self.planning_bom.operation_ids[0].time_cycle * 100.0 / self.wc_alt_1.time_efficiency + self.wc_alt_1.time_start + self.wc_alt_1.time_stop
- wo2_start = mo.workorder_ids[1].date_planned_start
- wo2_stop = mo.workorder_ids[1].date_planned_finished
-
- wo2_start_theo = self.wc_alt_2.resource_calendar_id.plan_hours(duration1 / 60.0, plan, compute_leaves=False, resource=self.wc_alt_2.resource_id)
- wo2_stop_theo = self.wc_alt_1.resource_calendar_id.plan_hours(duration2 / 60.0, wo2_start, compute_leaves=False, resource=self.wc_alt_2.resource_id)
-
- self.assertAlmostEqual(wo2_start, wo2_start_theo, delta=timedelta(seconds=10), msg="Wrong plannification")
- self.assertAlmostEqual(wo2_stop, wo2_stop_theo, delta=timedelta(seconds=10), msg="Wrong plannification")
-
- def test_planning_3(self):
- """ Plan some manufacturing orders with 1 workorder on 1 workcenter
- the first workorder will be hard set in the future to see if the second
- one take the free slot before on the calendar
- calendar after first mo : [ ][mo1]
- calendar after second mo: [mo2][mo1] """
- planned_date = datetime(2023, 5, 15, 14, 0)
-
- self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2
- self.env['mrp.workcenter'].search([]).write({'tz': 'UTC'}) # compute all date in UTC
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 1
- mo_form.date_planned_start = planned_date
- mo = mo_form.save()
- start = mo.date_planned_start
- mo.action_confirm()
- mo.button_plan()
- self.assertEqual(mo.workorder_ids[0].workcenter_id, self.wc_alt_2, "wrong workcenter")
- wo1_start = mo.workorder_ids[0].date_planned_start
- wo1_stop = mo.workorder_ids[0].date_planned_finished
- self.assertAlmostEqual(wo1_start, start, delta=timedelta(seconds=10), msg="Wrong plannification")
- self.assertAlmostEqual(wo1_stop, start + timedelta(minutes=85.58), delta=timedelta(seconds=10), msg="Wrong plannification")
-
- # second MO should be plan before as there is a free slot before
- planned_date = datetime(2023, 5, 15, 9, 0)
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 1
- mo_form.date_planned_start = planned_date
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
- self.assertEqual(mo.workorder_ids[0].workcenter_id, self.wc_alt_2, "wrong workcenter")
- wo1_start = mo.workorder_ids[0].date_planned_start
- wo1_stop = mo.workorder_ids[0].date_planned_finished
- self.assertAlmostEqual(wo1_start, planned_date, delta=timedelta(seconds=10), msg="Wrong plannification")
- self.assertAlmostEqual(wo1_stop, planned_date + timedelta(minutes=85.59), delta=timedelta(seconds=10), msg="Wrong plannification")
-
- def test_planning_4(self):
- """ Plan a manufacturing orders with 1 workorder on 1 workcenter
- the workcenter calendar is empty. which means the workcenter is never
- available. Planning a workorder on it should raise an error"""
-
- self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2
- self.env['resource.calendar'].search([]).write({'attendance_ids': [(5, False, False)]})
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 1
- mo = mo_form.save()
- mo.action_confirm()
- with self.assertRaises(UserError):
- mo.button_plan()
-
- def test_planning_5(self):
- """ Cancelling a production with workorders should free all reserved slot
- in the related workcenters calendars """
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 1
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
-
- mo.action_cancel()
- self.assertEqual(mo.workorder_ids.mapped('date_start'), [False])
- self.assertEqual(mo.workorder_ids.mapped('date_finished'), [False])
-
- def test_planning_6(self):
- """ Marking a workorder as done before the theoretical date should update
- the reservation slot in the calendar the be able to reserve the next
- production sooner """
- self.env['mrp.workcenter'].search([]).write({'tz': 'UTC'}) # compute all date in UTC
- mrp_workcenter_3 = self.env['mrp.workcenter'].create({
- 'name': 'assembly line 1',
- 'resource_calendar_id': self.env.ref('resource.resource_calendar_std').id,
- })
- self.planning_bom.operation_ids = False
- self.planning_bom.write({
- 'operation_ids': [(0, 0, {
- 'workcenter_id': mrp_workcenter_3.id,
- 'name': 'Manual Assembly',
- 'time_cycle': 60,
- })]
- })
- planned_date = datetime(2023, 5, 15, 9, 0)
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 1
- mo_form.date_planned_start = planned_date
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
- wo = mo.workorder_ids
- self.assertAlmostEqual(wo.date_planned_start, planned_date, delta=timedelta(seconds=10))
- self.assertAlmostEqual(wo.date_planned_finished, planned_date + timedelta(minutes=60), delta=timedelta(seconds=10))
- wo.button_start()
- wo.record_production()
- # Marking workorder as done should change the finished date
- self.assertAlmostEqual(wo.date_finished, datetime.now(), delta=timedelta(seconds=10))
- self.assertAlmostEqual(wo.date_planned_finished, datetime.now(), delta=timedelta(seconds=10))
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 1
- mo_form.date_planned_start = planned_date
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
- wo = mo.workorder_ids
- wo.button_start()
- self.assertAlmostEqual(wo.date_start, datetime.now(), delta=timedelta(seconds=10))
- self.assertAlmostEqual(wo.date_planned_start, datetime.now(), delta=timedelta(seconds=10))
-
- def test_planning_7(self):
- """ set the workcenter capacity to 10. Produce a dozen of product tracked by
- SN. The production should be done in two batches"""
- self.workcenter_1.capacity = 10
- self.workcenter_1.time_efficiency = 100
- self.workcenter_1.time_start = 0
- self.workcenter_1.time_stop = 0
- self.planning_bom.operation_ids.time_cycle = 60
- self.product_4.tracking = 'serial'
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_uom_id = self.uom_dozen
- mo_form.product_qty = 1
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
- wo = mo.workorder_ids
- self.assertEqual(wo.duration_expected, 120)
-
- def test_plan_unplan_date(self):
- """ Testing planning a workorder then cancel it and then plan it again.
- The planned date must be the same the first time and the second time the
- workorder is planned."""
- planned_date = datetime(2023, 5, 15, 9, 0)
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_4
- mo_form.bom_id = self.planning_bom
- mo_form.product_qty = 1
- mo_form.date_planned_start = planned_date
- mo = mo_form.save()
- mo.action_confirm()
- # Plans the MO and checks the date.
- mo.button_plan()
- self.assertAlmostEqual(mo.date_planned_start, planned_date, delta=timedelta(seconds=10))
- self.assertEqual(bool(mo.workorder_ids.exists()), True)
- leave = mo.workorder_ids.leave_id
- self.assertEqual(bool(leave.exists()), True)
- # Unplans the MO and checks the workorder and its leave no more exist.
- mo.button_unplan()
- self.assertEqual(bool(mo.workorder_ids.exists()), True)
- self.assertEqual(bool(leave.exists()), False)
- # Plans (again) the MO and checks the date is still the same.
- mo.button_plan()
- self.assertAlmostEqual(mo.date_planned_start, planned_date, delta=timedelta(seconds=10))
- self.assertAlmostEqual(mo.date_planned_start, mo.workorder_ids.date_planned_start, delta=timedelta(seconds=10))
-
- def test_kit_planning(self):
- """ Bom made of component 1 and component 2 which is a kit made of
- component 1 too. Check the workorder lines are well created after reservation
- Main bom :
- - comp1 (qty=1)
- - kit (qty=1)
- - comp1 (qty=4)
- - comp2 (qty=1)
- should give :
- - wo line 1 (comp1, qty=1)
- - wo line 2 (comp1, qty=4)
- - wo line 3 (comp2, qty=1) """
- # Kit bom
- self.env['mrp.bom'].create({
- 'product_id': self.product_4.id,
- 'product_tmpl_id': self.product_4.product_tmpl_id.id,
- 'product_uom_id': self.uom_unit.id,
- 'product_qty': 1.0,
- 'consumption': 'flexible',
- 'type': 'phantom',
- 'bom_line_ids': [
- (0, 0, {'product_id': self.product_2.id, 'product_qty': 1}),
- (0, 0, {'product_id': self.product_1.id, 'product_qty': 4})
- ]})
-
- # Main bom
- main_bom = self.env['mrp.bom'].create({
- 'product_id': self.product_5.id,
- 'product_tmpl_id': self.product_5.product_tmpl_id.id,
- 'product_uom_id': self.uom_unit.id,
- 'product_qty': 1.0,
- 'consumption': 'flexible',
- 'operation_ids': [
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_1.id,
- 'name': 'Packing',
- 'time_cycle': 30,
- 'sequence': 5}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Testing',
- 'time_cycle': 60,
- 'sequence': 10}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Long time assembly',
- 'time_cycle': 180,
- 'sequence': 15}),
- ],
- 'type': 'normal',
- 'bom_line_ids': [
- (0, 0, {'product_id': self.product_1.id, 'product_qty': 1}),
- (0, 0, {'product_id': self.product_4.id, 'product_qty': 1})
- ]})
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_5
- mo_form.bom_id = main_bom
- mo_form.product_qty = 1
- mo = mo_form.save()
- mo.action_confirm()
- mo.action_assign()
- mo.button_plan()
-
- self.assertEqual(len(mo.workorder_ids), 3)
- long_time_assembly = mo.workorder_ids[2]
- self.assertEqual(len(long_time_assembly.raw_workorder_line_ids), 3)
- line1 = long_time_assembly.raw_workorder_line_ids[0]
- line2 = long_time_assembly.raw_workorder_line_ids[1]
- line3 = long_time_assembly.raw_workorder_line_ids[2]
- self.assertEqual(line1.product_id, self.product_1)
- self.assertEqual(line1.qty_done, 1)
- self.assertEqual(line2.product_id, self.product_2)
- self.assertEqual(line2.qty_done, 1)
- self.assertEqual(line3.product_id, self.product_1)
- self.assertEqual(line3.qty_done, 4)
-
- def test_conflict_and_replan(self):
- """ TEST Json data conflicted and the replan button of a workorder """
- dining_table = self.dining_table
- bom = self.mrp_bom_desk
- bom.operation_ids = False
- bom.write({
- 'operation_ids': [
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Packing',
- 'time_cycle': 30,
- 'sequence': 5}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Testing',
- 'time_cycle': 60,
- 'sequence': 10}),
- (0, 0, {
- 'workcenter_id': self.mrp_workcenter_3.id,
- 'name': 'Long time assembly',
- 'time_cycle': 180,
- 'sequence': 15}),
- ]})
-
-
- bom.bom_line_ids.filtered(lambda p: p.product_id == self.product_table_sheet).operation_id = bom.operation_ids[0].id
- bom.bom_line_ids.filtered(lambda p: p.product_id == self.product_table_leg).operation_id = bom.operation_ids[1].id
- bom.bom_line_ids.filtered(lambda p: p.product_id == self.product_bolt).operation_id = bom.operation_ids[2].id
-
- production_table_form = Form(self.env['mrp.production'])
- production_table_form.product_id = dining_table
- production_table_form.bom_id = bom
- production_table_form.product_qty = 1.0
- production_table_form.product_uom_id = dining_table.uom_id
- production_table = production_table_form.save()
-
- production_table.action_confirm()
- # Create work order
- production_table.button_plan()
- # Check Work order created or not
- self.assertEqual(len(production_table.workorder_ids), 3)
-
- workorders = production_table.workorder_ids
- wo1, wo2, wo3 = workorders[0], workorders[1], workorders[2]
-
- self.assertEqual(wo1.state, 'ready', "First workorder state should be ready.")
- self.assertEqual(wo1.workcenter_id.id, self.mrp_workcenter_3.id)
- self.assertEqual(wo2.state, 'pending')
- self.assertEqual(wo3.state, 'pending')
-
- self.assertFalse(wo1.id in wo1._get_conflicted_workorder_ids(), "Shouldn't conflict")
- self.assertFalse(wo2.id in wo2._get_conflicted_workorder_ids(), "Shouldn't conflict")
- self.assertFalse(wo3.id in wo3._get_conflicted_workorder_ids(), "Shouldn't conflict")
-
- # Conflicted with wo1
- wo2.write({'date_planned_start': wo1.date_planned_start, 'date_planned_finished': wo1.date_planned_finished})
- # Bad order of workorders (wo3-wo1-wo2) + Late
- wo3.write({'date_planned_start': wo1.date_planned_start - timedelta(weeks=1), 'date_planned_finished': wo1.date_planned_finished - timedelta(weeks=1)})
-
- self.assertTrue(wo2.id in wo2._get_conflicted_workorder_ids(), "Should conflict with wo1")
- self.assertTrue(wo1.id in wo1._get_conflicted_workorder_ids(), "Should conflict with wo2")
-
- self.assertTrue('text-danger' in wo2.json_popover, "Popover should in be in red (due to conflict)")
- self.assertTrue('text-danger' in wo3.json_popover, "Popover should in be in red (due to bad order of wo)")
- self.assertTrue('text-warning' in wo3.json_popover, "Popover contains of warning (late)")
-
- wo1.button_start()
- self.assertEqual(wo1.state, 'progress')
- self.assertEqual(wo2.id in wo2._get_conflicted_workorder_ids(), False, "Shouldn't have a conflict because wo1 is in progress")
-
- wo1_date_planned_start = wo1.date_planned_start
- wo2_date_planned_start = wo2.date_planned_start
- wo3_date_planned_start = wo3.date_planned_start
-
- wo2.action_replan() # Replan all MO of WO
-
- self.assertEqual(wo1.date_planned_start, wo1_date_planned_start, "Planned date of Workorder 1 shouldn't change (because it is in progress)")
- self.assertNotEqual(wo2.date_planned_start, wo2_date_planned_start, "Planned date of Workorder 2 should be updated")
- self.assertNotEqual(wo3.date_planned_start, wo3_date_planned_start, "Planned date of Workorder 3 should be updated")
- self.assertTrue(wo3.date_planned_start > wo2.date_planned_start, "Workorder 2 should be before the 3")
-
-
-class TestRoutingAndKits(SavepointCase):
- @classmethod
- def setUpClass(cls):
- """
- kit1 (consu)
- compkit1
- finished1
- compfinished1
-
- Finished1 (Bom1)
- - compfinished1
- - kit1
- Kit1 (BomKit1)
- - compkit1
-
- Rounting1 (finished1)
- - operation 1
- - operation 2
- Rounting2 (kit1)
- - operation 1
- """
- super(TestRoutingAndKits, cls).setUpClass()
- cls.uom_unit = cls.env['uom.uom'].search([
- ('category_id', '=', cls.env.ref('uom.product_uom_categ_unit').id),
- ('uom_type', '=', 'reference')
- ], limit=1)
- cls.kit1 = cls.env['product.product'].create({
- 'name': 'kit1',
- 'type': 'consu',
- })
- cls.compkit1 = cls.env['product.product'].create({
- 'name': 'compkit1',
- 'type': 'product',
- })
- cls.finished1 = cls.env['product.product'].create({
- 'name': 'finished1',
- 'type': 'product',
- })
- cls.compfinished1 = cls.env['product.product'].create({
- 'name': 'compfinished',
- 'type': 'product',
- })
- cls.workcenter_finished1 = cls.env['mrp.workcenter'].create({
- 'name': 'workcenter1',
- })
- cls.workcenter_kit1 = cls.env['mrp.workcenter'].create({
- 'name': 'workcenter2',
- })
- cls.bom_finished1 = cls.env['mrp.bom'].create({
- 'product_id': cls.finished1.id,
- 'product_tmpl_id': cls.finished1.product_tmpl_id.id,
- 'product_uom_id': cls.uom_unit.id,
- 'consumption': 'flexible',
- 'product_qty': 1,
- 'type': 'normal',
- 'bom_line_ids': [
- (0, 0, {'product_id': cls.compfinished1.id, 'product_qty': 1}),
- (0, 0, {'product_id': cls.kit1.id, 'product_qty': 1}),
- ],
- 'operation_ids': [
- (0, 0, {'sequence': 1, 'name': 'finished operation 1', 'workcenter_id': cls.workcenter_finished1.id}),
- (0, 0, {'sequence': 2, 'name': 'finished operation 2', 'workcenter_id': cls.workcenter_finished1.id}),
- ],
- })
- cls.bom_kit1 = cls.env['mrp.bom'].create({
- 'product_id': cls.kit1.id,
- 'product_tmpl_id': cls.kit1.product_tmpl_id.id,
- 'product_uom_id': cls.uom_unit.id,
- 'product_qty': 1,
- 'type': 'phantom',
- 'bom_line_ids': [
- (0, 0, {'product_id': cls.compkit1.id, 'product_qty': 1}),
- ],
- 'operation_ids': [
- (0, 0, {'name': 'Kit operation', 'workcenter_id': cls.workcenter_kit1.id})
- ]
- })
-
- def test_1(self):
- """Operations are set on `self.bom_kit1` but none on `self.bom_finished1`."""
- self.bom_kit1.bom_line_ids.operation_id = self.bom_kit1.operation_ids[0]
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.finished1
- mo_form.bom_id = self.bom_finished1
- mo_form.product_qty = 1.0
- mo = mo_form.save()
-
- mo.action_confirm()
- mo.button_plan()
-
- self.assertEqual(len(mo.workorder_ids), 3)
- self.assertEqual(len(mo.workorder_ids[0].raw_workorder_line_ids), 0)
- self.assertEqual(mo.workorder_ids[1].raw_workorder_line_ids.product_id, self.compfinished1)
- self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
-
- def test_2(self):
- """Operations are not set on `self.bom_kit1` and `self.bom_finished1`."""
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.finished1
- mo_form.bom_id = self.bom_finished1
- mo_form.product_qty = 1.0
- mo = mo_form.save()
-
- mo.action_confirm()
- mo.button_plan()
-
- self.assertEqual(len(mo.workorder_ids), 3)
- self.assertEqual(len(mo.workorder_ids[0].raw_workorder_line_ids), 0)
- self.assertEqual(mo.workorder_ids[1].raw_workorder_line_ids.product_id, self.compfinished1)
- self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
-
- def test_3(self):
- """Operations are set both `self.bom_kit1` and `self.bom_finished1`."""
- self.bom_kit1.bom_line_ids.operation_id = self.bom_kit1.operation_ids
- self.bom_finished1.bom_line_ids[0].operation_id = self.bom_finished1.operation_ids[0]
- self.bom_finished1.bom_line_ids[1].operation_id = self.bom_finished1.operation_ids[1]
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.finished1
- mo_form.bom_id = self.bom_finished1
- mo_form.product_qty = 1.0
- mo = mo_form.save()
-
- mo.action_confirm()
- mo.button_plan()
-
- self.assertEqual(len(mo.workorder_ids), 3)
- self.assertEqual(mo.workorder_ids[0].raw_workorder_line_ids.product_id, self.compfinished1)
- self.assertFalse(mo.workorder_ids[1].raw_workorder_line_ids.product_id.id)
- self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
-
- def test_4(self):
- """Operations are set on `self.kit1`, none are set on `self.bom_finished1` and a kit
- without routing was added to `self.bom_finished1`. We expect the component of the kit
- without routing to be consumed at the last workorder of the main BoM.
- """
- kit2 = self.env['product.product'].create({
- 'name': 'kit2',
- 'type': 'consu',
- })
- compkit2 = self.env['product.product'].create({
- 'name': 'compkit2',
- 'type': 'product',
- })
- bom_kit2 = self.env['mrp.bom'].create({
- 'product_id': kit2.id,
- 'product_tmpl_id': kit2.product_tmpl_id.id,
- 'product_uom_id': self.uom_unit.id,
- 'product_qty': 1,
- 'type': 'phantom',
- 'bom_line_ids': [(0, 0, {'product_id': compkit2.id, 'product_qty': 1})]
- })
- self.bom_finished1.write({'bom_line_ids': [(0, 0, {'product_id': kit2.id, 'product_qty': 1})]})
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.finished1
- mo_form.bom_id = self.bom_finished1
- mo_form.product_qty = 1.0
- mo = mo_form.save()
-
- mo.action_confirm()
- mo.button_plan()
-
- self.assertEqual(len(mo.workorder_ids), 3)
-
- self.assertEqual(len(mo.workorder_ids[0].raw_workorder_line_ids), 0)
- self.assertEqual(set(mo.workorder_ids[1].raw_workorder_line_ids.product_id.ids), set([self.compfinished1.id, compkit2.id]))
- self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
-
- def test_5(self):
- # Main bom: set the normal component to the first of the two operations of the routing.
- bomline_compfinished = self.bom_finished1.bom_line_ids.filtered(lambda bl: bl.product_id == self.compfinished1)
- bomline_compfinished.operation_id = self.bom_finished1.operation_ids[0]
-
- # Main bom: the kit do not have an operation set but there's one on its bom
- bomline_kit1 = self.bom_finished1.bom_line_ids - bomline_compfinished
- self.assertFalse(bomline_kit1.operation_id.id)
- self.bom_kit1.bom_line_ids.operation_id = self.bom_kit1.operation_ids
-
- # Main bom: add a kit without routing
- kit2 = self.env['product.product'].create({
- 'name': 'kit2',
- 'type': 'consu',
- })
- compkit2 = self.env['product.product'].create({
- 'name': 'compkit2',
- 'type': 'product',
- })
- bom_kit2 = self.env['mrp.bom'].create({
- 'product_id': kit2.id,
- 'product_tmpl_id': kit2.product_tmpl_id.id,
- 'product_uom_id': self.uom_unit.id,
- 'product_qty': 1,
- 'type': 'phantom',
- 'bom_line_ids': [(0, 0, {'product_id': compkit2.id, 'product_qty': 1})]
- })
- self.bom_finished1.write({'bom_line_ids': [(0, 0, {'product_id': kit2.id, 'product_qty': 1})]})
-
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.finished1
- mo_form.bom_id = self.bom_finished1
- mo_form.product_qty = 1.0
- mo = mo_form.save()
-
- mo.action_confirm()
- mo.button_plan()
-
- self.assertEqual(len(mo.workorder_ids), 3)
- self.assertEqual(mo.workorder_ids[0].raw_workorder_line_ids.product_id, self.compfinished1)
- self.assertEqual(mo.workorder_ids[1].raw_workorder_line_ids.product_id, compkit2)
- self.assertEqual(mo.workorder_ids[2].raw_workorder_line_ids.product_id, self.compkit1)
-
- # -------------------------------------------------------------------------
- # Those 2 next tests aren't related to routing and kit but to flexible
- # consumption.
- # -------------------------------------------------------------------------
- def test_merge_lot(self):
- """ Produce 10 units of product tracked by lot on two workorder. On the
- first one, produce 4 onto lot1 then 6 onto lot1 as well. The second
- workorder should be prefilled with 10 units and lot1"""
- self.finished1.tracking = 'lot'
- lot1 = self.env['stock.production.lot'].create({
- 'product_id': self.finished1.id,
- 'company_id': self.env.company.id,
- })
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.finished1
- mo_form.bom_id = self.bom_finished1
- mo_form.product_qty = 10.0
- mo = mo_form.save()
-
- mo.action_confirm()
- mo.button_plan()
- wo1 = mo.workorder_ids.filtered(lambda wo: wo.state == 'ready')[0]
- wo1.button_start()
- wo1.qty_producing = 4
- wo1.finished_lot_id = lot1
- wo1.record_production()
- wo1.qty_producing = 6
- wo1.finished_lot_id = lot1
- wo1.record_production()
- wo2 = mo.workorder_ids.filtered(lambda wo: wo.state == 'ready')[0]
- wo2.button_start()
- self.assertEqual(wo2.qty_producing, 10)
- self.assertEqual(wo2.finished_lot_id, lot1)
-
- def test_add_move(self):
- """ Make a production using multi step routing. Add an additional move
- on a specific operation and check that the produce is consumed into the
- right workorder. """
- self.bom_finished1.consumption = 'flexible'
- add_product = self.env['product.product'].create({
- 'name': 'Additional',
- 'type': 'product',
- })
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.finished1
- mo_form.bom_id = self.bom_finished1
- mo_form.product_qty = 10.0
- mo = mo_form.save()
-
- mo_form = Form(mo)
- with mo_form.move_raw_ids.new() as move:
- move.name = mo.name
- move.product_id = add_product
- move.product_uom = add_product.uom_id
- move.location_id = mo.location_src_id
- move.location_dest_id = mo.production_location_id
- move.product_uom_qty = 2
- move.operation_id = mo.bom_id.operation_ids[0]
- mo = mo_form.save()
- self.assertEqual(len(mo.move_raw_ids), 3)
- mo.action_confirm()
- self.assertEqual(mo.move_raw_ids.mapped('state'), ['confirmed'] * 3)
- mo.button_plan()
- self.assertEqual(len(mo.workorder_ids), 3)
- wo1 = mo.workorder_ids[0]
- lines = wo1.raw_workorder_line_ids
- self.assertEqual(lines.product_id, add_product)
-
- def test_add_move_2(self):
- """ Make a production using multi step routing. Add an additional move
- on a specific operation and check that the produce is consumed into the
- right workorder. """
- self.bom_finished1.consumption = 'flexible'
- add_product = self.env['product.product'].create({
- 'name': 'Additional',
- 'type': 'product',
- })
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.finished1
- mo_form.bom_id = self.bom_finished1
- mo_form.product_qty = 10.0
- mo = mo_form.save()
- mo.action_confirm()
- mo_form = Form(mo)
- with mo_form.move_raw_ids.new() as move:
- move.name = mo.name
- move.product_id = add_product
- move.product_uom = add_product.uom_id
- move.location_id = mo.location_src_id
- move.location_dest_id = mo.production_location_id
- move.product_uom_qty = 2
- move.operation_id = mo.bom_id.operation_ids[0]
- mo = mo_form.save()
- new_move = mo.move_raw_ids.filtered(lambda move: move.additional)
- self.assertEqual(len(mo.move_raw_ids), 3)
- self.assertEqual(len(new_move), 1)
- self.assertEqual(mo.move_raw_ids.mapped('state'), ['confirmed'] * 3)
- mo.button_plan()
- self.assertEqual(len(mo.workorder_ids), 3)
- wo1 = mo.workorder_ids[0]
- lines = wo1.raw_workorder_line_ids
- self.assertEqual(lines.product_id, add_product)
diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml
index e3ce89b4a53..25a2c2f5dab 100644
--- a/addons/mrp/views/mrp_production_views.xml
+++ b/addons/mrp/views/mrp_production_views.xml
@@ -286,43 +286,7 @@
</field>
</page>
<page string="Operations" name="operations" groups="mrp.group_mrp_routings">
- <field name="workorder_ids" attrs="{'readonly': [('state', 'in', ['cancel', 'done'])]}" context="{'default_production_id': id, 'default_qty_producing': product_qty, 'default_product_uom_id': product_uom_id, 'default_consumption': consumption, 'default_company_id': company_id}">
- <tree editable="bottom">
- <field name="consumption" invisible="1"/>
- <field name="company_id" invisible="1"/>
- <field name="is_produced" invisible="1"/>
- <field name="is_user_working" invisible="1"/>
- <field name="name" invisible="1"/>
- <field name="product_uom_id" invisible="1" readonly="0"/>
- <field name="qty_producing" invisible="1"/>
- <field name="time_ids" invisible="1"/>
- <field name="working_state" invisible="1"/>
- <field name="operation_id" invisible="1" domain="['|', ('bom_id', '=', parent.bom_id), ('bom_id', '=', False)]" context="{'default_workcenter_id': workcenter_id, 'default_company_id': company_id}"/>
- <field name="name" string="Operation"/>
- <field name="workcenter_id"/>
- <field name="date_planned_start" optional="show"/>
- <field name="date_planned_finished" optional="hide"/>
- <field name="date_start" optional="hide" readonly="1"/>
- <field name="date_finished" optional="hide" readonly="1"/>
- <field name="duration_expected" widget="float_time" sum="Total excepted duration"/>
- <field name="duration" widget="mrp_time_counter" help="Time the currently logged user spent on this workorder."
- attrs="{'invisible': ['|', ('parent.state','=', 'draft'), ('time_ids', '=', [])]}"/>
- <button name="action_open_wizard" type="object" icon="fa-external-link" width="0" class="oe_edit_only"
- context="{'default_workcenter_id': workcenter_id}"/>
- <field name="state" widget="badge" decoration-success="state == 'done'" decoration-info="state not in ('done', 'cancel')"/>
- <button name="button_start" type="object" string="Start" width="0" class="btn-success"
- attrs="{'invisible': ['|', '|', '|', ('parent.state','in', ('draft', 'done')), ('working_state', '=', 'blocked'), ('state', '=', 'done'), ('is_user_working', '!=', False)]}"/>
- <button name="button_pending" type="object" string="Pause" width="0" class="btn-warning"
- attrs="{'invisible': ['|', '|', ('parent.state', 'in', ('draft', 'done')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False)]}"/>
- <button name="button_finish" type="object" string="Done" width="0" class="btn-success"
- attrs="{'invisible': ['|', '|', ('parent.state', 'in', ('draft', 'done')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False)]}"/>
- <button name="%(mrp.act_mrp_block_workcenter_wo)d" type="action" string="Block" context="{'default_workcenter_id': workcenter_id}" width="0" class="btn-danger"
- attrs="{'invisible': ['|', ('parent.state', 'in', ('draft', 'done')), ('working_state', '=', 'blocked')]}"/>
- <button name="button_unblock" type="object" string="Unblock" context="{'default_workcenter_id': workcenter_id}" width="0" class="btn-danger"
- attrs="{'invisible': ['|', ('parent.state', 'in', ('draft', 'done')), ('working_state', '!=', 'blocked')]}"/>
- </tree>
- </field>
- </page>
+ <field name="workorder_ids" attrs="{'readonly': [('state', 'in', ['cancel', 'done'])]}" context="{'tree_view_ref': 'mrp.mrp_production_workorder_tree_editable_view', 'default_production_id': id, 'default_product_uom_id': product_uom_id, 'default_consumption': consumption, 'default_company_id': company_id}"/> </page>
<page string="Miscellaneous" name="miscellaneous" groups="stock.group_stock_multi_locations">
<group>
<group>
diff --git a/addons/mrp/views/mrp_workorder_views.xml b/addons/mrp/views/mrp_workorder_views.xml
index 081dc74ab02..9b4bfc5196a 100644
--- a/addons/mrp/views/mrp_workorder_views.xml
+++ b/addons/mrp/views/mrp_workorder_views.xml
@@ -89,7 +89,6 @@
<field name="is_user_working" invisible="1"/>
<field name="working_state" invisible="1"/>
<field name="production_state" invisible="1"/>
- <field name="allowed_lots_domain" invisible="1"/>
<field name="is_finished_lines_editable" invisible="1"/>
<header>
<field name="state" widget="statusbar" statusbar_visible="pending,ready,progress,done"/>
@@ -107,6 +106,7 @@
<field name="company_id" invisible="1"/>
<field name="product_tracking" invisible="1"/>
<field name="production_id" invisible="1"/>
+ <field name="product_id" invisible="1"/>
<field name="finished_lot_id" invisible="1"/>
<field name="qty_producing" invisible="1"/>
<group attrs="{'invisible': [('date_planned_start', '=', False)]}">
diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py
index 72d00e9b348..bacce8e4ca5 100644
--- a/addons/mrp/wizard/change_production_qty.py
+++ b/addons/mrp/wizard/change_production_qty.py
@@ -50,11 +50,11 @@ class ChangeProductionQty(models.TransientModel):
format_qty = '%.{precision}f'.format(precision=precision)
raise UserError(_("You have already processed %s. Please input a quantity higher than %s ") % (format_qty % produced, format_qty % produced))
old_production_qty = production.product_qty
- production.write({'product_qty': wizard.product_qty})
+ new_production_qty = wizard.product_qty
done_moves = production.move_finished_ids.filtered(lambda x: x.state == 'done' and x.product_id == production.product_id)
qty_produced = production.product_id.uom_id._compute_quantity(sum(done_moves.mapped('product_qty')), production.product_uom_id)
- factor = (production.product_qty - qty_produced) / (old_production_qty - qty_produced)
+ factor = (new_production_qty - qty_produced) / (old_production_qty - qty_produced)
update_info = production._update_raw_moves(factor)
documents = {}
for move, old_qty, new_qty in update_info:
@@ -67,9 +67,10 @@ class ChangeProductionQty(models.TransientModel):
else:
documents[key] = [value]
production._log_manufacture_exception(documents)
- finished_moves_modification = self._update_finished_moves(production, production.product_qty - qty_produced, old_production_qty - qty_produced)
+ finished_moves_modification = self._update_finished_moves(production, new_production_qty - qty_produced, old_production_qty - qty_produced)
if finished_moves_modification:
production._log_downside_manufactured_quantity(finished_moves_modification)
+ production.write({'product_qty': new_production_qty})
for wo in production.workorder_ids:
operation = wo.operation_id
@@ -80,9 +81,9 @@ class ChangeProductionQty(models.TransientModel):
else:
quantity = quantity if (quantity > 0) else 0
if float_is_zero(quantity, precision_digits=precision):
- wo.finished_lot_id = False
- wo._workorder_line_ids().unlink()
- wo.qty_producing = quantity
+ wo.check_ids.unlink()
+ else:
+ wo.qty_producing = quantity
if wo.qty_produced < wo.qty_production and wo.state == 'done':
wo.state = 'progress'
if wo.qty_produced == wo.qty_production and wo.state == 'progress':
diff --git a/addons/mrp_account/tests/test_mrp_account.py b/addons/mrp_account/tests/test_mrp_account.py
index f77dbfc3c6e..f827a0eb023 100644
--- a/addons/mrp_account/tests/test_mrp_account.py
+++ b/addons/mrp_account/tests/test_mrp_account.py
@@ -1,16 +1,120 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo.addons.mrp.tests.test_workorder_operation import TestWorkOrderProcessCommon
+from odoo.addons.mrp.tests.common import TestMrpCommon
from odoo.tests import Form
-class TestMrpAccount(TestWorkOrderProcessCommon):
+class TestMrpAccount(TestMrpCommon):
@classmethod
def setUpClass(cls):
super(TestMrpAccount, cls).setUpClass()
+ cls.source_location_id = cls.stock_location_14.id
+ cls.warehouse = cls.env.ref('stock.warehouse0')
+ # setting up alternative workcenters
+ cls.wc_alt_1 = cls.env['mrp.workcenter'].create({
+ 'name': 'Nuclear Workcenter bis',
+ 'capacity': 3,
+ 'time_start': 9,
+ 'time_stop': 5,
+ 'time_efficiency': 80,
+ })
+ cls.wc_alt_2 = cls.env['mrp.workcenter'].create({
+ 'name': 'Nuclear Workcenter ter',
+ 'capacity': 1,
+ 'time_start': 10,
+ 'time_stop': 5,
+ 'time_efficiency': 85,
+ })
+ cls.product_4.uom_id = cls.uom_unit
+ cls.planning_bom = cls.env['mrp.bom'].create({
+ 'product_id': cls.product_4.id,
+ 'product_tmpl_id': cls.product_4.product_tmpl_id.id,
+ 'product_uom_id': cls.uom_unit.id,
+ 'product_qty': 4.0,
+ 'consumption': 'flexible',
+ 'operation_ids': [
+ (0, 0, {'name': 'Gift Wrap Maching', 'workcenter_id': cls.workcenter_1.id, 'time_cycle': 15, 'sequence': 1}),
+ ],
+ 'type': 'normal',
+ 'bom_line_ids': [
+ (0, 0, {'product_id': cls.product_2.id, 'product_qty': 2}),
+ (0, 0, {'product_id': cls.product_1.id, 'product_qty': 4})
+ ]})
+ cls.dining_table = cls.env['product.product'].create({
+ 'name': 'Table (MTO)',
+ 'type': 'product',
+ 'tracking': 'serial',
+ })
+ cls.product_table_sheet = cls.env['product.product'].create({
+ 'name': 'Table Top',
+ 'type': 'product',
+ 'tracking': 'serial',
+ })
+ cls.product_table_leg = cls.env['product.product'].create({
+ 'name': 'Table Leg',
+ 'type': 'product',
+ 'tracking': 'lot',
+ })
+ cls.product_bolt = cls.env['product.product'].create({
+ 'name': 'Bolt',
+ 'type': 'product',
+ })
+ cls.product_screw = cls.env['product.product'].create({
+ 'name': 'Screw',
+ 'type': 'product',
+ })
+ cls.mrp_workcenter = cls.env['mrp.workcenter'].create({
+ 'name': 'Assembly Line 1',
+ 'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id,
+ })
+ cls.mrp_bom_desk = cls.env['mrp.bom'].create({
+ 'product_tmpl_id': cls.dining_table.product_tmpl_id.id,
+ 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
+ 'sequence': 3,
+ 'consumption': 'flexible',
+ 'operation_ids': [
+ (0, 0, {'workcenter_id': cls.mrp_workcenter.id, 'name': 'Manual Assembly'}),
+ ],
+ })
+ cls.mrp_bom_desk.write({
+ 'bom_line_ids': [
+ (0, 0, {
+ 'product_id': cls.product_table_sheet.id,
+ 'product_qty': 1,
+ 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
+ 'sequence': 1,
+ 'operation_id': cls.mrp_bom_desk.operation_ids.id}),
+ (0, 0, {
+ 'product_id': cls.product_table_leg.id,
+ 'product_qty': 4,
+ 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
+ 'sequence': 2,
+ 'operation_id': cls.mrp_bom_desk.operation_ids.id}),
+ (0, 0, {
+ 'product_id': cls.product_bolt.id,
+ 'product_qty': 4,
+ 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
+ 'sequence': 3,
+ 'operation_id': cls.mrp_bom_desk.operation_ids.id}),
+ (0, 0, {
+ 'product_id': cls.product_screw.id,
+ 'product_qty': 10,
+ 'product_uom_id': cls.env.ref('uom.product_uom_unit').id,
+ 'sequence': 4,
+ 'operation_id': cls.mrp_bom_desk.operation_ids.id}),
+ ]
+ })
+ cls.mrp_workcenter_1 = cls.env['mrp.workcenter'].create({
+ 'name': 'Drill Station 1',
+ 'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id,
+ })
+ cls.mrp_workcenter_3 = cls.env['mrp.workcenter'].create({
+ 'name': 'Assembly Line 1',
+ 'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id,
+ })
cls.categ_standard = cls.env['product.category'].create({
'name': 'STANDARD',
'property_cost_method': 'standard'
@@ -66,10 +170,11 @@ class TestMrpAccount(TestWorkOrderProcessCommon):
})
inventory.action_validate
bom = self.mrp_bom_desk.copy()
+ bom.operation_ids = False
production_table_form = Form(self.env['mrp.production'])
production_table_form.product_id = self.dining_table
production_table_form.bom_id = bom
- production_table_form.product_qty = 5.0
+ production_table_form.product_qty = 1
production_table = production_table_form.save()
production_table.extra_cost = 20
diff --git a/addons/mrp_product_expiry/models/__init__.py b/addons/mrp_product_expiry/models/__init__.py
index 51e07c35917..3728a50a4f0 100644
--- a/addons/mrp_product_expiry/models/__init__.py
+++ b/addons/mrp_product_expiry/models/__init__.py
@@ -2,4 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mrp_production
-from . import mrp_workorder
+
diff --git a/addons/mrp_product_expiry/models/mrp_workorder.py b/addons/mrp_product_expiry/models/mrp_workorder.py
deleted file mode 100644
index 0acc06d829b..00000000000
--- a/addons/mrp_product_expiry/models/mrp_workorder.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import models, _
-
-
-class MrpWorkorder(models.Model):
- _inherit = 'mrp.workorder'
-
- def record_production(self):
- confirm_expired_lots = self._check_expired_lots()
- if confirm_expired_lots:
- return confirm_expired_lots
- return super().record_production()
-
- def _check_expired_lots(self):
- # We use the 'skip_expired' context key to avoid to make the check when
- # user already confirmed the wizard about using expired lots.
- if self.env.context.get('skip_expired'):
- return False
- expired_lot_ids = self.raw_workorder_line_ids.filtered(lambda ml: ml.lot_id.product_expiry_alert).lot_id.ids
- if expired_lot_ids:
- return {
- 'name': _('Confirmation'),
- 'type': 'ir.actions.act_window',
- 'res_model': 'expiry.picking.confirmation',
- 'view_mode': 'form',
- 'target': 'new',
- 'context': self._get_expired_context(expired_lot_ids),
- }
-
- def _get_expired_context(self, expired_lot_ids):
- context = dict(self.env.context)
- context.update({
- 'default_lot_ids': [(6, 0, expired_lot_ids)],
- 'default_workorder_id': self.id,
- })
- return context
diff --git a/addons/mrp_product_expiry/tests/test_mrp_product_expiry.py b/addons/mrp_product_expiry/tests/test_mrp_product_expiry.py
index 22e9764d2fd..137877c95a0 100644
--- a/addons/mrp_product_expiry/tests/test_mrp_product_expiry.py
+++ b/addons/mrp_product_expiry/tests/test_mrp_product_expiry.py
@@ -113,71 +113,3 @@ class TestStockProductionLot(TestStockCommon):
self.assertNotEqual(res, None)
self.assertEqual(res['res_model'], 'expiry.picking.confirmation')
- def test_03_workorder_without_expired_lot(self):
- """ Checks user doesn't get a confirmation wizard when they makes a
- workorder without expired components. """
- # Set a routing on the BOM.
- self.bom_apple_pie.write({
- 'operation_ids': [(0, 0, {
- 'name': 'Bake in the oven',
- 'workcenter_id': self.workcenter.id,
- 'time_cycle': 15,
- 'sequence': 1,
- })],
- })
- # Creates the MO, starts it and plans the Work Order.
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_apple_pie
- mo_form.bom_id = self.bom_apple_pie
- mo_form.product_qty = 1
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
-
- wo = mo.workorder_ids[0]
- wo.button_start()
- # Set a non-expired lot.
- wo.raw_workorder_line_ids.write({
- 'qty_done': 3,
- 'lot_id': self.lot_good_apple,
- })
-
- res = wo.record_production()
- # Try to record the production using non-expired lot must not return a wizard.
- self.assertEqual(res, True)
- mo.button_mark_done()
-
- def test_04_workorder_with_expired_lot(self):
- """ Checks user doesn't get a confirmation wizard when they makes a
- workorder without expired components. """
- # Set a routing on the BOM.
- self.bom_apple_pie.write({
- 'operation_ids': [(0, 0, {
- 'name': 'Bake in the oven',
- 'workcenter_id': self.workcenter.id,
- 'time_cycle': 15,
- 'sequence': 1,
- })],
- })
- # Creates the MO, starts it and plans the Work Order.
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = self.product_apple_pie
- mo_form.bom_id = self.bom_apple_pie
- mo_form.product_qty = 1
- mo = mo_form.save()
- mo.action_confirm()
- mo.button_plan()
-
- wo = mo.workorder_ids[0]
- wo.button_start()
- # Set an expired lot.
- wo.raw_workorder_line_ids.write({
- 'qty_done': 3,
- 'lot_id': self.lot_expired_apple,
- })
-
- res = wo.record_production()
- # Try to record the production using expired lot must return a
- # confirmation wizard.
- self.assertNotEqual(res, None)
- self.assertEqual(res['res_model'], 'expiry.picking.confirmation')
diff --git a/addons/mrp_subcontracting/models/stock_picking.py b/addons/mrp_subcontracting/models/stock_picking.py
index 43cba77b1a3..137015dc846 100644
--- a/addons/mrp_subcontracting/models/stock_picking.py
+++ b/addons/mrp_subcontracting/models/stock_picking.py
@@ -73,13 +73,7 @@ class StockPicking(models.Model):
(production.qty_producing - production.qty_produced) * move.unit_factor, production.product_uom_id,
rounding_method='HALF-UP')
move.move_line_ids.filtered(lambda ml: ml.state not in ('done', 'cancel')).qty_done = 0
- vals = move._set_quantity_done_prepare_vals(new_qty)
- if vals['to_create']:
- for res in vals['to_create']:
- move.move_line_ids.create(res)
- if vals['to_write']:
- for move_line, res in vals['to_write']:
- move_line.write(res)
+ move.move_line_ids = move._set_quantity_done_prepare_vals(new_qty)
productions |= production
for subcontracted_production in productions:
if subcontracted_production.state == 'progress':
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 0af3341055f..f008e4160e8 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -1552,7 +1552,7 @@ class StockMove(models.Model):
return [(self.picking_id, self.product_id.responsible_id, visited)]
def _set_quantity_done_prepare_vals(self, qty):
- res = {'to_write': [], 'to_create': []}
+ res = []
for ml in self.move_line_ids:
ml_qty = ml.product_uom_qty - ml.qty_done
if float_compare(ml_qty, 0, precision_rounding=ml.product_uom_id.rounding) <= 0:
@@ -1569,7 +1569,7 @@ class StockMove(models.Model):
# Assign qty_done and explicitly round to make sure there is no inconsistency between
# ml.qty_done and qty.
taken_qty = float_round(taken_qty, precision_rounding=ml.product_uom_id.rounding)
- res['to_write'].append((ml, {'qty_done': ml.qty_done + taken_qty}))
+ res.append((1, ml.id, {'qty_done': ml.qty_done + taken_qty}))
if ml.product_uom_id != self.product_uom:
taken_qty = ml.product_uom_id._compute_quantity(ml_qty, self.product_uom, round=False)
qty -= taken_qty
@@ -1580,14 +1580,14 @@ class StockMove(models.Model):
if self.product_id.tracking != 'serial':
vals = self._prepare_move_line_vals(quantity=0)
vals['qty_done'] = qty
- res['to_create'].append(vals)
+ res.append((0, 0, vals))
else:
uom_qty = self.product_uom._compute_quantity(qty, self.product_id.uom_id)
for i in range(0, int(uom_qty)):
vals = self._prepare_move_line_vals(quantity=0)
vals['qty_done'] = 1
vals['product_uom_id'] = self.product_id.uom_id.id
- res['to_create'].append(vals)
+ res.append((0, 0, vals))
return res
def _set_quantity_done(self, qty):
@@ -1597,13 +1597,7 @@ class StockMove(models.Model):
looking for trouble...).
@param qty: quantity in the UoM of move.product_uom
"""
- vals = self._set_quantity_done_prepare_vals(qty)
- if vals['to_create']:
- for res in vals['to_create']:
- self.env['stock.move.line'].create(res)
- if vals['to_write']:
- for move_line, vals in vals['to_write']:
- move_line.write(vals)
+ self.move_line_ids = self._set_quantity_done_prepare_vals(qty)
def _adjust_procure_method(self):
""" This method will try to apply the procure method MTO on some moves if