From 0e2765f5fd0ca0079f1f024ec3a1448b6d2ea3db Mon Sep 17 00:00:00 2001 From: William Henrotin Date: Mon, 27 Apr 2020 11:56:02 +0200 Subject: [PATCH] [REF] mrp: no more produce wizard Use a view similar as the pickings one. task-2241471 --- addons/mrp/__manifest__.py | 1 - addons/mrp/data/mrp_demo.xml | 27 +- addons/mrp/models/mrp_abstract_workorder.py | 10 +- addons/mrp/models/mrp_bom.py | 2 +- addons/mrp/models/mrp_production.py | 203 +++++- addons/mrp/models/mrp_unbuild.py | 2 +- addons/mrp/models/stock_move.py | 94 ++- addons/mrp/security/ir.model.access.csv | 2 - .../mrp/static/src/js/mrp_should_consume.js | 81 +++ addons/mrp/static/src/scss/mrp_fields.scss | 5 +- addons/mrp/tests/common.py | 3 +- addons/mrp/tests/test_backorder.py | 28 +- addons/mrp/tests/test_byproduct.py | 10 +- addons/mrp/tests/test_cancel_mo.py | 34 +- addons/mrp/tests/test_multicompany.py | 26 +- addons/mrp/tests/test_order.py | 674 +++++------------- addons/mrp/tests/test_procurement.py | 41 +- addons/mrp/tests/test_stock.py | 12 +- addons/mrp/tests/test_traceability.py | 139 ++-- addons/mrp/tests/test_unbuild.py | 130 ++-- .../test_warehouse_multistep_manufacturing.py | 8 +- addons/mrp/tests/test_workorder_operation.py | 187 +---- addons/mrp/views/mrp_production_views.xml | 151 +++- addons/mrp/views/mrp_templates.xml | 1 + addons/mrp/views/stock_move_views.xml | 151 +--- addons/mrp/wizard/__init__.py | 1 - addons/mrp/wizard/mrp_product_produce.py | 154 ---- .../mrp/wizard/mrp_product_produce_views.xml | 134 ---- addons/mrp_account/tests/test_mrp_account.py | 10 +- .../tests/test_valuation_layers.py | 18 +- .../views/mrp_production_views.xml | 2 +- .../tests/test_stock_landed_costs_mrp.py | 11 +- addons/mrp_product_expiry/models/__init__.py | 2 +- ...bstract_workorder.py => mrp_production.py} | 13 +- .../models/mrp_workorder.py | 27 +- .../tests/test_mrp_product_expiry.py | 40 +- addons/mrp_product_expiry/wizard/__init__.py | 1 - .../wizard/confirm_expiry.py | 11 +- .../wizard/confirm_expiry_view.xml | 11 +- .../wizard/mrp_product_produce.py | 19 - .../mrp_subcontracting/models/stock_move.py | 25 +- .../models/stock_picking.py | 34 +- .../tests/test_subcontracting.py | 95 +-- .../views/stock_move_views.xml | 31 +- .../wizard/mrp_product_produce.py | 80 +-- addons/sale_mrp/tests/test_sale_mrp_flow.py | 21 +- addons/stock/models/stock_move.py | 6 +- .../static/tests/tours/main_flow.js | 16 +- 48 files changed, 1163 insertions(+), 1621 deletions(-) create mode 100644 addons/mrp/static/src/js/mrp_should_consume.js delete mode 100644 addons/mrp/wizard/mrp_product_produce.py delete mode 100644 addons/mrp/wizard/mrp_product_produce_views.xml rename addons/mrp_product_expiry/models/{mrp_abstract_workorder.py => mrp_production.py} (67%) delete mode 100644 addons/mrp_product_expiry/wizard/mrp_product_produce.py diff --git a/addons/mrp/__manifest__.py b/addons/mrp/__manifest__.py index 3278695d7dd..34b915039db 100644 --- a/addons/mrp/__manifest__.py +++ b/addons/mrp/__manifest__.py @@ -15,7 +15,6 @@ 'security/mrp_security.xml', 'security/ir.model.access.csv', 'data/mrp_data.xml', - 'wizard/mrp_product_produce_views.xml', 'wizard/change_production_qty_views.xml', 'wizard/mrp_workcenter_block_view.xml', 'wizard/stock_warn_insufficient_qty_views.xml', diff --git a/addons/mrp/data/mrp_demo.xml b/addons/mrp/data/mrp_demo.xml index da787db79cd..3ef43e55024 100644 --- a/addons/mrp/data/mrp_demo.xml +++ b/addons/mrp/data/mrp_demo.xml @@ -684,29 +684,18 @@ + + + + + - - - - - - - - - - + + + diff --git a/addons/mrp/models/mrp_abstract_workorder.py b/addons/mrp/models/mrp_abstract_workorder.py index 13d560f2320..54edf3e8ffb 100644 --- a/addons/mrp/models/mrp_abstract_workorder.py +++ b/addons/mrp/models/mrp_abstract_workorder.py @@ -186,7 +186,7 @@ class MrpAbstractWorkorder(models.AbstractModel): if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) <= 0: break # move line already 'used' in workorder (from its lot for instance) - if move_line.lot_produced_ids or float_compare(move_line.product_uom_qty, move_line.qty_done, precision_rounding=move.product_uom.rounding) <= 0: + if float_compare(move_line.product_uom_qty, move_line.qty_done, precision_rounding=move.product_uom.rounding) <= 0: continue # search wo line on which the lot is not fully consumed or other reserved lot linked_wo_line = self._workorder_line_ids().filtered( @@ -371,9 +371,9 @@ class MrpAbstractWorkorderLine(models.AbstractModel): """ update a move line to save the workorder line data""" self.ensure_one() if self.lot_id: - move_lines = self.move_id.move_line_ids.filtered(lambda ml: ml.lot_id == self.lot_id and not ml.lot_produced_ids) + move_lines = self.move_id.move_line_ids.filtered(lambda ml: ml.lot_id == self.lot_id) else: - move_lines = self.move_id.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_produced_ids) + move_lines = self.move_id.move_line_ids.filtered(lambda ml: not ml.lot_id) # Sanity check: if the product is a serial number and `lot` is already present in the other # consumed move lines, raise. @@ -396,14 +396,12 @@ class MrpAbstractWorkorderLine(models.AbstractModel): if float_compare(new_quantity_done, ml.product_uom_qty, precision_rounding=rounding) >= 0: ml.write({ 'qty_done': new_quantity_done, - 'lot_produced_ids': self._get_produced_lots(), }) else: new_qty_reserved = ml.product_uom_qty - new_quantity_done default = { 'product_uom_qty': new_quantity_done, 'qty_done': new_quantity_done, - 'lot_produced_ids': self._get_produced_lots(), } ml.copy(default=default) ml.with_context(bypass_reservation_update=True).write({ @@ -436,7 +434,6 @@ class MrpAbstractWorkorderLine(models.AbstractModel): 'product_uom_qty': 0, 'product_uom_id': self.product_uom_id.id, 'qty_done': min(quantity, self.qty_done), - 'lot_produced_ids': self._get_produced_lots(), } if self.lot_id: vals.update({'lot_id': self.lot_id.id}) @@ -456,7 +453,6 @@ class MrpAbstractWorkorderLine(models.AbstractModel): 'product_uom_qty': 0, 'product_uom_id': self.product_uom_id.id, 'qty_done': self.qty_done, - 'lot_produced_ids': self._get_produced_lots(), } if self.lot_id: vals.update({'lot_id': self.lot_id.id}) diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py index 127f07af4b4..c999122c578 100644 --- a/addons/mrp/models/mrp_bom.py +++ b/addons/mrp/models/mrp_bom.py @@ -66,7 +66,7 @@ class MrpBom(models.Model): ('strict', 'Strict'), ('flexible', 'Flexible')], help="Defines if you can consume more or less components than the quantity defined on the BoM.", - default='strict', + default='flexible', string='Consumption', required=True ) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 06bf4fdee40..1398a6f1916 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -72,6 +72,7 @@ class MrpProduction(models.Model): domain="[('id', 'in', allowed_product_ids)]", readonly=True, required=True, check_company=True, states={'draft': [('readonly', False)]}) + product_tracking = fields.Selection(related='product_id.tracking') allowed_product_ids = fields.Many2many('product.product', compute='_compute_allowed_product_ids') product_tmpl_id = fields.Many2one('product.template', 'Product Template', related='product_id.product_tmpl_id') product_qty = fields.Float( @@ -83,6 +84,10 @@ class MrpProduction(models.Model): 'uom.uom', 'Product Unit of Measure', readonly=True, required=True, states={'draft': [('readonly', False)]}, domain="[('category_id', '=', product_uom_category_id)]") + lot_producing_id = fields.Many2one( + 'stock.production.lot', string='Lot/Serial Number', copy=False, + domain="[('product_id', '=', product_id), ('company_id', '=', company_id)]", check_company=True) + qty_producing = fields.Float(string="Quantity Producing", copy=False) product_uom_category_id = fields.Many2one(related='product_id.uom_id.category_id') product_uom_qty = fields.Float(string='Total Quantity', compute='_compute_product_uom_qty', store=True) picking_type_id = fields.Many2one( @@ -175,6 +180,7 @@ class MrpProduction(models.Model): 'stock.move', 'production_id', 'Finished Products', copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, domain=[('scrapped', '=', False)]) + move_byproduct_ids = fields.One2many('stock.move', compute='_compute_move_byproduct_ids', inverse='_set_move_byproduct_ids') finished_move_line_ids = fields.One2many( 'stock.move.line', compute='_compute_lines', inverse='_inverse_lines', string="Finished Product" ) @@ -454,8 +460,9 @@ class MrpProduction(models.Model): for order in self: already_reserved = order.is_locked and order.state not in ('done', 'cancel') and order.mapped('move_raw_ids.move_line_ids') any_quantity_done = any([m.quantity_done > 0 for m in order.move_raw_ids]) + order.unreserve_visible = not any_quantity_done and already_reserved - order.reserve_visible = order.state in ('confirmed', 'planned') and any(move.state in ['confirmed', 'partially_available'] for move in order.move_raw_ids) + order.reserve_visible = order.state in ('confirmed', 'progress', 'to_close') and any(move.state in ['confirmed', 'partially_available'] for move in order.move_raw_ids.filtered(lambda m: m.product_uom_qty)) @api.depends('move_finished_ids.quantity_done', 'move_finished_ids.state', 'is_locked') def _compute_post_visible(self): @@ -476,6 +483,14 @@ class MrpProduction(models.Model): for production in self: production.scrap_count = count_data.get(production.id, 0) + @api.depends('move_finished_ids') + def _compute_move_byproduct_ids(self): + for order in self: + order.move_byproduct_ids = order.move_finished_ids.filtered(lambda m: m.product_id != order.product_id) + + def _set_move_byproduct_ids(self): + self.move_finished_ids |= self.move_byproduct_ids + _sql_constraints = [ ('name_uniq', 'unique(name, company_id)', 'Reference must be unique per Company!'), ('qty_positive', 'check (product_qty > 0)', 'The quantity to produce must be positive!'), @@ -502,7 +517,7 @@ class MrpProduction(models.Model): """ Finds UoM of changed product. """ if not self.product_id: self.bom_id = False - else: + elif not self.bom_id or self.bom_id.product_tmpl_id != self.product_tmpl_id or (self.bom_id.product_id and self.bom_id.product_id != self.product_id): bom = self.env['mrp.bom']._bom_find(product=self.product_id, picking_type=self.picking_type_id, company_id=self.company_id.id, bom_type='normal') if bom: self.bom_id = bom.id @@ -563,6 +578,28 @@ class MrpProduction(models.Model): self.location_src_id = self.picking_type_id.default_location_src_id.id or location.id self.location_dest_id = self.picking_type_id.default_location_dest_id.id or location.id + @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) + def write(self, vals): res = super(MrpProduction, self).write(vals) @@ -572,7 +609,7 @@ class MrpProduction(models.Model): raise UserError(_('You cannot move a manufacturing order once it is cancelled or done.')) if production.workorder_ids and not self.env.context.get('force_date', False): raise UserError(_('You cannot move a planned manufacturing order.')) - if 'move_raw_ids' in vals and production.state != 'draft': + if ('move_raw_ids' in vals or 'move_finished_ids' in vals) and production.state != 'draft': production._autoconfirm_production() if not production.bom_id.operation_ids and vals.get('date_planned_start') and not vals.get('date_planned_finished'): new_date_planned_start = fields.Datetime.to_datetime(vals.get('date_planned_start')) @@ -595,7 +632,6 @@ class MrpProduction(models.Model): production = super(MrpProduction, self).create(values) production.move_raw_ids.write({ 'group_id': production.procurement_group_id.id, - 'reference': production.name, # set reference when MO name is different than 'New' }) # Trigger move_raw creation when importing a file if 'import_file' in self.env.context: @@ -692,7 +728,6 @@ class MrpProduction(models.Model): data = { 'sequence': bom_line.sequence if bom_line else 10, 'name': self.name, - 'reference': self.name, 'date': self.date_planned_start, 'date_expected': self.date_planned_start, 'bom_line_id': bom_line.id if bom_line else False, @@ -782,13 +817,14 @@ class MrpProduction(models.Model): ) additional_moves.write({ 'group_id': production.procurement_group_id.id, - 'reference': production.name, # set reference when MO name is different than 'New' }) additional_moves._adjust_procure_method() moves_to_confirm |= additional_moves - moves_to_confirm |= production.move_finished_ids.filtered( + additional_byproducts = production.move_finished_ids.filtered( lambda move: move.state == 'draft' and move.additional ) + moves_to_confirm |= additional_byproducts + if moves_to_confirm: moves_to_confirm._action_confirm() @@ -826,7 +862,7 @@ class MrpProduction(models.Model): }) else: action.update({ - 'name': _("MO Generated by %s" % self.name), + 'name': _("MO Generated by %s") % self.name, 'domain': [('id', 'in', mrp_production_ids)], 'view_mode': 'tree,form', }) @@ -842,6 +878,34 @@ class MrpProduction(models.Model): 'view_mode': 'tree,form', } + def action_generate_serial(self): + self.ensure_one() + self.lot_producing_id = self.env['stock.production.lot'].create({ + 'product_id': self.product_id.id, + 'company_id': self.company_id.id + }) + 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) + def action_confirm(self): self._check_company() for production in self: @@ -862,13 +926,6 @@ class MrpProduction(models.Model): production.workorder_ids._refresh_wo_lines() return True - def open_produce_product(self): - self.ensure_one() - if self.bom_id.type == 'phantom': - raise UserError(_('You cannot produce a MO with a bom kit product.')) - action = self.env.ref('mrp.act_mrp_product_produce').read()[0] - return action - def button_plan(self): """ Create work orders. And probably do stuff, like things. """ orders_to_plan = self.filtered(lambda order: order.bom_id.operation_ids and order.state == 'confirmed') @@ -1122,7 +1179,7 @@ class MrpProduction(models.Model): self.ensure_one() return True - def post_inventory(self): + def _post_inventory(self, cancel_backorder=False): for order in self: moves_not_to_do = order.move_raw_ids.filtered(lambda x: x.state == 'done') moves_to_do = order.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')) @@ -1132,23 +1189,26 @@ class MrpProduction(models.Model): # to get extra moves. moves_to_do = moves_to_do._action_done() moves_to_do = order.move_raw_ids.filtered(lambda x: x.state == 'done') - moves_not_to_do + + finish_moves = order.move_finished_ids.filtered(lambda m: m.product_id == order.product_id and m.state not in ('done', 'cancel')) + # the finish move can already be completed by the workorder. + if not finish_moves.quantity_done: + if order.product_tracking == 'serial': + uom = order.product_id.uom_id + finish_moves.quantity_done = order.product_uom_id._compute_quantity(order.qty_producing, uom, round='HALF-UP') + finish_moves.move_line_ids.product_uom_id = uom + else: + finish_moves.quantity_done = float_round(self.qty_producing - self.qty_produced, precision_rounding=order.product_uom_id.rounding, rounding_method='HALF-UP') + finish_moves.move_line_ids.lot_id = self.lot_producing_id order._cal_price(moves_to_do) + moves_to_finish = order.move_finished_ids.filtered(lambda x: x.state not in ('done', 'cancel')) - moves_to_finish = moves_to_finish._action_done() + 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') - for moveline in moves_to_finish.mapped('move_line_ids'): - if moveline.move_id.has_tracking != 'none' and moveline.product_id == order.product_id or moveline.lot_id in consume_move_lines.mapped('lot_produced_ids'): - if any([not ml.lot_produced_ids for ml in consume_move_lines]): - raise UserError(_('You can not consume without telling for which lot you consumed it')) - # Link all movelines in the consumed with same lot_produced_ids false or the correct lot_produced_ids - filtered_lines = consume_move_lines.filtered(lambda ml: moveline.lot_id in ml.lot_produced_ids) - moveline.write({'consume_line_ids': [(6, 0, [x for x in filtered_lines.ids])]}) - else: - # Link with everything - moveline.write({'consume_line_ids': [(6, 0, [x for x in consume_move_lines.ids])]}) + order.move_finished_ids.move_line_ids.consume_line_ids = [(6, 0, consume_move_lines.ids)] return True @api.model @@ -1189,13 +1249,10 @@ class MrpProduction(models.Model): production.move_raw_ids.filtered(lambda m: m.state not in ('done', 'cancel')).write({ 'raw_material_production_id': backorder_mo.id, - 'reference': backorder_mo.name, }) backorders |= backorder_mo production.name = self._get_name_backorder(production.name, production.backorder_sequence) - # update moves references - (production.move_raw_ids | production.move_finished_ids).reference = production.name for wo in backorder_mo.workorder_ids: wo.duration_expected = wo._get_duration_expected(wo.workcenter_id) @@ -1236,8 +1293,9 @@ class MrpProduction(models.Model): productions_to_backorder = self.env['mrp.production'] self.workorder_ids.button_finish() - self._post_inventory() + productions_not_to_backorder._post_inventory(cancel_backorder=False) + productions_to_backorder._post_inventory(cancel_backorder=True) backorders = productions_to_backorder._generate_backorder_productions() # Moves without quantity done are not posted => set them as done instead of canceling. In @@ -1383,7 +1441,7 @@ class MrpProduction(models.Model): 'res_model': 'mrp.unbuild', 'view_id': self.env.ref('mrp.mrp_unbuild_form_view_simplified').id, 'type': 'ir.actions.act_window', - 'context': {'default_mo_id': self.id, + 'context': {'default_mo_id': self.id, 'default_company_id': self.company_id.id, 'default_location_id': self.location_dest_id.id, 'default_location_dest_id': self.location_src_id.id, @@ -1398,3 +1456,84 @@ class MrpProduction(models.Model): def _get_quantity_to_backorder(self): self.ensure_one() return max(self.product_qty - self.qty_produced, 0) + + def _check_sn_uniqueness(self): + """ Alert the user if the serial number as already been consumed/produced """ + if self.product_tracking == 'serial' and self.lot_producing_id: + sml = self.env['stock.move.line'].search_count([ + ('lot_id', '=', self.lot_producing_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) + + for move in self.move_finished_ids: + if move.has_tracking != 'serial' or move.product_id == self.product_id: + continue + for move_line in move.move_line_ids: + domain = [ + ('lot_id', '=', move_line.lot_id.id), + ('qty_done', '=', 1), + ('state', '=', 'done') + ] + message = _('The serial number %s used for byproduct %s has already been produced') % (move_line.lot_id.name, move_line.product_id.name) + co_prod_move_lines = self.move_finished_ids.move_line_ids.filtered(lambda ml: ml.product_id != self.product_id) + domain_unbuild = domain + [ + ('production_id', '=', False), + ('location_dest_id.usage', '=', 'production') + ] + + # Check presence of same sn in previous productions + duplicates = self.env['stock.move.line'].search_count(domain + [ + ('location_id.usage', '=', 'production') + ]) + if duplicates: + # Maybe some move lines have been compensated by unbuild + duplicates_unbuild = self.env['stock.move.line'].search_count(domain_unbuild) + if not (duplicates_unbuild and duplicates - duplicates_unbuild == 0): + raise UserError(message) + # Check presence of same sn in current production + duplicates = co_prod_move_lines.filtered(lambda ml: ml.qty_done and ml.lot_id == move_line.lot_id) - move_line + if duplicates: + raise UserError(message) + + for move in self.move_raw_ids: + if move.has_tracking != 'serial': + continue + for move_line in move.move_line_ids: + if float_is_zero(move_line.qty_done, precision_rounding=move_line.product_uom_id.rounding): + continue + domain = [ + ('lot_id', '=', move_line.lot_id.id), + ('qty_done', '=', 1), + ('state', '=', 'done') + ] + message = _('The serial number %s used for component %s has already been consumed') % (move_line.lot_id.name, move_line.product_id.name) + co_prod_move_lines = self.move_raw_ids.move_line_ids + domain_unbuild = domain + [ + ('production_id', '=', False), + ('location_id.usage', '=', 'production') + ] + + # Check presence of same sn in previous productions + duplicates = self.env['stock.move.line'].search_count(domain + [ + ('location_dest_id.usage', '=', 'production') + ]) + if duplicates: + # Maybe some move lines have been compensated by unbuild + duplicates_unbuild = self.env['stock.move.line'].search_count(domain_unbuild) + if not (duplicates_unbuild and duplicates - duplicates_unbuild == 0): + raise UserError(message) + # Check presence of same sn in current production + duplicates = co_prod_move_lines.filtered(lambda ml: ml.qty_done and ml.lot_id == move_line.lot_id) - move_line + if duplicates: + raise UserError(message) + + def _strict_consumption_check(self): + if self.consumption == 'strict': + for move in self.move_raw_ids: + rounding = move.product_uom.rounding + if float_compare(move.quantity_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) diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py index 40856e8c141..6d345e75763 100644 --- a/addons/mrp/models/mrp_unbuild.py +++ b/addons/mrp/models/mrp_unbuild.py @@ -184,7 +184,7 @@ class MrpUnbuild(models.Model): needed_quantity = move.product_qty moves_lines = original_move.mapped('move_line_ids') if move in produce_moves and self.lot_id: - moves_lines = moves_lines.filtered(lambda ml: self.lot_id in ml.lot_produced_ids) + moves_lines = moves_lines.filtered(lambda ml: self.lot_id in ml.produce_line_ids.lot_id) # FIXME sle: double check with arm for move_line in moves_lines: # Iterate over all move_lines until we unbuilded the correct quantity. taken_quantity = min(needed_quantity, move_line.qty_done) diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 8702a292155..cf0ec783e6e 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_is_zero +from odoo.tools import float_compare, float_round, float_repr, float_is_zero class StockMoveLine(models.Model): @@ -11,7 +11,6 @@ class StockMoveLine(models.Model): workorder_id = fields.Many2one('mrp.workorder', 'Work Order', check_company=True) production_id = fields.Many2one('mrp.production', 'Production Order', check_company=True) - lot_produced_ids = fields.Many2many('stock.production.lot', string='Finished Lot/Serial Number', check_company=True) done_move = fields.Boolean('Move Done', related='move_id.is_done', readonly=False, store=True) # TDE FIXME: naming @api.model_create_multi @@ -23,8 +22,10 @@ class StockMoveLine(models.Model): # traceability report if line.move_id.raw_material_production_id and line.state == 'done': mo = line.move_id.raw_material_production_id - if line.lot_produced_ids: - produced_move_lines = mo.move_finished_ids.move_line_ids.filtered(lambda sml: sml.lot_id in line.lot_produced_ids) + finished_lots = mo.lot_producing_id + finished_lots |= mo.move_finished_ids.filtered(lambda m: m.product_id != mo.product_id).move_line_ids.lot_id + if finished_lots: + produced_move_lines = mo.move_finished_ids.move_line_ids.filtered(lambda sml: sml.lot_id in finished_lots) line.produce_line_ids = [(6, 0, produced_move_lines.ids)] else: produced_move_lines = mo.move_finished_ids.move_line_ids @@ -45,7 +46,7 @@ class StockMoveLine(models.Model): def _reservation_is_updatable(self, quantity, reserved_quant): self.ensure_one() - if self.lot_produced_ids: + if self.produce_line_ids.lot_id: ml_remaining_qty = self.qty_done - self.product_uom_qty ml_remaining_qty = self.product_uom_id._compute_quantity(ml_remaining_qty, self.product_id.uom_id, rounding_method="HALF-UP") if float_compare(ml_remaining_qty, quantity, precision_rounding=self.product_id.uom_id.rounding) < 0: @@ -54,10 +55,6 @@ class StockMoveLine(models.Model): def write(self, vals): for move_line in self: - if move_line.move_id.production_id and 'lot_id' in vals: - move_line.production_id.move_raw_ids.mapped('move_line_ids')\ - .filtered(lambda r: not r.done_move and move_line.lot_id in r.lot_produced_ids)\ - .write({'lot_produced_ids': [(4, vals['lot_id'])]}) production = move_line.move_id.production_id or move_line.move_id.raw_material_production_id if production and move_line.state == 'done' and any(field in vals for field in ('lot_id', 'location_id', 'qty_done')): move_line._log_message(production, move_line, 'mrp.track_production_move_template', vals) @@ -95,6 +92,7 @@ class StockMove(models.Model): needs_lots = fields.Boolean('Tracking', compute='_compute_needs_lots') order_finished_lot_ids = fields.Many2many('stock.production.lot', compute='_compute_order_finished_lot_ids') finished_lots_exist = fields.Boolean('Finished Lots Exist', compute='_compute_order_finished_lot_ids') + should_consume_qty = fields.Float('Quantity To Consume', compute='_compute_should_consume_qty') def _unreserve_initial_demand(self, new_move): # If you were already putting stock.move.lots on the next one in the work order, transfer those to the new move @@ -153,22 +151,55 @@ class StockMove(models.Model): for move in self: move.is_done = (move.state in ('done', 'cancel')) - @api.depends('product_uom_qty') + @api.depends('product_uom_qty', 'raw_material_production_id', 'raw_material_production_id.product_qty', 'raw_material_production_id.qty_produced') def _compute_unit_factor(self): for move in self: mo = move.raw_material_production_id or move.production_id if mo: - move.unit_factor = (move.product_uom_qty - move.quantity_done) / ((mo.product_qty - mo.qty_produced) or 1) + move.unit_factor = move.product_uom_qty / ((mo.product_qty - mo.qty_produced) or 1) else: move.unit_factor = 1.0 + @api.depends('raw_material_production_id', 'raw_material_production_id.name', 'production_id', 'production_id.name') + def _compute_reference(self): + moves_with_reference = self.env['stock.move'] + for move in self: + if move.raw_material_production_id and move.raw_material_production_id.name: + move.reference = move.raw_material_production_id.name + moves_with_reference |= move + if move.production_id and move.production_id.name: + move.reference = move.production_id.name + moves_with_reference |= move + super(StockMove, self - moves_with_reference)._compute_reference() + + @api.depends('raw_material_production_id.qty_producing', 'product_uom_qty') + def _compute_should_consume_qty(self): + for move in self: + mo = move.raw_material_production_id + if not mo: + move.qty_summary = 0 + continue + move.should_consume_qty = mo.product_uom_id._compute_quantity((mo.qty_producing - mo.qty_produced) * move.unit_factor, mo.product_uom_id, rounding_method='HALF-UP') + + @api.onchange('product_uom_qty') + def _onchange_product_uom_qty(self): + 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): defaults = super(StockMove, self).default_get(fields_list) - if self.env.context.get('default_raw_material_production_id'): - production_id = self.env['mrp.production'].browse(self.env.context['default_raw_material_production_id']) - if production_id.state in ('confirmed', 'done'): - if production_id.state == 'confirmed': + if self.env.context.get('default_raw_material_production_id') or self.env.context.get('default_production_id'): + production_id = self.env['mrp.production'].browse(self.env.context.get('default_raw_material_production_id') or self.env.context.get('default_production_id')) + if production_id.state not in ('draft', 'cancel'): + if production_id.state != 'done': defaults['state'] = 'draft' else: defaults['state'] = 'done' @@ -225,6 +256,17 @@ class StockMove(models.Model): moves_to_return |= phantom_moves.action_explode() return moves_to_return + def action_show_details(self): + self.ensure_one() + action = super().action_show_details() + if self.raw_material_production_id: + action['views'] = [(self.env.ref('mrp.view_stock_move_operations_raw').id, 'form')] + action['context']['show_destination_location'] = False + elif self.production_id: + action['views'] = [(self.env.ref('mrp.view_stock_move_operations_finished').id, 'form')] + action['context']['show_source_location'] = False + return action + def _action_cancel(self): res = super(StockMove, self)._action_cancel() for production in self.mapped('raw_material_production_id'): @@ -328,3 +370,25 @@ class StockMove(models.Model): return min(qty_ratios) // 1 else: return 0.0 + + @api.depends('raw_material_production_id') + def _compute_is_quantity_done_editable(self): + return super()._compute_is_quantity_done_editable() + + def _show_details_in_draft(self): + self.ensure_one() + if self.raw_material_production_id and self.state == 'draft': + return True + else: + return super()._show_details_in_draft() + + def _update_quantity_done(self, mo): + self.ensure_one() + ml_values = {} + 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) + 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 b5f3ba1c95a..6fd221fe93b 100644 --- a/addons/mrp/security/ir.model.access.csv +++ b/addons/mrp/security/ir.model.access.csv @@ -57,8 +57,6 @@ access_mrp_unbuild,mrp.unbuild,model_mrp_unbuild,group_mrp_user,1,1,1,1 access_mrp_unbuild_manager,mrp.unbuild manager,model_mrp_unbuild,group_mrp_manager,1,1,1,1 access_mrp_document_mrp_manager,mrp.document group_user,model_mrp_document,group_mrp_manager,1,1,1,1 access_mrp_document_mrp_user,mrp.document group_user,model_mrp_document,group_mrp_user,1,1,1,1 -access_mrp_product_produce,access.mrp.product.produce,model_mrp_product_produce,mrp.group_mrp_user,1,1,1,0 -access_mrp_product_produce_line,access.mrp.product.produce.line,model_mrp_product_produce_line,mrp.group_mrp_user,1,1,1,1 access_change_production_qty,access.change.production.qty,model_change_production_qty,mrp.group_mrp_user,1,1,1,0 access_stock_warn_insufficient_qty_unbuild,access.stock.warn.insufficient.qty.unbuild,model_stock_warn_insufficient_qty_unbuild,mrp.group_mrp_user,1,1,1,0 access_mrp_production_backorder,access.mrp.production.backorder,model_mrp_production_backorder,mrp.group_mrp_user,1,1,1,0 diff --git a/addons/mrp/static/src/js/mrp_should_consume.js b/addons/mrp/static/src/js/mrp_should_consume.js new file mode 100644 index 00000000000..50b4f3f893b --- /dev/null +++ b/addons/mrp/static/src/js/mrp_should_consume.js @@ -0,0 +1,81 @@ +odoo.define('mrp.should_consume', function (require) { +"use strict"; + +var BasicFields = require('web.basic_fields'); +var FieldFloat = BasicFields.FieldFloat; +var fieldRegistry = require('web.field_registry'); +var field_utils = require('web.field_utils'); + +/** + * This widget is used to display alongside the total quantity to consume of a production order, + * the exact quantity that the worker should consume depending on the BoM. Ex: + * 2 components to make 1 finished product. + * The production order is created to make 5 finished product and the quantity producing is set to 3. + * The widget will be '3.000 / 5.000'. + */ +var MrpShouldConsume = FieldFloat.extend({ + /** + * @override + */ + init: function (parent, name, params) { + this._super.apply(this, arguments); + this.displayShouldConsume = !['done', 'draft', 'cancel'].includes(params.data.state); + let options = {'digits': [false, 3]}; + this.should_consume_qty = field_utils.format.float(params.data.should_consume_qty, false, options); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Object} [el] jquery input element that will be surrounded by a new span + * @param {float} [value] quantity to display before the input `el` + * @return {jquery element} + */ + _addShouldConsume: function (el, value) { + var $to_consume_container = $(''); + $to_consume_container.text(value + ' / '); + $to_consume_container.append(el); + return $to_consume_container + }, + + /** + * @private + * @override + */ + _renderEdit: function () { + // Keep a reference to the input so $el can become something else + // without losing track of the actual input. + var def = this._super.apply(this, arguments); + if (this.displayShouldConsume) { + var $container = this._addShouldConsume(this.$el, this.should_consume_qty); + $container.addClass('o_row'); + this.$el = $container; + }; + return def; + }, + /** + * Resets the content to the formated value in readonly mode. + * + * @override + * @private + */ + _renderReadonly: function () { + var def = this._super.apply(this, arguments); + if (this.displayShouldConsume) { + var $container = this._addShouldConsume(this.$el, this.should_consume_qty); + this.$el = $container; + }; + return def; + }, +}); + +fieldRegistry.add('mrp_should_consume', MrpShouldConsume); + +return { + MrpShouldConsume: MrpShouldConsume, +}; + +}); diff --git a/addons/mrp/static/src/scss/mrp_fields.scss b/addons/mrp/static/src/scss/mrp_fields.scss index 606e32f4d94..da959565fc8 100644 --- a/addons/mrp/static/src/scss/mrp_fields.scss +++ b/addons/mrp/static/src/scss/mrp_fields.scss @@ -5,4 +5,7 @@ height: 30rem; border: none; } -} \ No newline at end of file +} +.o_should_consume{ + padding-left: 0.3em; +} diff --git a/addons/mrp/tests/common.py b/addons/mrp/tests/common.py index 6e70957237d..832a7f2e442 100644 --- a/addons/mrp/tests/common.py +++ b/addons/mrp/tests/common.py @@ -7,7 +7,7 @@ from odoo.addons.stock.tests import common2 class TestMrpCommon(common2.TestStockCommon): @classmethod - def generate_mo(self, tracking_final='none', tracking_base_1='none', tracking_base_2='none', qty_final=5, qty_base_1=4, qty_base_2=1, picking_type_id=False): + def generate_mo(self, tracking_final='none', tracking_base_1='none', tracking_base_2='none', qty_final=5, qty_base_1=4, qty_base_2=1, picking_type_id=False, consumption=False): """ This function generate a manufacturing order with one final product and two consumed product. Arguments allows to choose the tracking/qty for each different products. It returns the @@ -34,6 +34,7 @@ class TestMrpCommon(common2.TestStockCommon): 'product_uom_id': self.uom_unit.id, 'product_qty': 1.0, 'type': 'normal', + 'consumption': consumption if consumption else 'flexible', 'bom_line_ids': [ (0, 0, {'product_id': product_to_use_2.id, 'product_qty': qty_base_2}), (0, 0, {'product_id': product_to_use_1.id, 'product_qty': qty_base_1}) diff --git a/addons/mrp/tests/test_backorder.py b/addons/mrp/tests/test_backorder.py index 7023c98627b..3dedc1528c0 100644 --- a/addons/mrp/tests/test_backorder.py +++ b/addons/mrp/tests/test_backorder.py @@ -27,10 +27,9 @@ class TestMrpProductionBackorder(TestMrpCommon): """ mo = self.generate_mo(qty_final=4)[0] - produce_form = Form(self.env['mrp.product.produce'].with_context(active_id=mo.id)) - produce_form.qty_producing = 4 - produce = produce_form.save() - produce.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 4 + mo = mo_form.save() # No backorder is proposed self.assertTrue(mo.button_mark_done()) @@ -61,10 +60,9 @@ class TestMrpProductionBackorder(TestMrpCommon): self.assertEqual(production.state, 'confirmed') self.assertEqual(production.reserve_visible, False) - produce_form = Form(self.env['mrp.product.produce'].with_context(active_id=production.id)) - produce_form.qty_producing = 1 - produce = produce_form.save() - produce.do_produce() + mo_form = Form(production) + mo_form.qty_producing = 1 + production = mo_form.save() action = production.button_mark_done() backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context'])) @@ -103,10 +101,9 @@ class TestMrpProductionBackorder(TestMrpCommon): self.assertEqual(set(pbm_move.mapped("product_id")), {product_to_use_1, product_to_use_2}) self.assertFalse(pbm_move.move_orig_ids) - produce_form = Form(self.env['mrp.product.produce'].with_context(active_id=production.id)) - produce_form.qty_producing = 1 - produce = produce_form.save() - produce.do_produce() + mo_form = Form(production) + mo_form.qty_producing = 1 + production = mo_form.save() self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_1.id).mapped("product_qty")), 16) self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_2.id).mapped("product_qty")), 4) @@ -154,10 +151,9 @@ class TestMrpProductionBackorder(TestMrpCommon): self.assertEqual(sam_move.product_id.id, product_to_build.id) self.assertEqual(sum(sam_move.mapped("product_qty")), 4) - produce_form = Form(self.env['mrp.product.produce'].with_context(active_id=production.id)) - produce_form.qty_producing = 1 - produce = produce_form.save() - produce.do_produce() + mo_form = Form(production) + mo_form.qty_producing = 1 + production = mo_form.save() action = production.button_mark_done() backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context'])) diff --git a/addons/mrp/tests/test_byproduct.py b/addons/mrp/tests/test_byproduct.py index 58759c3e0e3..e07bdea83ae 100644 --- a/addons/mrp/tests/test_byproduct.py +++ b/addons/mrp/tests/test_byproduct.py @@ -58,9 +58,6 @@ class TestMrpByProduct(common.TransactionCase): mnf_product_a = mnf_product_a_form.save() mnf_product_a.action_confirm() - # I compute the data of production order - context = {"active_model": "mrp.production", "active_ids": [mnf_product_a.id], "active_id": mnf_product_a.id} - # I confirm the production order. self.assertEqual(mnf_product_a.state, 'confirmed', 'Production order should be in state confirmed') @@ -71,12 +68,11 @@ class TestMrpByProduct(common.TransactionCase): # I consume and produce the production of products. # I create record for selecting mode and quantity of products to produce. - produce_form = Form(self.env['mrp.product.produce'].with_context(context)) - produce_form.qty_producing = 2.00 - product_consume = produce_form.save() + mo_form = Form(mnf_product_a) + mo_form.qty_producing = 2.00 + mnf_product_a = mo_form.save() # I finish the production order. self.assertEqual(len(mnf_product_a.move_raw_ids), 1, "Wrong consume move on production order.") - product_consume.do_produce() consume_move_c = mnf_product_a.move_raw_ids by_product_move = mnf_product_a.move_finished_ids.filtered(lambda x: x.product_id.id == self.product_b.id) # Check sub production produced quantity... diff --git a/addons/mrp/tests/test_cancel_mo.py b/addons/mrp/tests/test_cancel_mo.py index 3a132ba02c4..97726da7d60 100644 --- a/addons/mrp/tests/test_cancel_mo.py +++ b/addons/mrp/tests/test_cancel_mo.py @@ -33,10 +33,9 @@ class TestMrpCancelMO(TestMrpCommon): # Create MO manufacturing_order = self.generate_mo()[0] # Produce some quantity - produce_form = Form(self.env['mrp.product.produce'].with_context(active_id=manufacturing_order.id)) - produce_form.qty_producing = 2 - produce = produce_form.save() - produce.do_produce() + mo_form = Form(manufacturing_order) + mo_form.qty_producing = 2 + manufacturing_order = mo_form.save() # Cancel it manufacturing_order.action_cancel() # Check it's cancelled @@ -53,12 +52,11 @@ class TestMrpCancelMO(TestMrpCommon): after post inventory. """ # Create MO - manufacturing_order = self.generate_mo()[0] + manufacturing_order = self.generate_mo(consumption='strict')[0] # Produce some quantity (not all to avoid to done the MO when post inventory) - produce_form = Form(self.env['mrp.product.produce'].with_context(active_id=manufacturing_order.id)) - produce_form.qty_producing = 2 - produce = produce_form.save() - produce.do_produce() + mo_form = Form(manufacturing_order) + mo_form.qty_producing = 2 + manufacturing_order = mo_form.save() # Post Inventory manufacturing_order.post_inventory() # Cancel the MO @@ -84,7 +82,7 @@ class TestMrpCancelMO(TestMrpCommon): done and the WO must be cancelled. """ # Create MO - mo_data = self.generate_mo() + mo_data = self.generate_mo(consumption='strict') manufacturing_order = mo_data[0] bom = mo_data[1] bom.write({ @@ -92,6 +90,15 @@ class TestMrpCancelMO(TestMrpCommon): (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() + mo_form = Form(manufacturing_order) + mo_form.qty_producing = 2 + manufacturing_order = mo_form.save() manufacturing_order.button_plan() workorder = manufacturing_order.workorder_ids @@ -136,10 +143,9 @@ class TestMrpCancelMO(TestMrpCommon): # it (cannot be deleted) manufacturing_order = self.generate_mo()[0] # Produce some quantity (not all to avoid to done the MO when post inventory) - produce_form = Form(self.env['mrp.product.produce'].with_context(active_id=manufacturing_order.id)) - produce_form.qty_producing = 2 - produce = produce_form.save() - produce.do_produce() + mo_form = Form(manufacturing_order) + mo_form.qty_producing = 2 + manufacturing_order = mo_form.save() # Post Inventory manufacturing_order.post_inventory() # Unlink the MO must raises an UserError since it cannot be really cancelled diff --git a/addons/mrp/tests/test_multicompany.py b/addons/mrp/tests/test_multicompany.py index b6593cfb517..6b0c3fd3a47 100644 --- a/addons/mrp/tests/test_multicompany.py +++ b/addons/mrp/tests/test_multicompany.py @@ -133,15 +133,10 @@ class TestMrpMulticompany(common.TransactionCase): }) mo_form = Form(self.env['mrp.production'].with_user(self.user_a)) mo_form.product_id = product + mo_form.lot_producing_id = lot_b mo = mo_form.save() - mo.with_user(self.user_b).action_confirm() - produce_form = Form(self.env['mrp.product.produce'].with_user(self.user_b).with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.finished_lot_id = lot_b with self.assertRaises(UserError): - produce_form.save() + mo.with_user(self.user_b).action_confirm() def test_product_produce_2(self): """Check that using a component lot of company b in the produce wizard of a production @@ -168,14 +163,17 @@ class TestMrpMulticompany(common.TransactionCase): mo_form.product_id = product mo = mo_form.save() mo.with_user(self.user_b).action_confirm() - produce_form = Form(self.env['mrp.product.produce'].with_user(self.user_b).with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - with produce_form.raw_workorder_line_ids.edit(0) as line: - line.lot_id = lot_b + mo_form = Form(mo) + mo_form.qty_producing = 1 + mo = mo_form.save() + details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.lot_id = lot_b + ml.qty_done = 1 + details_operation_form.save() with self.assertRaises(UserError): - produce_form.save() + mo.button_mark_done() + def test_partner_1(self): """ On a product without company, as a user of Company B, check it is not possible to use a diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index 8e7467b5354..3cca0512e0b 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -8,7 +8,6 @@ from odoo.fields import Datetime as Dt from odoo.exceptions import UserError from odoo.addons.mrp.tests.common import TestMrpCommon - class TestMrpOrder(TestMrpCommon): def test_access_rights_manager(self): @@ -95,13 +94,9 @@ class TestMrpOrder(TestMrpCommon): self.assertEqual(first_move.product_qty, test_quantity / self.bom_1.product_qty * self.product_4.uom_id.factor_inv * 4) # produce product - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': man_order.id, - 'active_ids': [man_order.id], - })) - produce_form.qty_producing = 1.0 - produce_wizard = produce_form.save() - produce_wizard.do_produce() + mo_form = Form(man_order) + mo_form.qty_producing = 1.0 + man_order = mo_form.save() action = man_order.button_mark_done() self.assertEqual(man_order.state, 'progress', "Production order should be open a backorder wizard, then not done yet.") @@ -160,15 +155,12 @@ class TestMrpOrder(TestMrpCommon): mo, bom, p_final, p1, p2 = self.generate_mo(qty_base_1=10, qty_final=1, qty_base_2=1) bom.consumption = 'flexible' mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - for i in range(len(produce_form.raw_workorder_line_ids)): - with produce_form.raw_workorder_line_ids.edit(i) as line: - line.qty_done += 1 - product_produce = produce_form.save() - product_produce.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 1 + for i in range(len(mo_form.move_raw_ids)): + with mo_form.move_raw_ids.edit(i) as move: + move.quantity_done += 1 + mo = mo_form.save() self.assertEqual(len(mo.move_raw_ids), 2) self.assertEqual(len(mo.move_raw_ids.mapped('move_line_ids')), 2) self.assertEqual(mo.move_raw_ids[0].move_line_ids.mapped('qty_done'), [2]) @@ -248,11 +240,9 @@ class TestMrpOrder(TestMrpCommon): self.assertEqual(mo_custom_laptop.reservation_state, 'assigned') # produce one item, call `post_inventory` - context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id} - produce_form = Form(self.env['mrp.product.produce'].with_context(context)) - produce_form.qty_producing = 1.00 - custom_laptop_produce = produce_form.save() - custom_laptop_produce.do_produce() + mo_form = Form(mo_custom_laptop) + mo_form.qty_producing = 1.00 + mo_custom_laptop = mo_form.save() mo_custom_laptop.post_inventory() # check the consumed quants of the produced quant @@ -264,11 +254,9 @@ class TestMrpOrder(TestMrpCommon): self.assertEqual(second_move.quantity_done, 0, "There is still one product to pruduce") # produce the second item, call `post_inventory` - context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id} - produce_form = Form(self.env['mrp.product.produce'].with_context(context)) - produce_form.qty_producing = 1.00 - custom_laptop_produce = produce_form.save() - custom_laptop_produce.do_produce() + mo_form = Form(mo_custom_laptop) + mo_form.qty_producing = 2.00 + mo_custom_laptop = mo_form.save() mo_custom_laptop.post_inventory() self.assertEqual(second_move.quantity_done, 1, "Order produce the second product") quant_after2 = custom_laptop.qty_available @@ -301,14 +289,15 @@ class TestMrpOrder(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_wizard = produce_form.save() - produce_wizard.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 1 + mo = mo_form.save() - mo.move_finished_ids.move_line_ids.qty_done -= 1 + details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = lot_1 + ml.qty_done = 20 + details_operation_form.save() update_quantity_wizard = self.env['change.production.qty'].create({ 'mo_id': mo.id, 'product_qty': 4, @@ -333,67 +322,25 @@ class TestMrpOrder(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 2 - produce_wizard = produce_form.save() - produce_wizard.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 2 + mo = mo_form.save() + + mo._post_inventory() - mo.post_inventory() update_quantity_wizard = self.env['change.production.qty'].create({ 'mo_id': mo.id, 'product_qty': 5, }) update_quantity_wizard.change_prod_qty() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_wizard = produce_form.save() - produce_wizard.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 5 + mo = mo_form.save() mo.button_mark_done() self.assertEqual(sum(mo.move_raw_ids.filtered(lambda m: m.product_id == p1).mapped('quantity_done')), 20) self.assertEqual(sum(mo.move_finished_ids.mapped('quantity_done')), 5) - def test_update_quantity_3(self): - """ Build 1 final products then update the Manufacturing - order quantity. Check the remaining quantity to produce - take care of the first quantity produced.""" - self.stock_location = self.env.ref('stock.stock_location_stock') - mo, bom, p_final, p1, p2 = self.generate_mo(qty_final=2) - self.assertEqual(len(mo), 1, 'MO should have been created') - - self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 20) - self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) - mo.action_assign() - - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 1 - produce_wizard = produce_form.save() - produce_wizard.do_produce() - - update_quantity_wizard = self.env['change.production.qty'].create({ - 'mo_id': mo.id, - 'product_qty': 3, - }) - update_quantity_wizard.change_prod_qty() - - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_wizard = produce_form.save() - produce_wizard.do_produce() - mo.button_mark_done() - self.assertEqual(sum(mo.move_raw_ids.filtered(lambda m: m.product_id == p1).mapped('quantity_done')), 12) - self.assertEqual(sum(mo.move_finished_ids.mapped('quantity_done')), 3) - def test_rounding(self): """ Checks we round up when bringing goods to produce and round half-up when producing. This implementation allows to implement an efficiency notion (see rev 347f140fe63612ee05e). @@ -422,13 +369,9 @@ class TestMrpOrder(TestMrpCommon): self.assertEqual(production.move_raw_ids[1].product_qty, 84, 'The quantity should be rounded up') # produce product - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': production.id, - 'active_ids': [production.id], - })) - produce_form.qty_producing = 8 - produce_wizard = produce_form.save() - produce_wizard.do_produce() + mo_form = Form(production) + mo_form.qty_producing = 8 + production = mo_form.save() self.assertEqual(production.move_raw_ids[0].quantity_done, 16, 'Should use half-up rounding when producing') self.assertEqual(production.move_raw_ids[1].quantity_done, 34, 'Should use half-up rounding when producing') @@ -443,27 +386,21 @@ class TestMrpOrder(TestMrpCommon): mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) # change the quantity done in one line - produce_form.raw_workorder_line_ids._records[0]['qty_done'] = 1 + details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.qty_done = 1 + details_operation_form.save() # change the quantity producing - produce_form.qty_producing = 3 + mo_form = Form(mo) + mo_form.qty_producing = 3 # check than all quantities are update correctly - line1 = produce_form.raw_workorder_line_ids._records[0] - line2 = produce_form.raw_workorder_line_ids._records[1] - self.assertEqual(line1['qty_to_consume'], 3, "Wrong quantity to consume") - self.assertEqual(line1['qty_done'], 3, "Wrong quantity done") - self.assertEqual(line2['qty_to_consume'], 12, "Wrong quantity to consume") - self.assertEqual(line2['qty_done'], 12, "Wrong quantity done") - - product_produce = produce_form.save() - self.assertEqual(len(product_produce.raw_workorder_line_ids), 2, 'You should have produce lines even the consumed products are not tracked.') - product_produce.do_produce() + self.assertEqual(mo_form.move_raw_ids._records[0]['product_uom_qty'], 5, "Wrong quantity to consume") + self.assertEqual(mo_form.move_raw_ids._records[0]['quantity_done'], 3, "Wrong quantity done") + self.assertEqual(mo_form.move_raw_ids._records[1]['product_uom_qty'], 20, "Wrong quantity to consume") + self.assertEqual(mo_form.move_raw_ids._records[1]['quantity_done'], 12, "Wrong quantity done") def test_product_produce_2(self): """ Checks that, for a BOM where one of the components is tracked by serial number and the @@ -490,22 +427,19 @@ class TestMrpOrder(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - self.assertEqual(len(produce_form.raw_workorder_line_ids), 3, 'You should have 3 produce lines. One for each serial to consume and for the untracked product.') - produce_form.qty_producing = 1 + self.assertEqual(len(mo.move_raw_ids.move_line_ids), 3, 'You should have 3 stock move lines. One for each serial to consume and for the untracked product.') + mo_form = Form(mo) + mo_form.qty_producing = 1 + mo = mo_form.save() # get the proposed lot - consumed_lots = self.env['stock.production.lot'] - for workorder_line in produce_form.raw_workorder_line_ids._records: - if workorder_line['product_id'] == p1.id: - consumed_lots |= self.env['stock.production.lot'].browse(workorder_line['lot_id']) - consumed_lots.ensure_one() - product_produce = produce_form.save() - product_produce.do_produce() + details_operation_form = Form(mo.move_raw_ids.filtered(lambda move: move.product_id == p1), view=self.env.ref('stock.view_stock_move_operations')) + self.assertEqual(len(details_operation_form.move_line_ids), 2) + with details_operation_form.move_line_ids.edit(0) as ml: + consumed_lots = ml.lot_id + ml.qty_done = 1 + details_operation_form.save() remaining_lot = (lot_p1_1 | lot_p1_2) - consumed_lots remaining_lot.ensure_one() @@ -513,22 +447,22 @@ class TestMrpOrder(TestMrpCommon): backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context'])) backorder.save().action_backorder() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - product_produce = produce_form.save() - self.assertEqual(len(product_produce.raw_workorder_line_ids), 2, 'You should have 2 produce lines left.') - for line in product_produce.raw_workorder_line_ids.filtered(lambda x: x.lot_id): - self.assertEqual(line.lot_id, remaining_lot, 'Wrong lot proposed.') + # Check MO backorder + mo_backorder = mo.procurement_group_id.mrp_production_ids[-1] + + mo_form = Form(mo_backorder) + mo_form.qty_producing = 1 + mo_backorder = mo_form.save() + details_operation_form = Form(mo_backorder.move_raw_ids.filtered(lambda move: move.product_id == p1), view=self.env.ref('stock.view_stock_move_operations')) + self.assertEqual(len(details_operation_form.move_line_ids), 1) + with details_operation_form.move_line_ids.edit(0) as ml: + self.assertEqual(ml.lot_id, remaining_lot) def test_product_produce_3(self): """ Checks that, for a BOM where one of the components is tracked by lot and the other is not tracked, when creating a manufacturing order for 1 finished product and reserving, the - produce wizard proposes the corrects lines. Then, checks the generated move lines when over - consuming. + reserved lines are displayed. Then, over-consume by creating new line. """ - # FIXME: some asserts on the quants after overproducing would be nice self.stock_location = self.env.ref('stock.stock_location_stock') self.stock_shelf_1 = self.stock_location_components @@ -560,22 +494,31 @@ class TestMrpOrder(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 1.0 - for i in range(len(produce_form.raw_workorder_line_ids)): - with produce_form.raw_workorder_line_ids.edit(i) as line: - line.qty_done += 1 - product_produce = produce_form.save() - product_produce.finished_lot_id = final_product_lot.id - # product 1 lot 1 shelf1 - # product 1 lot 1 shelf2 - # product 1 lot 2 - self.assertEqual(len(product_produce.raw_workorder_line_ids), 4, 'You should have 4 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2 and for product which have tracking None') + mo_form = Form(mo) + mo_form.qty_producing = 1.0 + mo_form.lot_producing_id = final_product_lot + mo = mo_form.save() + # p2 + details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as line: + line.qty_done = line.product_uom_qty + with details_operation_form.move_line_ids.new() as line: + line.qty_done = 1 + details_operation_form.save() - product_produce.do_produce() + # p1 + details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations')) + for i in range(len(details_operation_form.move_line_ids)): + # reservation in shelf1: 3 lot1, shelf2: 3 lot1, stock: 4 lot2 + with details_operation_form.move_line_ids.edit(i) as line: + line.qty_done = line.product_uom_qty + with details_operation_form.move_line_ids.new() as line: + line.qty_done = 2 + line.lot_id = first_lot_for_p1 + with details_operation_form.move_line_ids.new() as line: + line.qty_done = 1 + line.lot_id = second_lot_for_p1 + details_operation_form.save() move_1 = mo.move_raw_ids.filtered(lambda m: m.product_id == p1) # qty_done/product_uom_qty lot @@ -616,18 +559,15 @@ class TestMrpOrder(TestMrpCommon): ml_p1[0].qty_done = 1.0 # Produce baby! - product_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - product_produce = product_form.save() - product_produce.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 1 + mo = mo_form.save() m_p1 = mo.move_raw_ids.filtered(lambda x: x.product_id == p1) ml_p1 = m_p1.mapped('move_line_ids') - self.assertEqual(len(ml_p1), 3) - self.assertEqual(sorted(ml_p1.mapped('qty_done')), [1.0, 2.0, 3.0], 'Quantity done should be 1.0, 2.0 or 3.0') - self.assertEqual(m_p1.quantity_done, 6.0, 'Total qty done should be 6.0') + self.assertEqual(len(ml_p1), 2) + self.assertEqual(sorted(ml_p1.mapped('qty_done')), [2.0, 3.0], 'Quantity done should be 1.0, 2.0 or 3.0') + self.assertEqual(m_p1.quantity_done, 5.0, 'Total qty done should be 6.0') self.assertEqual(sum(ml_p1.mapped('product_uom_qty')), 5.0, 'Total qty reserved should be 5.0') mo.button_mark_done() @@ -636,7 +576,7 @@ class TestMrpOrder(TestMrpCommon): def test_product_produce_6(self): """ Plan 5 finished products, reserve and produce 3. Post the current production. Simulate an unlock and edit and, on the opened moves, set the consumed quantity - to 3. Now, try to update the quantity to produce to 3. It should fail since there + to 3. Now, try to update the quantity to mo2 to 3. It should fail since there are consumed quantities. Unlock and edit, remove the consumed quantities and update the quantity to produce to 3.""" self.stock_location = self.env.ref('stock.stock_location_stock') @@ -648,13 +588,9 @@ class TestMrpOrder(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 3 - produce_wizard = produce_form.save() - produce_wizard.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 3 + mo = mo_form.save() mo.post_inventory() self.assertEqual(len(mo.move_raw_ids), 4) @@ -675,127 +611,11 @@ class TestMrpOrder(TestMrpCommon): self.assertTrue(all(s == 'done' for s in mo.move_raw_ids.mapped('state'))) self.assertEqual(sum(mo.move_raw_ids.mapped('move_line_ids.product_uom_qty')), 0) - def test_product_produce_7(self): - """ Add components in 2 different sub location. Do not reserve the MO - and checks that the move line created takes stock from location that - contains needed raw materials. - """ - mo, bom, p_final, p1, p2 = self.generate_mo(qty_final=2) - self.assertEqual(len(mo), 1, 'MO should have been created') - - self.stock_location = self.env.ref('stock.stock_location_stock') - self.stock_shelf_1 = self.stock_location_components - self.stock_shelf_2 = self.stock_location_14 - - self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_1, 3) - self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 3) - self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_2, 2) - - self.env['stock.quant']._update_available_quantity(p2, self.stock_shelf_1, 1) - self.env['stock.quant']._update_available_quantity(p2, self.stock_shelf_2, 1) - - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 1 - produce_wizard = produce_form.save() - - self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 2) - produce_wizard.do_produce() - - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 1 - - produce_wizard = produce_form.save() - - self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 2) - produce_wizard.do_produce() - - mo.button_mark_done() - mo_move_line_p1 = mo.move_raw_ids[1].move_line_ids - self.assertEqual(sum(mo_move_line_p1.filtered(lambda ml: ml.location_id == self.stock_location).mapped('qty_done')), 3) - self.assertEqual(sum(mo_move_line_p1.filtered(lambda ml: ml.location_id == self.stock_shelf_1).mapped('qty_done')), 3) - self.assertEqual(sum(mo_move_line_p1.filtered(lambda ml: ml.location_id == self.stock_shelf_2).mapped('qty_done')), 2) - self.assertEqual(sum(mo.move_finished_ids.move_line_ids.mapped('qty_done')), 2) - - self.assertEqual(self.env['stock.quant']._gather(p1, self.stock_location, strict=True).quantity, 0) - self.assertEqual(self.env['stock.quant']._gather(p1, self.stock_shelf_1, strict=True).quantity, 0) - self.assertEqual(self.env['stock.quant']._gather(p1, self.stock_shelf_2, strict=True).quantity, 0) - - self.assertEqual(self.env['stock.quant']._gather(p2, self.stock_shelf_1, strict=True).quantity, 0) - self.assertEqual(self.env['stock.quant']._gather(p2, self.stock_shelf_2, strict=True).quantity, 0) - self.assertEqual(self.env['stock.quant']._gather(p_final, self.stock_location, strict=True).quantity, 2) - - def test_product_produce_8(self): - """ Produce more than reserved and planned. Check that produce wizard - only propose one line for product not reserved. - """ - mo, bom, p_final, p1, p2 = self.generate_mo(qty_final=2) - self.assertEqual(len(mo), 1, 'MO should have been created') - - self.stock_location = self.env.ref('stock.stock_location_stock') - - self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 5) - self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 2) - - mo.action_assign() - - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 1 - produce_wizard = produce_form.save() - self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 2) - self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1).qty_reserved, 4) - self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1) - produce_wizard.do_produce() - - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 1 - produce_wizard = produce_form.save() - # p1 1 1 1 - # p1 3 0 3 - # p2 1 1 1 - self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 3) - self.assertEqual(sum(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1) - self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1) - self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 3) - self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1) - - with Form(produce_wizard) as produce_form: - produce_form.qty_producing = 2 - # p1 1 1 1 - # p1 7 0 7 - # p2 1 1 1 - # p2 1 0 1 - self.assertEqual(len(produce_wizard.raw_workorder_line_ids), 4) - self.assertEqual(sum(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1) - self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1) - self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 7) - self.assertEqual(sum(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p2).mapped('qty_reserved')), 1) - - produce_wizard.do_produce() - - mo.button_mark_done() - - self.assertEqual(self.env['stock.quant']._gather(p1, self.stock_location, strict=True).quantity, -7) - self.assertEqual(self.env['stock.quant']._gather(p2, self.stock_location, strict=True).quantity, -1) - self.assertEqual(self.env['stock.quant']._gather(p_final, self.stock_location, strict=True).quantity, 3) - def test_product_produce_9(self): - """ Checks the constraints of a strict BOM without tracking when playing around in the - produce wizard. - """ + """ Checks the constraints of a strict BOM without tracking when playing around + quantities to consume.""" self.stock_location = self.env.ref('stock.stock_location_stock') - mo, bom, p_final, p1, p2 = self.generate_mo() + mo, bom, p_final, p1, p2 = self.generate_mo(consumption='strict') self.assertEqual(len(mo), 1, 'MO should have been created') self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100) @@ -803,52 +623,22 @@ class TestMrpOrder(TestMrpCommon): mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) + mo_form = Form(mo.with_context({'debug': True})) with self.assertRaises(UserError): # try adding another line for a bom product to increase the quantity - produce_form.qty_producing = 1 - with produce_form.raw_workorder_line_ids.new() as line: - line.product_id = p1 - line.qty_done = 1 - product_produce = produce_form.save() - product_produce.do_produce() + mo_form.qty_producing = 1 + with mo_form.move_raw_ids.new() as move: + move.product_id = p1 + move.quantity_done = 1 + mo = mo_form.save() + mo.button_mark_done() with self.assertRaises(UserError): # Try updating qty_done - product_produce = produce_form.save() - product_produce.raw_workorder_line_ids[0].qty_done += 1 - product_produce.do_produce() - - with self.assertRaises(UserError): - # try adding another product - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 1 - with produce_form.raw_workorder_line_ids.new() as line: - line.product_id = self.product_4 - line.qty_done = 1 - product_produce = produce_form.save() - product_produce.do_produce() - - # try adding another line for a bom product but the total quantity is good - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 1 - - with produce_form.raw_workorder_line_ids.new() as line: - line.product_id = p1 - line.qty_done = 1 - product_produce = produce_form.save() - product_produce.raw_workorder_line_ids[1].qty_done -= 1 - product_produce.do_produce() + mo = mo_form.save() + mo.move_raw_ids[0].move_line_ids[0].qty_done += 1 + mo.button_mark_done() def test_product_produce_10(self): """ Produce byproduct with serial, lot and not tracked. @@ -1027,43 +817,39 @@ class TestMrpOrder(TestMrpCommon): mo.bom_id.consumption = 'flexible' # Because we'll over-consume with a product not defined in the BOM mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 3 - self.assertEqual(len(produce_form.raw_workorder_line_ids._records), 4, 'Update the produce quantity should change the components quantity.') - self.assertEqual(sum([x['qty_done'] for x in produce_form.raw_workorder_line_ids._records]), 15, 'Update the produce quantity should change the components quantity.') - self.assertEqual(sum([x['qty_reserved'] for x in produce_form.raw_workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.') - produce_form.qty_producing = 4 - self.assertEqual(len(produce_form.raw_workorder_line_ids._records), 4, 'Update the produce quantity should change the components quantity.') - self.assertEqual(sum([x['qty_done'] for x in produce_form.raw_workorder_line_ids._records]), 20, 'Update the produce quantity should change the components quantity.') - self.assertEqual(sum([x['qty_reserved'] for x in produce_form.raw_workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.') - - produce_form.qty_producing = 1 - self.assertEqual(len(produce_form.raw_workorder_line_ids._records), 2, 'Update the produce quantity should change the components quantity.') - self.assertEqual(sum([x['qty_done'] for x in produce_form.raw_workorder_line_ids._records]), 5, 'Update the produce quantity should change the components quantity.') - self.assertEqual(sum([x['qty_reserved'] for x in produce_form.raw_workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.') + mo_form = Form(mo.with_context({'debug': True})) + mo_form.qty_producing = 3 + self.assertEqual(sum([x['quantity_done'] for x in mo_form.move_raw_ids._records]), 15, 'Update the produce quantity should change the components quantity.') + self.assertEqual(sum([x['reserved_availability'] for x in mo_form.move_raw_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.') + mo_form.qty_producing = 4 + self.assertEqual(sum([x['quantity_done'] for x in mo_form.move_raw_ids._records]), 20, 'Update the produce quantity should change the components quantity.') + self.assertEqual(sum([x['reserved_availability'] for x in mo_form.move_raw_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.') + mo_form.qty_producing = 1 + self.assertEqual(sum([x['quantity_done'] for x in mo_form.move_raw_ids._records]), 5, 'Update the produce quantity should change the components quantity.') + self.assertEqual(sum([x['reserved_availability'] for x in mo_form.move_raw_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.') # try adding another product that doesn't belong to the BoM - with produce_form.raw_workorder_line_ids.new() as line: - line.product_id = self.product_4 - line.qty_done = 1 - produce_wizard = produce_form.save() - produce_wizard.do_produce() + with mo_form.move_raw_ids.new() as move: + move.product_id = self.product_4 + move.quantity_done = 10 + # Check that this new product is not updated by qty_producing + mo_form.qty_producing = 2 + for move in mo_form.move_raw_ids._records: + if move['product_id'] == self.product_4.id: + self.assertEqual(move['quantity_done'], 10) + break + mo = mo_form.save() + mo.button_mark_done() def test_product_produce_duplicate_1(self): """ produce a finished product tracked by serial number 2 times with the same SN. Check that an error is raised the second time""" mo1, bom, p_final, p1, p2 = self.generate_mo(tracking_final='serial', qty_final=1, qty_base_1=1,) - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo1.id, - 'active_ids': [mo1.id], - })) - product_produce = produce_form.save() - product_produce.action_generate_serial() - sn = product_produce.finished_lot_id - product_produce.do_produce() + mo_form = Form(mo1) + mo_form.qty_producing = 1 + mo1 = mo_form.save() + mo1.action_generate_serial() + sn = mo1.lot_producing_id mo1.button_mark_done() mo_form = Form(self.env['mrp.production']) @@ -1073,14 +859,11 @@ class TestMrpOrder(TestMrpCommon): mo2 = mo_form.save() mo2.action_confirm() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo2.id, - 'active_ids': [mo2.id], - })) - produce_form.finished_lot_id = sn - product_produce = produce_form.save() + mo_form = Form(mo2) + mo_form.lot_producing_id = sn + mo2 = mo_form.save() with self.assertRaises(UserError): - product_produce.do_produce() + mo2.button_mark_done() def test_product_produce_duplicate_2(self): """ produce a finished product with component tracked by serial number 2 @@ -1091,14 +874,13 @@ class TestMrpOrder(TestMrpCommon): 'product_id': p2.id, 'company_id': self.env.company.id, }) - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo1.id, - 'active_ids': [mo1.id], - })) - with produce_form.raw_workorder_line_ids.edit(0) as line: - line.lot_id = sn - product_produce = produce_form.save() - product_produce.do_produce() + mo_form = Form(mo1) + mo_form.qty_producing = 1 + mo1 = mo_form.save() + details_operation_form = Form(mo1.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = sn + details_operation_form.save() mo1.button_mark_done() mo_form = Form(self.env['mrp.production']) @@ -1108,15 +890,15 @@ class TestMrpOrder(TestMrpCommon): mo2 = mo_form.save() mo2.action_confirm() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo2.id, - 'active_ids': [mo2.id], - })) - with produce_form.raw_workorder_line_ids.edit(0) as line: - line.lot_id = sn - product_produce = produce_form.save() + mo_form = Form(mo2) + mo_form.qty_producing = 1 + mo2 = mo_form.save() + details_operation_form = Form(mo2.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = sn + details_operation_form.save() with self.assertRaises(UserError): - product_produce.do_produce() + mo2.button_mark_done() def test_product_produce_duplicate_3(self): """ produce a finished product with by-product tracked by serial number 2 @@ -1148,15 +930,15 @@ class TestMrpOrder(TestMrpCommon): 'product_id': byproduct.id, 'company_id': self.env.company.id, }) - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - with produce_form.finished_workorder_line_ids.edit(0) as line: - line.lot_id = sn - product_produce = produce_form.save() - product_produce.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 1 + mo = mo_form.save() + move_byproduct = mo.move_finished_ids.filtered(lambda m: m.product_id != mo.product_id) + details_operation_form = Form(move_byproduct, view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = sn + details_operation_form.save() mo.button_mark_done() mo_form = Form(self.env['mrp.production']) @@ -1166,15 +948,16 @@ class TestMrpOrder(TestMrpCommon): mo2 = mo_form.save() mo2.action_confirm() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo2.id, - 'active_ids': [mo2.id], - })) - with produce_form.finished_workorder_line_ids.edit(0) as line: - line.lot_id = sn - product_produce = produce_form.save() + mo_form = Form(mo2) + mo_form.qty_producing = 1 + mo2 = mo_form.save() + move_byproduct = mo2.move_finished_ids.filtered(lambda m: m.product_id != mo.product_id) + details_operation_form = Form(move_byproduct, view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = sn + details_operation_form.save() with self.assertRaises(UserError): - product_produce.do_produce() + mo2.button_mark_done() def test_product_produce_duplicate_4(self): """ Consuming the same serial number two times should not give an error if @@ -1185,14 +968,13 @@ class TestMrpOrder(TestMrpCommon): 'product_id': p2.id, 'company_id': self.env.company.id, }) - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo1.id, - 'active_ids': [mo1.id], - })) - with produce_form.raw_workorder_line_ids.edit(0) as line: - line.lot_id = sn - product_produce = produce_form.save() - product_produce.do_produce() + mo_form = Form(mo1) + mo_form.qty_producing = 1 + mo1 = mo_form.save() + details_operation_form = Form(mo1.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = sn + details_operation_form.save() mo1.button_mark_done() unbuild_form = Form(self.env['mrp.unbuild']) @@ -1210,15 +992,15 @@ class TestMrpOrder(TestMrpCommon): mo2 = mo_form.save() mo2.action_confirm() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo2.id, - 'active_ids': [mo2.id], - })) - with produce_form.raw_workorder_line_ids.edit(0) as line: - line.lot_id = sn - product_produce = produce_form.save() - product_produce.do_produce() - + mo_form = Form(mo2) + mo_form.qty_producing = 1 + mo2 = mo_form.save() + details_operation_form = Form(mo2.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = sn + details_operation_form.save() + mo2.button_mark_done() + def test_product_produce_uom(self): """ Produce a finished product tracked by serial number. Set another UoM on the bom. The produce wizard should keep the UoM of the product (unit) @@ -1268,86 +1050,20 @@ class TestMrpOrder(TestMrpCommon): self.assertEqual(mo.move_raw_ids.product_qty, 12, '12 units should be reserved.') # produce product - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.finished_lot_id = final_product_lot - product_produce = produce_form.save() - self.assertEqual(product_produce.qty_producing, 1) - self.assertEqual(product_produce.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.') - product_produce.finished_lot_id = final_product_lot.id + mo_form = Form(mo) + mo_form.qty_producing = 1/12.0 + mo_form.lot_producing_id = final_product_lot + mo = mo_form.save() - product_produce.do_produce() move_line_raw = mo.move_raw_ids.mapped('move_line_ids').filtered(lambda m: m.qty_done) self.assertEqual(move_line_raw.qty_done, 1) self.assertEqual(move_line_raw.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.') + mo._post_inventory() move_line_finished = mo.move_finished_ids.mapped('move_line_ids').filtered(lambda m: m.qty_done) self.assertEqual(move_line_finished.qty_done, 1) self.assertEqual(move_line_finished.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.') - def test_product_produce_uom_2(self): - """ Create a bom with a serial tracked component and a pair UoM (2 x unit). - The produce wizard should create 2 line with quantity = 1 and UoM = unit for - this component. """ - - unit = self.env.ref("uom.product_uom_unit") - categ_unit_id = self.env.ref('uom.product_uom_categ_unit') - paire = self.env['uom.uom'].create({ - 'name': 'Paire', - 'factor_inv': 2, - 'uom_type': 'bigger', - 'rounding': 0.001, - 'category_id': categ_unit_id.id - }) - binocular = self.env['product.product'].create({ - 'name': 'Binocular', - 'type': 'product', - 'uom_id': unit.id, - 'uom_po_id': unit.id - }) - nocular = self.env['product.product'].create({ - 'name': 'Nocular', - 'type': 'product', - 'tracking': 'serial', - 'uom_id': unit.id, - 'uom_po_id': unit.id - }) - bom_binocular = self.env['mrp.bom'].create({ - 'product_tmpl_id': binocular.product_tmpl_id.id, - 'product_qty': 1, - 'product_uom_id': unit.id, - 'bom_line_ids': [(0, 0, { - 'product_id': nocular.id, - 'product_qty': 1, - 'product_uom_id': paire.id - })] - }) - mo_form = Form(self.env['mrp.production']) - mo_form.product_id = binocular - mo_form.bom_id = bom_binocular - mo_form.product_uom_id = unit - mo_form.product_qty = 1 - mo = mo_form.save() - - mo.action_confirm() - self.assertEqual(mo.move_raw_ids.product_uom_qty, 1, 'Quantity should be 1.') - self.assertEqual(mo.move_raw_ids.product_uom, paire, 'Move UoM should be "Paire".') - - # produce product - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - product_produce = produce_form.save() - self.assertEqual(product_produce.qty_producing, 1) - self.assertEqual(len(product_produce.raw_workorder_line_ids), 2, 'Should be 2 lines since the component tracking is serial and quantity 2.') - self.assertEqual(product_produce.raw_workorder_line_ids[0].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.') - self.assertEqual(product_produce.raw_workorder_line_ids[0].product_uom_id, unit, 'Should be the product uom so "unit"') - self.assertEqual(product_produce.raw_workorder_line_ids[1].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.') - self.assertEqual(product_produce.raw_workorder_line_ids[1].product_uom_id, unit, 'should be the product uom so "unit"') - def test_product_type_service_1(self): # Create finished product finished_product = self.env['product.product'].create({ diff --git a/addons/mrp/tests/test_procurement.py b/addons/mrp/tests/test_procurement.py index 9e1d25e59ec..89be67074bd 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', 'Production order should be for Confirmed state') + self.assertEqual(production_product_6.state, 'confirmed') # Check procurement for product 4 created or not. # Check it created a purchase order @@ -69,14 +69,9 @@ class TestProcurement(TestMrpCommon): # produce product4 # --------------- - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': produce_product_4.id, - 'active_ids': [produce_product_4.id], - })) - produce_form.qty_producing = produce_product_4.product_qty - product_produce = produce_form.save() - product_produce.do_produce() - produce_product_4.post_inventory() + mo_form = Form(produce_product_4) + mo_form.qty_producing = produce_product_4.product_qty + produce_product_4 = mo_form.save() # Check procurement and Production state for product 4. produce_product_4.button_mark_done() self.assertEqual(produce_product_4.state, 'done', 'Production order should be in state done') @@ -95,14 +90,9 @@ class TestProcurement(TestMrpCommon): # ------------------------------------ self.assertEqual(production_product_6.reservation_state, 'assigned', "Consume material not available") - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': production_product_6.id, - 'active_ids': [production_product_6.id], - })) - produce_form.qty_producing = production_product_6.product_qty - product_produce = produce_form.save() - product_produce.do_produce() - production_product_6.post_inventory() + mo_form = Form(production_product_6) + mo_form.qty_producing = production_product_6.product_qty + production_product_6 = mo_form.save() # Check procurement and Production state for product 6. production_product_6.button_mark_done() self.assertEqual(production_product_6.state, 'done', 'Production order should be in state done') @@ -191,13 +181,9 @@ class TestProcurement(TestMrpCommon): self.assertEqual(picking_qc_to_stock.state, 'done') mo.action_assign() self.assertEqual(mo.move_raw_ids.reserved_availability, 3.0) - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) + produce_form = Form(mo) produce_form.qty_producing = 3.0 - produce_wizard = produce_form.save() - produce_wizard.do_produce() + mo = produce_form.save() self.assertEqual(mo.move_raw_ids.quantity_done, 3.0) picking_qc_to_stock.move_line_ids.qty_done = 5.0 self.assertEqual(mo.move_raw_ids.reserved_availability, 5.0) @@ -419,12 +405,9 @@ class TestProcurement(TestMrpCommon): self.env['stock.move'].create(move_values) production.action_confirm() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': production.id, - 'active_ids': [production.id], - })) - product_produce = produce_form.save() - product_produce.do_produce() + produce_form = Form(production) + produce_form.qty_producing = production.product_qty + production = produce_form.save() production.button_mark_done() move_dest._action_assign() diff --git a/addons/mrp/tests/test_stock.py b/addons/mrp/tests/test_stock.py index 0d1097abbf3..9b51973f19d 100644 --- a/addons/mrp/tests/test_stock.py +++ b/addons/mrp/tests/test_stock.py @@ -207,14 +207,10 @@ class TestWarehouse(common.TestMrpCommon): mo_laptop = self.new_mo_laptop() serial = self.env['stock.production.lot'].create({'product_id': self.laptop.id, 'company_id': self.env.company.id}) - product_produce = self.env['mrp.product.produce'].with_context({ - 'active_id': mo_laptop.id, - 'active_ids': [mo_laptop.id], - }).create({ - "qty_producing": 1.0, - "finished_lot_id": serial.id, - }) - product_produce.do_produce() + mo_form = Form(mo_laptop) + mo_form.qty_producing = 1 + mo_form.lot_producing_id = serial + mo_laptop = mo_form.save() mo_laptop.button_mark_done() # We check if the laptop go in the depot and not in the stock diff --git a/addons/mrp/tests/test_traceability.py b/addons/mrp/tests/test_traceability.py index 2c05b2e70fe..a7f09bf6de1 100644 --- a/addons/mrp/tests/test_traceability.py +++ b/addons/mrp/tests/test_traceability.py @@ -63,19 +63,22 @@ class TestTraceability(TestMrpCommon): mo.action_assign() # Start MO production - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - - if finished_product.tracking != 'serial': - produce_form.qty_producing = 1 - + mo_form = Form(mo) + mo_form.qty_producing = 1 if finished_product.tracking != 'none': - produce_form.finished_lot_id = self.env['stock.production.lot'].create({'name': 'Serial or Lot finished', 'product_id': finished_product.id, 'company_id': self.env.company.id}) - produce_wizard = produce_form.save() + mo_form.lot_producing_id = self.env['stock.production.lot'].create({'name': 'Serial or Lot finished', 'product_id': finished_product.id, 'company_id': self.env.company.id}) + mo = mo_form.save() + + details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.qty_done = 1 + details_operation_form.save() + details_operation_form = Form(mo.move_raw_ids[2], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.qty_done = 1 + details_operation_form.save() + - produce_wizard.do_produce() mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") @@ -160,41 +163,56 @@ class TestTraceability(TestMrpCommon): mo = mo_form.save() mo.action_confirm() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.finished_lot_id = self.env['stock.production.lot'].create({ + mo_form = Form(mo) + mo_form.lot_producing_id = self.env['stock.production.lot'].create({ 'product_id': product_final.id, 'name': 'Final_lot_1', 'company_id': self.env.company.id, }) - with produce_form.raw_workorder_line_ids.edit(0) as line: - line.lot_id = self.env['stock.production.lot'].create({ + mo = mo_form.save() + + details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = self.env['stock.production.lot'].create({ 'product_id': product_1.id, 'name': 'Raw_1_lot_1', 'company_id': self.env.company.id, }) - with produce_form.raw_workorder_line_ids.edit(1) as line: - line.lot_id = self.env['stock.production.lot'].create({ + ml.qty_done = 1 + details_operation_form.save() + details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = self.env['stock.production.lot'].create({ 'product_id': product_2.id, 'name': 'Raw_2_lot_1', 'company_id': self.env.company.id, }) - with produce_form.finished_workorder_line_ids.edit(0) as line: - line.lot_id = self.env['stock.production.lot'].create({ + ml.qty_done = 1 + details_operation_form.save() + details_operation_form = Form( + mo.move_finished_ids.filtered(lambda m: m.product_id == byproduct_1), + view=self.env.ref('stock.view_stock_move_operations') + ) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = self.env['stock.production.lot'].create({ 'product_id': byproduct_1.id, 'name': 'Byproduct_1_lot_1', 'company_id': self.env.company.id, }) - with produce_form.finished_workorder_line_ids.edit(1) as line: - line.lot_id = self.env['stock.production.lot'].create({ + ml.qty_done = 1 + details_operation_form.save() + details_operation_form = Form( + mo.move_finished_ids.filtered(lambda m: m.product_id == byproduct_2), + view=self.env.ref('stock.view_stock_move_operations') + ) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = self.env['stock.production.lot'].create({ 'product_id': byproduct_2.id, 'name': 'Byproduct_2_lot_1', 'company_id': self.env.company.id, }) - produce_wizard = produce_form.save() - produce_wizard.continue_production() + ml.qty_done = 1 + details_operation_form.save() action = mo.button_mark_done() backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context'])) @@ -206,59 +224,84 @@ class TestTraceability(TestMrpCommon): 'name': 'Final_lot_2', 'company_id': self.env.company.id, }) - with produce_form.raw_workorder_line_ids.edit(0) as line: - line.lot_id = self.env['stock.production.lot'].create({ + mo_form.qty_producing = 1 + mo_backorder = mo_form.save() + + details_operation_form = Form( + mo_backorder.move_raw_ids.filtered(lambda m: m.product_id == product_1), + view=self.env.ref('stock.view_stock_move_operations') + ) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = self.env['stock.production.lot'].create({ 'product_id': product_1.id, 'name': 'Raw_1_lot_2', 'company_id': self.env.company.id, }) - with produce_form.raw_workorder_line_ids.edit(1) as line: - line.lot_id = self.env['stock.production.lot'].create({ + ml.qty_done = 1 + details_operation_form.save() + details_operation_form = Form( + mo_backorder.move_raw_ids.filtered(lambda m: m.product_id == product_2), + view=self.env.ref('stock.view_stock_move_operations') + ) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = self.env['stock.production.lot'].create({ 'product_id': product_2.id, 'name': 'Raw_2_lot_2', 'company_id': self.env.company.id, }) - with produce_form.finished_workorder_line_ids.edit(0) as line: - line.lot_id = self.env['stock.production.lot'].create({ + ml.qty_done = 1 + details_operation_form.save() + details_operation_form = Form( + mo_backorder.move_finished_ids.filtered(lambda m: m.product_id == byproduct_1), + view=self.env.ref('stock.view_stock_move_operations') + ) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = self.env['stock.production.lot'].create({ 'product_id': byproduct_1.id, 'name': 'Byproduct_1_lot_2', 'company_id': self.env.company.id, }) - with produce_form.finished_workorder_line_ids.edit(1) as line: - line.lot_id = self.env['stock.production.lot'].create({ + ml.qty_done = 1 + details_operation_form.save() + details_operation_form = Form( + mo_backorder.move_finished_ids.filtered(lambda m: m.product_id == byproduct_2), + view=self.env.ref('stock.view_stock_move_operations') + ) + with details_operation_form.move_line_ids.new() as ml: + ml.lot_id = self.env['stock.production.lot'].create({ 'product_id': byproduct_2.id, 'name': 'Byproduct_2_lot_2', 'company_id': self.env.company.id, }) - produce_wizard = produce_form.save() - produce_wizard.do_produce() - mo.button_mark_done() + ml.qty_done = 1 + details_operation_form.save() - self.assertEqual(len(mo.move_raw_ids.mapped('move_line_ids')), 4) - self.assertEqual(len(mo.move_finished_ids.mapped('move_line_ids')), 6) + mo_backorder.button_mark_done() + # self.assertEqual(len(mo.move_raw_ids.mapped('move_line_ids')), 4) + # self.assertEqual(len(mo.move_finished_ids.mapped('move_line_ids')), 6) + + mo = mo | mo_backorder raw_move_lines = mo.move_raw_ids.mapped('move_line_ids') raw_line_raw_1_lot_1 = raw_move_lines.filtered(lambda ml: ml.lot_id.name == 'Raw_1_lot_1') - self.assertEqual(set(raw_line_raw_1_lot_1.lot_produced_ids.mapped('name')), set(['Final_lot_1', 'Byproduct_1_lot_1', 'Byproduct_2_lot_1'])) - raw_line_raw_1_lot_2 = raw_move_lines.filtered(lambda ml: ml.lot_id.name == 'Raw_1_lot_2') - self.assertEqual(set(raw_line_raw_1_lot_2.lot_produced_ids.mapped('name')), set(['Final_lot_2', 'Byproduct_1_lot_2', 'Byproduct_2_lot_2'])) + self.assertEqual(set(raw_line_raw_1_lot_1.produce_line_ids.lot_id.mapped('name')), set(['Final_lot_1', 'Byproduct_1_lot_1', 'Byproduct_2_lot_1'])) raw_line_raw_2_lot_1 = raw_move_lines.filtered(lambda ml: ml.lot_id.name == 'Raw_2_lot_1') - self.assertEqual(set(raw_line_raw_2_lot_1.lot_produced_ids.mapped('name')), set(['Final_lot_1', 'Byproduct_1_lot_1', 'Byproduct_2_lot_1'])) - raw_line_raw_2_lot_2 = raw_move_lines.filtered(lambda ml: ml.lot_id.name == 'Raw_2_lot_2') - self.assertEqual(set(raw_line_raw_2_lot_2.lot_produced_ids.mapped('name')), set(['Final_lot_2', 'Byproduct_1_lot_2', 'Byproduct_2_lot_2'])) + self.assertEqual(set(raw_line_raw_2_lot_1.produce_line_ids.lot_id.mapped('name')), set(['Final_lot_1', 'Byproduct_1_lot_1', 'Byproduct_2_lot_1'])) finished_move_lines = mo.move_finished_ids.mapped('move_line_ids') finished_move_line_lot_1 = finished_move_lines.filtered(lambda ml: ml.lot_id.name == 'Final_lot_1') - self.assertEqual(finished_move_line_lot_1.consume_line_ids, raw_line_raw_1_lot_1 | raw_line_raw_2_lot_1) + self.assertEqual(finished_move_line_lot_1.consume_line_ids.filtered(lambda l: l.qty_done), raw_line_raw_1_lot_1 | raw_line_raw_2_lot_1) finished_move_line_lot_2 = finished_move_lines.filtered(lambda ml: ml.lot_id.name == 'Final_lot_2') + raw_line_raw_1_lot_2 = raw_move_lines.filtered(lambda ml: ml.lot_id.name == 'Raw_1_lot_2') + raw_line_raw_2_lot_2 = raw_move_lines.filtered(lambda ml: ml.lot_id.name == 'Raw_2_lot_2') self.assertEqual(finished_move_line_lot_2.consume_line_ids, raw_line_raw_1_lot_2 | raw_line_raw_2_lot_2) byproduct_move_line_1_lot_1 = finished_move_lines.filtered(lambda ml: ml.lot_id.name == 'Byproduct_1_lot_1') - self.assertEqual(byproduct_move_line_1_lot_1.consume_line_ids, raw_line_raw_1_lot_1 | raw_line_raw_2_lot_1) + self.assertEqual(byproduct_move_line_1_lot_1.consume_line_ids.filtered(lambda l: l.qty_done), raw_line_raw_1_lot_1 | raw_line_raw_2_lot_1) byproduct_move_line_1_lot_2 = finished_move_lines.filtered(lambda ml: ml.lot_id.name == 'Byproduct_1_lot_2') self.assertEqual(byproduct_move_line_1_lot_2.consume_line_ids, raw_line_raw_1_lot_2 | raw_line_raw_2_lot_2) byproduct_move_line_2_lot_1 = finished_move_lines.filtered(lambda ml: ml.lot_id.name == 'Byproduct_2_lot_1') - self.assertEqual(byproduct_move_line_2_lot_1.consume_line_ids, raw_line_raw_1_lot_1 | raw_line_raw_2_lot_1) + self.assertEqual(byproduct_move_line_2_lot_1.consume_line_ids.filtered(lambda l: l.qty_done), raw_line_raw_1_lot_1 | raw_line_raw_2_lot_1) byproduct_move_line_2_lot_2 = finished_move_lines.filtered(lambda ml: ml.lot_id.name == 'Byproduct_2_lot_2') self.assertEqual(byproduct_move_line_2_lot_2.consume_line_ids, raw_line_raw_1_lot_2 | raw_line_raw_2_lot_2) diff --git a/addons/mrp/tests/test_unbuild.py b/addons/mrp/tests/test_unbuild.py index 3ceb071bf1b..159b4c3df3c 100644 --- a/addons/mrp/tests/test_unbuild.py +++ b/addons/mrp/tests/test_unbuild.py @@ -5,6 +5,7 @@ from odoo.tests import Form from odoo.addons.mrp.tests.common import TestMrpCommon from odoo.exceptions import UserError + class TestUnbuild(TestMrpCommon): def setUp(self): super(TestUnbuild, self).setUp() @@ -26,14 +27,9 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 5.0 - produce_wizard = produce_form.save() - produce_wizard.do_produce() - + mo_form = Form(mo) + mo_form.qty_producing = 5.0 + mo = mo_form.save() mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") @@ -97,15 +93,10 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 5.0 - produce_form.finished_lot_id = lot - produce_wizard = produce_form.save() - - produce_wizard.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 5.0 + mo_form.lot_producing_id = lot + mo = mo_form.save() mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") @@ -182,14 +173,15 @@ class TestUnbuild(TestMrpCommon): if ml.product_id.tracking != 'none': self.assertEqual(ml.lot_id, lot, 'Wrong reserved lot.') - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 5.0 - produce_wizard = produce_form.save() - - produce_wizard.do_produce() + # FIXME sle: behavior change + mo_form = Form(mo) + mo_form.qty_producing = 5.0 + mo = mo_form.save() + details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.lot_id = lot + ml.qty_done = 20 + details_operation_form.save() mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") @@ -272,15 +264,19 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5, lot_id=lot_2) mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 5.0 - produce_form.finished_lot_id = lot_final - produce_wizard = produce_form.save() - - produce_wizard.do_produce() + # FIXME sle: behavior change + mo_form = Form(mo) + mo_form.qty_producing = 5.0 + mo_form.lot_producing_id = lot_final + mo = mo_form.save() + details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.qty_done = 5 + details_operation_form.save() + details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.qty_done = 20 + details_operation_form.save() mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") @@ -326,7 +322,7 @@ class TestUnbuild(TestMrpCommon): x.save().action_unbuild() self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), 2, 'You should have consumed 3 final product in stock') - self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot_1), 92, 'You should have 80 products in stock') + self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot_1), 92, 'You should have 92 products in stock') self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_2), 3, 'You should have consumed all the 5 product in stock') x = Form(self.env['mrp.unbuild']) @@ -381,14 +377,18 @@ class TestUnbuild(TestMrpCommon): self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 2, lot_id=lot_3) mo.action_assign() - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 5.0 - produce_wizard = produce_form.save() + mo_form = Form(mo) + mo_form.qty_producing = 5.0 + mo = mo_form.save() + details_operation_form = Form(mo.move_raw_ids.filtered(lambda ml: ml.product_id == p2), view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.qty_done = ml.product_uom_qty + with details_operation_form.move_line_ids.edit(1) as ml: + ml.qty_done = ml.product_uom_qty + with details_operation_form.move_line_ids.edit(2) as ml: + ml.qty_done = ml.product_uom_qty + details_operation_form.save() - produce_wizard.do_produce() mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") # Check quantity in stock before unbuild. @@ -415,6 +415,9 @@ class TestUnbuild(TestMrpCommon): """ This test produces an MO in two times and checks that the move lines are linked in a correct way """ mo, bom, p_final, p1, p2 = self.generate_mo(tracking_final='lot', tracking_base_1='none', tracking_base_2='lot') + # Young Tom + # \ Botox - 4 - p1 + # \ Old Tom - 1 - p2 lot_1 = self.env['stock.production.lot'].create({ 'name': 'lot_1', 'product_id': p2.id, @@ -456,21 +459,26 @@ class TestUnbuild(TestMrpCommon): 'company_id': self.env.company.id, }) - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 2.0 - produce_form.finished_lot_id = lot_finished_2 + mo = mo.procurement_group_id.mrp_production_ids[1] + # FIXME sle: issue in backorder? + mo.move_raw_ids.move_line_ids.unlink() + self.assertEqual(mo.product_qty, 2) + mo_form = Form(mo) + mo_form.qty_producing = 2 + mo_form.lot_producing_id = lot_finished_2 + mo = mo_form.save() + details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.new() as ml: + ml.qty_done = 2 + ml.lot_id = lot_2 + details_operation_form.save() + action = mo.button_mark_done() - produce_wizard = produce_form.save() - produce_wizard._workorder_line_ids()[0].lot_id = lot_2 - produce_wizard.do_produce() - mo.button_mark_done() - ml = mo.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and lot_finished_1 in m.lot_produced_ids) - self.assertEqual(ml[0].qty_done, 12.0, 'Should have consumed 12 for the first lot') - ml = mo.finished_move_line_ids[1].consume_line_ids.filtered(lambda m: m.product_id == p1 and lot_finished_2 in m.lot_produced_ids) - self.assertEqual(ml[0].qty_done, 8.0, 'Should have consumed 8 for the second lot') + mo1 = mo.procurement_group_id.mrp_production_ids[0] + ml = mo1.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and lot_finished_1 in m.produce_line_ids.lot_id) + self.assertEqual(sum(ml.mapped('qty_done')), 12.0, 'Should have consumed 12 for the first lot') + ml = mo.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and lot_finished_2 in m.produce_line_ids.lot_id) + self.assertEqual(sum(ml.mapped('qty_done')), 8.0, 'Should have consumed 8 for the second lot') def test_unbuild_with_routes(self): """ This test creates a MO of a stockable product (Table). A new route for rule QC/Unbuild -> Stock @@ -544,13 +552,9 @@ class TestUnbuild(TestMrpCommon): mo.action_assign() # Produce the final product - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': mo.id, - 'active_ids': [mo.id], - })) - produce_form.qty_producing = 1.0 - produce_wizard = produce_form.save() - produce_wizard.do_produce() + mo_form = Form(mo) + mo_form.qty_producing = 1.0 + produce_wizard = mo_form.save() mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") diff --git a/addons/mrp/tests/test_warehouse_multistep_manufacturing.py b/addons/mrp/tests/test_warehouse_multistep_manufacturing.py index a04f9078acc..408045ae274 100644 --- a/addons/mrp/tests/test_warehouse_multistep_manufacturing.py +++ b/addons/mrp/tests/test_warehouse_multistep_manufacturing.py @@ -184,13 +184,9 @@ class TestMultistepManufacturingWarehouse(TestMrpCommon): self.assertEqual(production_order.reservation_state, 'assigned') self.assertEqual(picking_stock_postprod.state, 'waiting') - produce_form = Form(self.env['mrp.product.produce'].with_context({ - 'active_id': production_order.id, - 'active_ids': [production_order.id], - })) + produce_form = Form(production_order) produce_form.qty_producing = production_order.product_qty - product_produce = produce_form.save() - product_produce.do_produce() + production_order = produce_form.save() production_order.button_mark_done() self.assertFalse(sum(self.env['stock.quant']._gather(self.raw_product, self.warehouse.pbm_loc_id).mapped('quantity'))) diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py index 399c1d963c2..7327ed84c11 100644 --- a/addons/mrp/tests/test_workorder_operation.py +++ b/addons/mrp/tests/test_workorder_operation.py @@ -151,7 +151,6 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): 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() # Set tracking lot on finish and consume products. dining_table.tracking = 'lot' @@ -159,6 +158,7 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): 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}) @@ -626,9 +626,6 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): # ----------------- # Produce 6 Unit of custom laptop will consume ( 12 Unit of keybord and 12 Unit of charger) - context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id} - product_form = Form(self.env['mrp.product.produce'].with_context(context)) - product_form.qty_producing = 6.00 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 @@ -651,11 +648,8 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): 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) - mo_custom_laptop.post_inventory() - self.assertEqual(len(mo_custom_laptop.move_raw_ids), 4) # 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) @@ -665,165 +659,26 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): # ---------------------------- # Produce 4 Unit of custom laptop will consume ( 8 Unit of keybord and 8 Unit of charger). - context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id} - produce_form = Form(self.env['mrp.product.produce'].with_context(context)) - produce_form.qty_producing = 4.00 laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id, 'company_id': self.env.company.id}) - produce_form.finished_lot_id = laptop_lot_002 - product_consume = produce_form.save() - self.assertEqual(len(product_consume._workorder_line_ids()), 2) - product_consume._workorder_line_ids()[0].qty_done = 8 - product_consume.do_produce() + 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) - # Post Inventory of production order. - mo_custom_laptop.post_inventory() - -# raw_moves_state = any(move.state != 'done' for move in mo_custom_laptop.move_raw_ids) -# finsh_moves_state = any(move.state != 'done' for move in mo_custom_laptop.move_finished_ids) -# self.assertFalse(raw_moves_state, "Wrong state in consumed moves of production order.") -# self.assertFalse(finsh_moves_state, "Wrong state in consumed moves of production order.") -# -# # Finished move quants of production order -# -# finshed_quant_lot_001 = mo_custom_laptop.move_finished_ids.filtered(lambda x: x.product_id.id == custom_laptop.id and x.product_uom_qty==6).mapped('quant_ids') -# finshed_quant_lot_002 = mo_custom_laptop.move_finished_ids.filtered(lambda x: x.product_id.id == custom_laptop.id and x.product_uom_qty==4).mapped('quant_ids') -# -# # Check total quantity consumed of charger, keybord -# # -------------------------------------------------- -# charger_quants = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_charger.id and x.state == 'done').mapped('quant_ids') -# keybord_moves = mo_custom_laptop.move_raw_ids.filtered(lambda x: x.product_id.id == product_keybord.id and x.state == 'done').mapped('quant_ids') -# self.assertEqual(sum(charger_quants.mapped('qty')), 20) -# self.assertEqual(sum(keybord_moves.mapped('qty')), 20) - - def test_02_different_uom_on_bomlines(self): - """ Testing bill of material with different unit of measure.""" - route_manufacture = self.warehouse.manufacture_pull_id.route_id.id - route_mto = self.warehouse.mto_pull_id.route_id.id - unit = self.ref("uom.product_uom_unit") - dozen = self.ref("uom.product_uom_dozen") - kg = self.ref("uom.product_uom_kgm") - gm = self.ref("uom.product_uom_gram") - # Create Product A, B, C - product_A = self.env['product.product'].create({ - 'name': 'Product A', - 'type': 'product', - 'tracking': 'lot', - 'uom_id': dozen, - 'uom_po_id': dozen, - 'route_ids': [(6, 0, [route_manufacture, route_mto])]}) - product_B = self.env['product.product'].create({ - 'name': 'Product B', - 'type': 'product', - 'tracking': 'lot', - 'uom_id': dozen, - 'uom_po_id': dozen}) - product_C = self.env['product.product'].create({ - 'name': 'Product C', - 'type': 'product', - 'tracking': 'lot', - 'uom_id': kg, - 'uom_po_id': kg}) - - # Bill of materials - # ----------------- - - #=================================== - # Product A 1 Unit - # Product B 4 Unit - # Product C 600 gram - # ----------------------------------- - - bom_a = self.env['mrp.bom'].create({ - 'product_tmpl_id': product_A.product_tmpl_id.id, - 'product_qty': 2, - 'product_uom_id': unit, - 'bom_line_ids': [(0, 0, { - 'product_id': product_B.id, - 'product_qty': 4, - 'product_uom_id': unit - }), (0, 0, { - 'product_id': product_C.id, - 'product_qty': 600, - 'product_uom_id': gm - })] - }) - - # Create production order with product A 10 Unit. - # ----------------------------------------------- - - mo_custom_product_form = Form(self.env['mrp.production']) - mo_custom_product_form.product_id = product_A - mo_custom_product_form.bom_id = bom_a - mo_custom_product_form.product_qty = 10.0 - mo_custom_product_form.product_uom_id = self.env.ref("uom.product_uom_unit") - mo_custom_product = mo_custom_product_form.save() - - move_product_b = mo_custom_product.move_raw_ids.filtered(lambda x: x.product_id == product_B) - move_product_c = mo_custom_product.move_raw_ids.filtered(lambda x: x.product_id == product_C) - - # Check move correctly created or not. - self.assertEqual(move_product_b.product_uom_qty, 20) - self.assertEqual(move_product_b.product_uom.id, unit) - self.assertEqual(move_product_c.product_uom_qty, 3000) - self.assertEqual(move_product_c.product_uom.id, gm) - - # Lot create for product B and product C - # --------------------------------------- - lot_a = self.env['stock.production.lot'].create({'product_id': product_A.id, 'company_id': self.env.company.id}) - lot_b = self.env['stock.production.lot'].create({'product_id': product_B.id, 'company_id': self.env.company.id}) - lot_c = self.env['stock.production.lot'].create({'product_id': product_C.id, 'company_id': self.env.company.id}) - - # Inventory Update - # ---------------- - inventory = self.env['stock.inventory'].create({ - 'name': 'Inventory Product B and C', - 'line_ids': [(0, 0, { - 'product_id': product_B.id, - 'product_uom_id': product_B.uom_id.id, - 'product_qty': 3, - 'prod_lot_id': lot_b.id, - 'location_id': self.source_location_id - }), (0, 0, { - 'product_id': product_C.id, - 'product_uom_id': product_C.uom_id.id, - 'product_qty': 3, - 'prod_lot_id': lot_c.id, - 'location_id': self.source_location_id - })] - }) - inventory.action_start() - inventory.action_validate() - - # Start Production ... - # -------------------- - - mo_custom_product.action_confirm() - mo_custom_product.action_assign() - context = {"active_ids": [mo_custom_product.id], "active_id": mo_custom_product.id} - produce_form = Form(self.env['mrp.product.produce'].with_context(context)) - produce_form.qty_producing = 10.00 - produce_form.finished_lot_id = lot_a - product_consume = produce_form.save() - # laptop_lot_002 = self.env['stock.production.lot'].create({'product_id': custom_laptop.id}) - self.assertEqual(len(product_consume._workorder_line_ids()), 2) - product_consume._workorder_line_ids().filtered(lambda x: x.product_id == product_C).write({'qty_done': 3000}) - product_consume._workorder_line_ids().filtered(lambda x: x.product_id == product_B).write({'qty_done': 20}) - product_consume.do_produce() - mo_custom_product.post_inventory() - - # Check correct quant linked with move or not - # ------------------------------------------- - #TODO: check original quants qtys diminished -# self.assertEqual(len(move_product_b.quant_ids), 1) -# self.assertEqual(len(move_product_c.quant_ids), 1) -# self.assertEqual(move_product_b.quant_ids.qty, move_product_b.product_qty) -# self.assertEqual(move_product_c.quant_ids.qty, 3) -# self.assertEqual(move_product_c.quant_ids.product_uom_id.id, kg) - def test_03_test_serial_number_defaults(self): """ Test that the correct serial number is suggested on consecutive work orders. """ laptop = self.laptop @@ -1311,6 +1166,18 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): 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, + })] + }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml index 4fba0d6b30f..3b9159cd177 100644 --- a/addons/mrp/views/mrp_production_views.xml +++ b/addons/mrp/views/mrp_production_views.xml @@ -14,6 +14,7 @@ + @@ -52,8 +53,6 @@