[REF] mrp: no more produce wizard
Use a view similar as the pickings one. task-2241471
This commit is contained in:
committed by
Simon Lejeune
parent
1b6421285c
commit
0e2765f5fd
@@ -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',
|
||||
|
||||
@@ -684,29 +684,18 @@
|
||||
<value eval="[ref('mrp.mrp_production_laptop_cust')]"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.production" name="write">
|
||||
<value eval="[ref('mrp.mrp_production_laptop_cust')]"/>
|
||||
<value eval="{'qty_producing': 5, 'lot_producing_id': ref('mrp.lot_product_27_0')}"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.production" name="action_assign">
|
||||
<value eval="[ref('mrp.mrp_production_laptop_cust')]"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.product.produce" name="create">
|
||||
<value model="mrp.product.produce" eval="dict(
|
||||
obj().with_context(active_id=ref('mrp.mrp_production_laptop_cust')).default_get(list(obj().fields_get())),
|
||||
**{
|
||||
'qty_producing': obj().env.ref('mrp.mrp_production_laptop_cust').product_qty,
|
||||
'finished_lot_id': ref('mrp.lot_product_27_0'),
|
||||
}
|
||||
)"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.product.produce.line" name="create">
|
||||
<value model="mrp.product.produce.line" eval="[dict(item,
|
||||
**{
|
||||
'raw_product_produce_id': obj().env['mrp.product.produce'].search([('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)]).id
|
||||
}) for item in obj().env['mrp.product.produce'].search([('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)])._update_workorder_lines()['to_create']]"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.product.produce" name="do_produce">
|
||||
<value model="mrp.product.produce" search="[('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)]"/>
|
||||
<function model="stock.move" name="write">
|
||||
<value model="stock.move" eval="obj().env['stock.move'].search([('raw_material_production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)]).ids"/>
|
||||
<value eval="{'quantity_done': 5}"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.production" name="post_inventory">
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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 = $('<span class="o_should_consume"/>');
|
||||
$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,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -5,4 +5,7 @@
|
||||
height: 30rem;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_should_consume{
|
||||
padding-left: 0.3em;
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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']))
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+195
-479
@@ -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({
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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')))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<field name="date_deadline" widget="remaining_days" attrs="{'invisible': [('state', 'in', ['done', 'cancel'])]}" optional="hide"/>
|
||||
<field name="product_id" readonly="1" optional="show"/>
|
||||
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}" groups="uom.group_uom" optional="show"/>
|
||||
<field name="lot_producing_id" optional="hide"/>
|
||||
<field name="bom_id" readonly="1" optional="hide"/>
|
||||
<field name="origin" optional="show"/>
|
||||
<field name="user_id" optional="hide" widget="many2one_avatar_user"/>
|
||||
@@ -52,8 +53,6 @@
|
||||
<button name="action_assign" attrs="{'invisible': ['|', ('state', 'in', ('draft', 'done', 'cancel')), ('reserve_visible', '=', False)]}" string="Check availability" type="object" class="oe_highlight"/>
|
||||
<button name="button_plan" attrs="{'invisible': ['|', '|', ('state', 'not in', ('confirmed', 'progress')), ('workorder_ids', '=', []), ('is_planned', '=', True)]}" type="object" string="Plan" class="oe_highlight"/>
|
||||
<button name="button_unplan" type="object" string="Unplan" attrs="{'invisible': ['|', '|', ('state', '!=', 'planned'), ('date_planned_start', '=', False), ('date_planned_finished', '=', False)]}"/>
|
||||
<button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', ('state', '=', 'to_close'), ('is_locked', '=', False), ('reservation_state', '!=', 'assigned'), ('bom_has_operations', '!=', False)]}" string="Produce" type="object" class="oe_highlight"/>
|
||||
<button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', ('state', '=', 'to_close'), ('is_locked', '=', False), ('reservation_state', 'not in', ('confirmed', 'waiting')), ('bom_has_operations', '!=', False)]}" string="Produce" type="object"/>
|
||||
<button name="post_inventory" string="Post Inventory" type="object" attrs="{'invisible': [('post_visible', '=', False)]}"/>
|
||||
<button name="button_scrap" type="object" string="Scrap" attrs="{'invisible': ['|', ('state', 'in', ('cancel', 'draft')), ('is_locked', '=', False)]}"/>
|
||||
<button name="button_unreserve" type="object" string="Unreserve" attrs="{'invisible': [('unreserve_visible', '=', False)]}"/>
|
||||
@@ -120,20 +119,27 @@
|
||||
<group>
|
||||
<field name="id" invisible="1"/>
|
||||
<field name="allowed_product_ids" invisible="1"/>
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
<field name="product_id" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="product_tmpl_id" invisible="1"/>
|
||||
<field name="product_description_variants" attrs="{'invisible': [('product_description_variants', '=', False)], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<label for="product_qty"/>
|
||||
<field name="product_description_variants" attrs="{'invisible': [('product_description_variants', 'in', (False, ''))], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<label for="product_qty" string="Quantity"/>
|
||||
<div class="o_row no-gutters d-flex">
|
||||
<div class="col">
|
||||
<field name="product_qty" class="mr-1" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="product_uom_category_id" invisible="1"/>
|
||||
<field name="product_uom_id" options="{'no_open':True,'no_create':True}" force_save="1" groups="uom.group_uom" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<button type="action"
|
||||
name="%(mrp.action_change_production_qty)d"
|
||||
context="{'default_mo_id': id}"
|
||||
string="Update" class="oe_link pt-0" attrs="{'invisible': ['|', '|', ('state', 'in', ('draft', 'done','cancel')), ('id', '=', False), ('bom_id', '=', False)]}"/>
|
||||
<div attrs="{'invisible': [('state', '=', 'draft')]}" class="o_row">
|
||||
<field name="qty_producing" digits="[12,3]" class="text-left"/>
|
||||
/
|
||||
</div>
|
||||
<field name="product_qty" class="oe_inline text-left" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<label for="product_uom_id" string="" class="oe_inline"/>
|
||||
<field name="product_uom_category_id" invisible="1"/>
|
||||
<field name="product_uom_id" options="{'no_open': True, 'no_create': True}" force_save="1" groups="uom.group_uom" attrs="{'readonly': [('state', '!=', 'draft')]}" class="oe_inline"/>
|
||||
<span class='text-bf'>To Produce</span>
|
||||
</div>
|
||||
<label for="lot_producing_id" attrs="{'invisible': [('product_tracking', 'in', ('none', False))]}"/>
|
||||
<div class="o_row">
|
||||
<field name="lot_producing_id"
|
||||
context="{'default_product_id': product_id, 'default_company_id': company_id}" attrs="{'invisible': [('product_tracking', 'in', ('none', False))]}"/>
|
||||
<button name="action_generate_serial" type="object" class="btn btn-primary fa fa-plus-square-o" aria-label="Creates a new serial/lot number" title="Creates a new serial/lot number" role="img" attrs="{'invisible': ['|', ('product_tracking', 'in', ('none', False)), ('lot_producing_id', '!=', False)]}"/>
|
||||
</div>
|
||||
<field name="bom_id"
|
||||
context="{'default_product_tmpl_id': product_tmpl_id}" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
@@ -193,26 +199,115 @@
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Components" name="components">
|
||||
<field name="move_raw_ids" context="{'final_lots': show_final_lots, 'tree_view_ref': 'mrp.view_stock_move_raw_tree', 'form_view_ref': 'mrp.view_stock_move_lots', 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id}" attrs="{'readonly': ['&', '&', ('state', '!=', 'draft'), ('is_locked', '=', True), '|', ('state', '!=', 'confirmed'), ('consumption', '=', 'strict')]}"/>
|
||||
</page>
|
||||
<page string="Finished Products" name="finished_products">
|
||||
<field name="move_finished_ids" invisible="1"/>
|
||||
<field name="finished_move_line_ids" context="{'form_view_ref': 'mrp.view_finisehd_move_line'}" attrs="{'readonly': [('is_locked', '=', True)], 'invisible': [('finished_move_line_ids', '=', [])]}">
|
||||
<tree default_order="done_move" editable="bottom" create="0" delete="0" decoration-muted="state in ('done', 'cancel')">
|
||||
<field name="product_id" readonly="1"/>
|
||||
<<<<<<< HEAD
|
||||
<field name="move_raw_ids" context="{'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}">
|
||||
=======
|
||||
<field name="move_raw_ids"
|
||||
context="{'default_date_expected': date_planned_start, 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}"
|
||||
attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}">
|
||||
>>>>>>> dd9359b6fbe... fixup! [REF] mrp: no more produce wizard
|
||||
<tree delete="0" default_order="is_done,sequence" editable="bottom">
|
||||
<field name="product_id" required="1" attrs="{'readonly': ['|', '|', ('has_move_lines', '=', True), ('state', '=', 'cancel'), '&', ('state', '!=', 'draft'), ('additional', '=', False) ]}"/>
|
||||
|
||||
<field name="move_line_ids" invisible="1">
|
||||
<tree>
|
||||
<field name="lot_id" invisible="1"/>
|
||||
<field name="owner_id" invisible="1"/>
|
||||
<field name="package_id" invisible="1"/>
|
||||
<field name="result_package_id" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="location_dest_id" invisible="1"/>
|
||||
<field name="qty_done" invisible="1"/>
|
||||
<field name="product_id" invisible="1"/>
|
||||
<field name="product_uom_id" invisible="1"/>
|
||||
<field name="product_uom_qty" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="move_id" invisible="1"/>
|
||||
<field name="id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="lot_id" groups="stock.group_production_lot" context="{'default_product_id': product_id}" attrs="{'invisible': [('lots_visible', '=', False)]}"/>
|
||||
<field name="product_uom_id" groups="uom.group_uom"/>
|
||||
<field name="qty_done" string="Produced"/>
|
||||
<field name="lots_visible" invisible="1"/>
|
||||
<field name="done_move" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="product_uom_category_id" invisible="1"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="allowed_operation_ids" invisible="1"/>
|
||||
<field name="unit_factor" invisible="1"/>
|
||||
<field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft')]}" options="{'no_open': True, 'no_create': True}" groups="uom.group_uom"/>
|
||||
<field name="date" invisible="1"/>
|
||||
<field name="date_expected" invisible="1"/>
|
||||
<field name="additional" invisible="1"/>
|
||||
<field name="picking_type_id" invisible="1"/>
|
||||
<field name="has_tracking" invisible="1"/>
|
||||
<field name="operation_id" invisible="1"/>
|
||||
<field name="is_done" invisible="1"/>
|
||||
<field name="bom_line_id" invisible="1"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="warehouse_id" invisible="1"/>
|
||||
<field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/>
|
||||
<field name="state" invisible="1" force_save="1"/>
|
||||
<field name="should_consume_qty" invisible="1"/>
|
||||
<field name="product_uom_qty" widget="mrp_should_consume" string="To Consume" attrs="{'readonly': ['&', ('parent.state', '!=', 'draft'), '|', ('parent.state', 'not in', ('confirmed', 'planned', 'progress', 'to_close')), ('parent.is_locked', '=', True)]}" width="1"/>
|
||||
<field name="reserved_availability" attrs="{'invisible': [('is_done', '=', True)], 'column_invisible': [('parent.state', 'in', ('draft', 'done'))]}" string="Reserved" decoration-danger="not is_done and reserved_availability < product_uom_qty and product_uom_qty - reserved_availability > 0.0001"/>
|
||||
<field name="is_quantity_done_editable" invisible="1"/>
|
||||
<field name="quantity_done" string="Consumed"
|
||||
decoration-success="not is_done and (quantity_done - should_consume_qty == 0)"
|
||||
decoration-warning="not is_done and (quantity_done - should_consume_qty > 0.0001)"
|
||||
attrs="{'column_invisible': [('parent.state', '=', 'draft')], 'readonly': [('is_quantity_done_editable', '=', False)]}"/>
|
||||
<field name="show_details_visible" invisible="1"/>
|
||||
<button name="action_show_details" type="object" icon="fa-list" attrs="{'invisible': [('show_details_visible', '=', False)]}" options="{"warn": true}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="By-Products" name="finished_products" groups="mrp.group_mrp_byproducts">
|
||||
<field name="move_byproduct_ids" context="{'default_location_id': production_location_id, 'default_location_dest_id': location_src_id, 'default_state': 'draft', 'default_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}" attrs="{'readonly': [('state', '=', 'done'), ('is_locked', '=', True)]}">
|
||||
<tree delete="0" default_order="is_done,sequence" decoration-muted="is_done" editable="bottom">
|
||||
<field name="product_id" domain="[('id', '!=', parent.product_id)]" required="1"/>
|
||||
|
||||
<field name="move_line_ids" invisible="1">
|
||||
<tree>
|
||||
<field name="lot_id" invisible="1"/>
|
||||
<field name="owner_id" invisible="1"/>
|
||||
<field name="package_id" invisible="1"/>
|
||||
<field name="result_package_id" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="location_dest_id" invisible="1"/>
|
||||
<field name="qty_done" invisible="1"/>
|
||||
<field name="product_id" invisible="1"/>
|
||||
<field name="product_uom_id" invisible="1"/>
|
||||
<field name="product_uom_qty" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="move_id" invisible="1"/>
|
||||
<field name="id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_uom_category_id" invisible="1"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="allowed_operation_ids" invisible="1"/>
|
||||
<field name="unit_factor" invisible="1"/>
|
||||
<field name="product_uom" groups="uom.group_uom"/>
|
||||
<field name="date" invisible="1"/>
|
||||
<field name="date_expected" invisible="1"/>
|
||||
<field name="additional" invisible="1"/>
|
||||
<field name="picking_type_id" invisible="1"/>
|
||||
<field name="has_tracking" invisible="1"/>
|
||||
<field name="operation_id" invisible="1"/>
|
||||
<field name="is_done" invisible="1"/>
|
||||
<field name="bom_line_id" invisible="1"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="warehouse_id" invisible="1"/>
|
||||
<field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/>
|
||||
<field name="state" invisible="1" force_save="1"/>
|
||||
<field name="product_uom_qty" string="To Produce" attrs="{'readonly': ['&', ('parent.state', '!=', 'draft'), '|', ('parent.state', 'not in', ('confirmed', 'planned', 'progress', 'to_close')), ('parent.is_locked', '=', True)]}"/>
|
||||
<field name="is_quantity_done_editable" invisible="1"/>
|
||||
<field name="quantity_done" string="Produced" attrs="{'column_invisible': [('parent.state', '=', 'draft')], 'readonly': [('is_quantity_done_editable', '=', False)]}"/>
|
||||
<field name="show_details_visible" invisible="1"/>
|
||||
<button name="action_show_details" type="object" icon="fa-list" attrs="{'invisible': [('show_details_visible', '=', False)]}" options="{"warn": true}"/>
|
||||
</tree>
|
||||
</field>
|
||||
<p attrs="{'invisible': [('finished_move_line_ids', '!=', [])]}">
|
||||
Use the Produce button or process the work orders to create some finished products.
|
||||
</p>
|
||||
</page>
|
||||
<page string="Miscellaneous" name="miscellaneous" groups="stock.group_stock_multi_locations">
|
||||
<group>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<script type="text/javascript" src="/mrp/static/src/js/mrp_documents_kanban_record.js"></script>
|
||||
<script type="text/javascript" src="/mrp/static/src/js/mrp_documents_kanban_renderer.js"></script>
|
||||
<script type="text/javascript" src="/mrp/static/src/js/mrp_document_kanban_view.js"></script>
|
||||
<script type="text/javascript" src="/mrp/static/src/js/mrp_should_consume.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,142 +8,35 @@
|
||||
<field name="domain">['|', ('move_id.raw_material_production_id', '=', active_id), ('move_id.production_id', '=', active_id)]</field>
|
||||
</record>
|
||||
|
||||
<record id="view_stock_move_lots" model="ir.ui.view">
|
||||
<field name="name">stock.move.lots.form</field>
|
||||
<record id="view_stock_move_operations_raw" model="ir.ui.view">
|
||||
<field name="name">stock.move.operations.raw.form</field>
|
||||
<field name="model">stock.move</field>
|
||||
<field name="priority">1000</field>
|
||||
<field name="priority">1</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="inherit_id" ref="stock.view_stock_move_operations" />
|
||||
<field name="arch" type="xml">
|
||||
<form string="Lots">
|
||||
<field name="state" invisible="1" force_save="1"/>
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id" attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<label for="product_uom_qty"/>
|
||||
<div class="o_row">
|
||||
<span><field name="product_uom_qty" attrs="{'readonly': ['&', ('parent.state', '!=', 'draft'), '|', ('parent.state', '!=', 'confirmed'), ('parent.consumption', '=', 'strict')]}" nolabel="1"/></span>
|
||||
<span>
|
||||
<field name="product_uom" attrs="{'readonly': [('id', '!=', False)]}" force_save="1" nolabel="1" groups="uom.group_uom"/>
|
||||
</span>
|
||||
</div>
|
||||
<label for="quantity_done" attrs="{'invisible': ['|', ('parent.state', '=', 'draft'), ('id', '=', False)]}"/>
|
||||
<div class="o_row" attrs="{'invisible': ['|', ('parent.state', '=', 'draft'), ('id', '=', False)]}">
|
||||
<span><field name="quantity_done" attrs="{'readonly': ['|', '|', ('move_line_ids', '!=', False), ('is_locked', '=', True), '|', ('finished_lots_exist', '=', True), ('has_tracking', '!=', 'none')]}" nolabel="1"/></span>
|
||||
<span> / </span>
|
||||
<span><field name="reserved_availability" nolabel="1"/></span>
|
||||
</div>
|
||||
<field name="operation_id" attrs="{'invisible': [('id', '!=', False)]}" groups="mrp.group_mrp_routings"/>
|
||||
<field name="allowed_operation_ids" invisible="1"/>
|
||||
<field name="unit_factor" invisible="1"/>
|
||||
<field name="additional" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="is_done" invisible="1"/>
|
||||
<field name="workorder_id" invisible="1"/>
|
||||
<field name="bom_line_id" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="location_dest_id" invisible="1"/>
|
||||
<field name="picking_type_id" invisible="1"/>
|
||||
<field name="operation_id" invisible="1"/>
|
||||
<field name="warehouse_id" invisible="1"/>
|
||||
<field name="production_id" invisible="1"/>
|
||||
<field name="date" invisible="1"/>
|
||||
<field name="date_expected" invisible="1"/>
|
||||
<field name="raw_material_production_id" invisible="1"/>
|
||||
<field name="is_locked" invisible="1"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="has_tracking" invisible="1"/>
|
||||
<field name="order_finished_lot_ids" invisible="1"/>
|
||||
<field name="finished_lots_exist" invisible="1"/>
|
||||
<field name="product_uom_category_id" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name="move_line_ids" attrs="{'invisible': ['|', ('parent.state', '=', 'draft'), ('id', '=', False)], 'readonly': ['|', ('is_locked', '=', True), ('state', '=', 'cancel')]}" context="{'default_workorder_id': workorder_id, 'default_product_uom_id': product_uom, 'default_product_id': product_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_production_id': production_id or raw_material_production_id}">
|
||||
<tree editable="bottom" decoration-success="product_uom_qty==qty_done" decoration-danger="(product_uom_qty > 0) and (qty_done>product_uom_qty)">
|
||||
<field name="lot_id" attrs="{'column_invisible': [('parent.has_tracking', '=', 'none')]}" context="{'default_product_id': parent.product_id}"/>
|
||||
<field name="lot_produced_ids" widget="many2many_tags" options="{'no_open': True, 'no_create': True}" domain="[('id', 'in', parent.order_finished_lot_ids)]" invisible="not context.get('final_lots')"/>
|
||||
<field name="owner_id" groups="stock.group_tracking_owner" options="{'no_open': True, 'no_create': True}" optional="hide"/>
|
||||
<field name="package_id" groups="stock.group_tracking_lot" options="{'no_open': True, 'no_create': True}" optional="hide"/>
|
||||
<field name="product_uom_qty" string="Reserved" readonly="1" optional="show"/>
|
||||
<field name="qty_done"/>
|
||||
<field name="workorder_id" invisible="1"/>
|
||||
<field name="product_id" invisible="1"/>
|
||||
<field name="product_uom_id" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="location_dest_id" invisible="1"/>
|
||||
<field name="production_id" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_uom_category_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_stock_move_raw_tree" model="ir.ui.view">
|
||||
<field name="name">stock.move.raw.tree</field>
|
||||
<field name="model">stock.move</field>
|
||||
<field name="priority">1000</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree delete="0" default_order="is_done,sequence" decoration-muted="is_done" decoration-warning="quantity_done - product_uom_qty > 0.0001" decoration-success="not is_done and quantity_done - product_uom_qty < 0.0001" decoration-danger="not is_done and reserved_availability < product_uom_qty and product_uom_qty - reserved_availability > 0.0001">
|
||||
<field name="product_id" required="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_uom_category_id" invisible="1"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="unit_factor" invisible="1"/>
|
||||
<field name="product_uom" groups="uom.group_uom"/>
|
||||
<field name="date" invisible="1"/>
|
||||
<field name="date_expected" invisible="1"/>
|
||||
<field name="picking_type_id" invisible="1"/>
|
||||
<field name="has_tracking" invisible="1"/>
|
||||
<field name="operation_id" invisible="1"/>
|
||||
<field name="needs_lots" readonly="1" groups="stock.group_production_lot"/>
|
||||
<field name="is_done" invisible="1"/>
|
||||
<field name="bom_line_id" invisible="1"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="warehouse_id" invisible="1"/>
|
||||
<field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/>
|
||||
<field name="state" invisible="1" force_save="1"/>
|
||||
<field name="product_uom_qty" string="To Consume"/>
|
||||
<field name="reserved_availability" attrs="{'invisible': [('is_done', '=', True)], 'column_invisible': [('parent.state', 'in', ('draft', 'done'))]}" string="Reserved"/>
|
||||
<field name="quantity_done" string="Consumed" attrs="{'column_invisible': [('parent.state', '=', 'draft')]}" readonly="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_move_kanban_inherit_mrp" model="ir.ui.view">
|
||||
<field name="name">stock.move.kanban.inherit.mrp</field>
|
||||
<field name="model">stock.move</field>
|
||||
<field name="inherit_id" ref="stock.view_move_kandan"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//templates" position="before">
|
||||
<field name="bom_line_id"/>
|
||||
<xpath expr="//label[@for='product_uom_qty']" position="attributes">
|
||||
<attribute name="string">Total To Consume</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//label[@for='quantity_done']" position="attributes">
|
||||
<attribute name="string">Consumed</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_finisehd_move_line" model="ir.ui.view">
|
||||
<field name="name">mrp.finished.move.line.form</field>
|
||||
<field name="priority">1000</field>
|
||||
<field name="model">stock.move.line</field>
|
||||
<record id="view_stock_move_operations_finished" model="ir.ui.view">
|
||||
<field name="name">stock.move.operations.finished.form</field>
|
||||
<field name="model">stock.move</field>
|
||||
<field name="priority">1</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="inherit_id" ref="stock.view_stock_move_operations" />
|
||||
<field name="arch" type="xml">
|
||||
<form string="Finished Product">
|
||||
<group>
|
||||
<group>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="product_uom_category_id" readonly="1"/>
|
||||
<label for="qty_done" string ="Quantity"/>
|
||||
<div class="o_row">
|
||||
<span><field name="qty_done" readonly="1" nolabel="1"/></span>
|
||||
<span>/</span>
|
||||
<span><field name="product_uom_qty" readonly="1" nolabel="1"/></span>
|
||||
<span><field name="product_uom_id" attrs="{'readonly': [('id', '!=', False)]}" nolabel="1"/></span>
|
||||
</div>
|
||||
<field name="lot_id" string="Lot/Serial number" groups="stock.group_production_lot" readonly="1" attrs="{'invisible': [('lots_visible', '=', False)]}"/>
|
||||
<field name="lots_visible" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
<xpath expr="//label[@for='product_uom_qty']" position="attributes">
|
||||
<attribute name="string">To Produce</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//label[@for='quantity_done']" position="attributes">
|
||||
<attribute name="string">Produced</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mrp_product_produce
|
||||
from . import change_production_qty
|
||||
from . import stock_warn_insufficient_qty
|
||||
from . import mrp_production_backorder
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import float_compare
|
||||
|
||||
|
||||
class MrpProductProduce(models.TransientModel):
|
||||
_name = "mrp.product.produce"
|
||||
_description = "Record Production"
|
||||
_inherit = ["mrp.abstract.workorder"]
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(MrpProductProduce, self).default_get(fields)
|
||||
production = self.env['mrp.production']
|
||||
production_id = self.env.context.get('default_production_id') or self.env.context.get('active_id')
|
||||
if production_id:
|
||||
production = self.env['mrp.production'].browse(production_id)
|
||||
if production.exists():
|
||||
serial_finished = (production.product_id.tracking == 'serial')
|
||||
todo_uom = production.product_uom_id.id
|
||||
todo_quantity = self._get_todo(production)
|
||||
if serial_finished:
|
||||
todo_quantity = 1.0
|
||||
if production.product_uom_id.uom_type != 'reference':
|
||||
todo_uom = self.env['uom.uom'].search([('category_id', '=', production.product_uom_id.category_id.id), ('uom_type', '=', 'reference')]).id
|
||||
if 'production_id' in fields:
|
||||
res['production_id'] = production.id
|
||||
if 'product_id' in fields:
|
||||
res['product_id'] = production.product_id.id
|
||||
if 'product_uom_id' in fields:
|
||||
res['product_uom_id'] = todo_uom
|
||||
if 'serial' in fields:
|
||||
res['serial'] = bool(serial_finished)
|
||||
if 'qty_producing' in fields:
|
||||
res['qty_producing'] = todo_quantity
|
||||
if 'consumption' in fields:
|
||||
res['consumption'] = production.bom_id.consumption if production.bom_id else 'flexible'
|
||||
return res
|
||||
|
||||
serial = fields.Boolean('Requires Serial')
|
||||
product_tracking = fields.Selection(related="product_id.tracking")
|
||||
is_pending_production = fields.Boolean(compute='_compute_pending_production')
|
||||
|
||||
move_raw_ids = fields.One2many(related='production_id.move_raw_ids', string="PO Components")
|
||||
move_finished_ids = fields.One2many(related='production_id.move_finished_ids')
|
||||
|
||||
raw_workorder_line_ids = fields.One2many('mrp.product.produce.line',
|
||||
'raw_product_produce_id', string='Components')
|
||||
finished_workorder_line_ids = fields.One2many('mrp.product.produce.line',
|
||||
'finished_product_produce_id', string='By-products')
|
||||
production_id = fields.Many2one('mrp.production', 'Manufacturing Order',
|
||||
required=True, ondelete='cascade')
|
||||
|
||||
@api.depends('qty_producing')
|
||||
def _compute_pending_production(self):
|
||||
""" Compute if it exits remaining quantity once the quantity on the
|
||||
current wizard will be processed. The purpose is to display or not
|
||||
button 'continue'.
|
||||
"""
|
||||
for product_produce in self:
|
||||
remaining_qty = product_produce._get_todo(product_produce.production_id)
|
||||
product_produce.is_pending_production = remaining_qty - product_produce.qty_producing > 0.0
|
||||
|
||||
def continue_production(self):
|
||||
""" Save current wizard and directly opens a new. """
|
||||
self.ensure_one()
|
||||
self._record_production()
|
||||
action = self.production_id.open_produce_product()
|
||||
action['context'] = {'default_production_id': self.production_id.id}
|
||||
return action
|
||||
|
||||
def action_generate_serial(self):
|
||||
self.ensure_one()
|
||||
product_produce_wiz = self.env.ref('mrp.view_mrp_product_produce_wizard', False)
|
||||
self.finished_lot_id = self.env['stock.production.lot'].create({
|
||||
'product_id': self.product_id.id,
|
||||
'company_id': self.production_id.company_id.id
|
||||
})
|
||||
return {
|
||||
'name': _('Produce'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mrp.product.produce',
|
||||
'res_id': self.id,
|
||||
'view_id': product_produce_wiz.id,
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
def do_produce(self):
|
||||
""" Save the current wizard and go back to the MO. """
|
||||
self.ensure_one()
|
||||
self._record_production()
|
||||
self._check_company()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def _get_todo(self, production):
|
||||
""" This method will return remaining todo quantity of production. """
|
||||
main_product_moves = production.move_finished_ids.filtered(lambda x: x.product_id.id == production.product_id.id)
|
||||
todo_quantity = production.product_qty - sum(main_product_moves.mapped('quantity_done'))
|
||||
todo_quantity = todo_quantity if (todo_quantity > 0) else 0
|
||||
return todo_quantity
|
||||
|
||||
def _record_production(self):
|
||||
# Check all the product_produce line have a move id (the user can add product
|
||||
# to consume directly in the wizard)
|
||||
for line in self._workorder_line_ids():
|
||||
line._check_line_sn_uniqueness()
|
||||
# because of an ORM limitation (fields on transient models are not
|
||||
# recomputed by updates in non-transient models), the related fields on
|
||||
# this model are not recomputed by the creations above
|
||||
self.invalidate_cache(['move_raw_ids', 'move_finished_ids'])
|
||||
|
||||
# Save product produce lines data into stock moves/move lines
|
||||
quantity = self.qty_producing
|
||||
if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
|
||||
raise UserError(_("The production order for '%s' has no quantity specified.") % self.product_id.display_name)
|
||||
|
||||
self._check_sn_uniqueness()
|
||||
self._update_finished_move()
|
||||
self._update_moves()
|
||||
if self.production_id.state == 'confirmed':
|
||||
self.production_id.write({
|
||||
'date_start': datetime.now(),
|
||||
})
|
||||
|
||||
|
||||
class MrpProductProduceLine(models.TransientModel):
|
||||
_name = 'mrp.product.produce.line'
|
||||
_inherit = ["mrp.abstract.workorder.line"]
|
||||
_description = "Record production line"
|
||||
|
||||
raw_product_produce_id = fields.Many2one('mrp.product.produce', 'Component in Produce wizard')
|
||||
finished_product_produce_id = fields.Many2one('mrp.product.produce', 'Finished Product in Produce wizard')
|
||||
|
||||
@api.model
|
||||
def _get_raw_workorder_inverse_name(self):
|
||||
return 'raw_product_produce_id'
|
||||
|
||||
@api.model
|
||||
def _get_finished_workoder_inverse_name(self):
|
||||
return 'finished_product_produce_id'
|
||||
|
||||
def _get_final_lots(self):
|
||||
product_produce_id = self.raw_product_produce_id or self.finished_product_produce_id
|
||||
return product_produce_id.finished_lot_id | product_produce_id.finished_workorder_line_ids.mapped('lot_id')
|
||||
|
||||
def _get_production(self):
|
||||
product_produce_id = self.raw_product_produce_id or self.finished_product_produce_id
|
||||
return product_produce_id.production_id
|
||||
@@ -1,134 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_mrp_product_produce_wizard" model="ir.ui.view">
|
||||
<field name="name">MRP Product Produce</field>
|
||||
<field name="model">mrp.product.produce</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Produce">
|
||||
<group>
|
||||
<group>
|
||||
<field name="serial" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="production_id" invisible="1"/>
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="is_pending_production" invisible="1"/>
|
||||
<label for="qty_producing" string="Quantity"/>
|
||||
<div class="o_row">
|
||||
<field name="qty_producing" attrs="{'readonly': [('serial', '=', True)]}"/>
|
||||
<field name="product_uom_id" readonly="1" groups="uom.group_uom"/>
|
||||
</div>
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
<label for="finished_lot_id" attrs="{'invisible': [('product_tracking', '=', 'none')]}"/>
|
||||
<div class="o_row">
|
||||
<field name="finished_lot_id" attrs="{'invisible': [('product_tracking', '=', 'none')], 'required': [('product_tracking', '!=', 'none'), ('finished_lot_id', '!=', False)]}" context="{'default_product_id': product_id, 'default_company_id': company_id}"/>
|
||||
<button name="action_generate_serial" type="object" class="btn btn-primary fa fa-plus-square-o" aria-label="Creates a new serial/lot number" title="Creates a new serial/lot number" role="img" attrs="{'invisible': ['|', ('product_tracking', '=', 'none'), ('finished_lot_id', '!=', False)]}"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<h4 attrs="{'invisible': [('raw_workorder_line_ids', '=', [])]}">Components</h4>
|
||||
<group>
|
||||
<field name="raw_workorder_line_ids" attrs="{'invisible': [('raw_workorder_line_ids', '=', [])]}" nolabel="1" context="{'w_production': True, 'active_id': production_id, 'default_finished_lot_id': finished_lot_id}">
|
||||
<tree editable="bottom" delete="0" decoration-danger="(qty_to_consume < qty_done)">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_id" attrs="{'readonly': [('move_id', '!=', False)]}" required="1" domain="[('id', '!=', parent.product_id), ('type', 'in', ['product', 'consu']), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]" force_save="1"/>
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
<field name="lot_id" attrs="{'readonly': [('product_tracking', '=', 'none')]}" context="{'default_product_id': product_id, 'active_mo_id': parent.production_id, 'default_company_id': parent.company_id}" groups="stock.group_production_lot"/>
|
||||
<field name="qty_to_consume" readonly="1" force_save="1"/>
|
||||
<field name="qty_reserved" readonly="1" force_save="1" optional="show"/>
|
||||
<field name="qty_done"/>
|
||||
<field name="product_uom_id" readonly="1" force_save="1" groups="uom.group_uom"/>
|
||||
<field name="move_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<h4 attrs="{'invisible': [('finished_workorder_line_ids', '=', [])]}">By-products</h4>
|
||||
<group>
|
||||
<field name="finished_workorder_line_ids" attrs="{'invisible': [('finished_workorder_line_ids', '=', [])]}" nolabel="1" context="{'w_production': True, 'active_id': production_id, 'default_finished_lot_id': finished_lot_id}">
|
||||
<tree editable="bottom" delete="0" decoration-danger="(qty_to_consume < qty_done)">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_id" attrs="{'readonly': [('move_id', '!=', False)]}" required="1" domain="[('id', '!=', parent.product_id), ('type', 'in', ['product', 'consu']), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]" force_save="1"/>
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
<field name="lot_id" attrs="{'readonly': [('product_tracking', '=', 'none')]}" context="{'default_product_id': product_id, 'default_company_id': company_id}" groups="stock.group_production_lot"/>
|
||||
<field name="qty_to_consume" string="To Produce" readonly="1" force_save="1"/>
|
||||
<field name="qty_done" string="Produced"/>
|
||||
<field name="product_uom_id" readonly="1" force_save="1" groups="uom.group_uom"/>
|
||||
<field name="move_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="continue_production" attrs="{'invisible':[('is_pending_production', '=', False)]}" type="object" string="Continue" class="btn-primary"/>
|
||||
<button name="do_produce" type="object" string="Save" class="btn-default btn-secondary" attrs="{'invisible':[('is_pending_production', '=', False)]}"/>
|
||||
<button name="do_produce" type="object" string="Save" class="btn-primary" attrs="{'invisible':[('is_pending_production', '=', True)]}"/>
|
||||
<button string="Discard" class="btn-default btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_product_produce_line_form" model="ir.ui.view">
|
||||
<field name="name">MRP Product Produce Line</field>
|
||||
<field name="model">mrp.product.produce.line</field>
|
||||
<field name="priority">100</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<group>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_tracking" invisible="1"/>
|
||||
<field name="lot_id" attrs="{'readonly': [('product_tracking', '=', 'none')]}" context="{'default_product_id': product_id}" groups="stock.group_production_lot"/>
|
||||
<field name="qty_to_consume" readonly="1" force_save="1"/>
|
||||
<field name="qty_reserved" readonly="1" force_save="1" optional="show"/>
|
||||
<field name="qty_done"/>
|
||||
<field name="product_uom_id" readonly="1" force_save="1" groups="uom.group_uom"/>
|
||||
<field name="move_id" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_product_produce_line_kanban" model="ir.ui.view">
|
||||
<field name="name">MRP Product Produce Line</field>
|
||||
<field name="model">mrp.product.produce.line</field>
|
||||
<field name="priority">100</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="product_id"/>
|
||||
<field name="lot_id"/>
|
||||
<field name="qty_to_consume"/>
|
||||
<field name="qty_reserved"/>
|
||||
<field name="qty_done"/>
|
||||
<field name="product_uom_id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_global_click">
|
||||
<div class="o_kanban_record_top">
|
||||
<div class="o_kanban_record_headings">
|
||||
<strong class="o_kanban_record_title"><span><field name="product_id"/></span></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_kanban_record_body">
|
||||
<span>Lot <field name="lot_id"/></span><br/>
|
||||
<span>Quantity To Consume <field name="qty_to_consume"/></span><br/>
|
||||
<span>Quantity Reserved <field name="qty_reserved"/></span><br/>
|
||||
<span>Quantity Done <field name="qty_done"/></span><br/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="act_mrp_product_produce" model="ir.actions.act_window">
|
||||
<field name="name">Produce</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">mrp.product.produce</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -75,13 +75,9 @@ class TestMrpAccount(TestWorkOrderProcessCommon):
|
||||
production_table.extra_cost = 20
|
||||
production_table.action_confirm()
|
||||
|
||||
produce_form = Form(self.env['mrp.product.produce'].with_context({
|
||||
'active_id': production_table.id,
|
||||
'active_ids': [production_table.id],
|
||||
}))
|
||||
produce_form.qty_producing = 1.0
|
||||
produce_wizard = produce_form.save()
|
||||
produce_wizard.do_produce()
|
||||
mo_form = Form(production_table)
|
||||
mo_form.qty_producing = 1
|
||||
production_table = mo_form.save()
|
||||
production_table.post_inventory()
|
||||
move_value = production_table.move_finished_ids.filtered(lambda x: x.state == "done").stock_valuation_layer_ids.value
|
||||
|
||||
|
||||
@@ -39,14 +39,11 @@ class TestMrpValuationCommon(TestStockValuationCommon):
|
||||
return mo
|
||||
|
||||
def _produce(self, mo, quantity=0):
|
||||
produce_form = Form(self.env['mrp.product.produce'].with_context({
|
||||
'active_id': mo.id,
|
||||
'active_ids': [mo.id],
|
||||
}))
|
||||
if quantity:
|
||||
produce_form.qty_producing = quantity
|
||||
product_produce = produce_form.save()
|
||||
product_produce.do_produce()
|
||||
mo_form = Form(mo)
|
||||
if not quantity:
|
||||
quantity = mo.product_qty - mo.qty_produced
|
||||
mo_form.qty_producing += quantity
|
||||
mo = mo_form.save()
|
||||
|
||||
|
||||
class TestMrpValuationStandard(TestMrpValuationCommon):
|
||||
@@ -94,7 +91,10 @@ class TestMrpValuationStandard(TestMrpValuationCommon):
|
||||
self._make_in_move(self.component, 1, 20)
|
||||
mo = self._make_mo(self.bom, 2)
|
||||
self._produce(mo, 1)
|
||||
mo.post_inventory()
|
||||
action = mo.button_mark_done()
|
||||
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
|
||||
backorder.save().action_close_mo()
|
||||
mo = mo.procurement_group_id.mrp_production_ids[-1]
|
||||
self.assertEqual(self.component.value_svl, 20)
|
||||
self.assertEqual(self.product1.value_svl, 10)
|
||||
self.assertEqual(self.component.quantity_svl, 1)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view" />
|
||||
<field name="groups_id" eval="[(4, ref('stock.group_stock_manager'))]"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='finished_move_line_ids']" position="before">
|
||||
<xpath expr="//field[@name='product_id']" position="before">
|
||||
<field name="show_valuation" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_view_mrp_production_sources']" position="after">
|
||||
|
||||
@@ -117,13 +117,10 @@ class TestStockLandedCostsMrp(StockAccountTestCommon):
|
||||
self.assertEqual(first_move.product_qty, 2.0)
|
||||
|
||||
# produce product
|
||||
produce_form = Form(self.env['mrp.product.produce'].with_user(self.allow_user).with_context({
|
||||
'active_id': man_order.id,
|
||||
'active_ids': [man_order.id],
|
||||
}))
|
||||
produce_form.qty_producing = 2.0
|
||||
produce_wizard = produce_form.save()
|
||||
produce_wizard.do_produce()
|
||||
mo_form = Form(man_order.with_user(self.allow_user))
|
||||
mo_form.qty_producing = 2
|
||||
man_order = mo_form.save()
|
||||
|
||||
|
||||
man_order.button_mark_done()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mrp_abstract_workorder
|
||||
from . import mrp_production
|
||||
from . import mrp_workorder
|
||||
|
||||
+10
-3
@@ -4,15 +4,21 @@
|
||||
from odoo import models, _
|
||||
|
||||
|
||||
class MrpAbstractWorkorder(models.AbstractModel):
|
||||
_inherit = 'mrp.abstract.workorder'
|
||||
class MrpWorkorder(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
|
||||
def _pre_button_mark_done(self):
|
||||
confirm_expired_lots = self._check_expired_lots()
|
||||
if confirm_expired_lots:
|
||||
return confirm_expired_lots
|
||||
return super()._pre_button_mark_done()
|
||||
|
||||
def _check_expired_lots(self):
|
||||
# We use the 'skip_expired' context key to avoid to make the check when
|
||||
# user already confirmed the wizard about using expired lots.
|
||||
if self.env.context.get('skip_expired'):
|
||||
return False
|
||||
expired_lot_ids = self.raw_workorder_line_ids.filtered(lambda ml: ml.lot_id.product_expiry_alert).lot_id.ids
|
||||
expired_lot_ids = self.move_raw_ids.move_line_ids.filtered(lambda ml: ml.lot_id.product_expiry_alert).lot_id.ids
|
||||
if expired_lot_ids:
|
||||
return {
|
||||
'name': _('Confirmation'),
|
||||
@@ -27,5 +33,6 @@ class MrpAbstractWorkorder(models.AbstractModel):
|
||||
context = dict(self.env.context)
|
||||
context.update({
|
||||
'default_lot_ids': [(6, 0, expired_lot_ids)],
|
||||
'default_production_ids': self.ids,
|
||||
})
|
||||
return context
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
from odoo import models, _
|
||||
|
||||
|
||||
class MrpWorkorder(models.Model):
|
||||
@@ -11,9 +11,28 @@ class MrpWorkorder(models.Model):
|
||||
confirm_expired_lots = self._check_expired_lots()
|
||||
if confirm_expired_lots:
|
||||
return confirm_expired_lots
|
||||
return super(MrpWorkorder, self).record_production()
|
||||
return super().record_production()
|
||||
|
||||
def _check_expired_lots(self):
|
||||
# We use the 'skip_expired' context key to avoid to make the check when
|
||||
# user already confirmed the wizard about using expired lots.
|
||||
if self.env.context.get('skip_expired'):
|
||||
return False
|
||||
expired_lot_ids = self.raw_workorder_line_ids.filtered(lambda ml: ml.lot_id.product_expiry_alert).lot_id.ids
|
||||
if expired_lot_ids:
|
||||
return {
|
||||
'name': _('Confirmation'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'expiry.picking.confirmation',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': self._get_expired_context(expired_lot_ids),
|
||||
}
|
||||
|
||||
def _get_expired_context(self, expired_lot_ids):
|
||||
context = super(MrpWorkorder, self)._get_expired_context(expired_lot_ids)
|
||||
context['default_workorder_id'] = self.id
|
||||
context = dict(self.env.context)
|
||||
context.update({
|
||||
'default_lot_ids': [(6, 0, expired_lot_ids)],
|
||||
'default_workorder_id': self.id,
|
||||
})
|
||||
return context
|
||||
|
||||
@@ -76,16 +76,17 @@ class TestStockProductionLot(TestStockCommon):
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
# ... and tries to product with a non-expired lot as component.
|
||||
produce_form = Form(self.env['mrp.product.produce'].with_context({
|
||||
'active_id': mo.id,
|
||||
'active_ids': [mo.id],
|
||||
}))
|
||||
with produce_form.raw_workorder_line_ids.edit(0) as line:
|
||||
line.lot_id = self.lot_good_apple
|
||||
product_produce = produce_form.save()
|
||||
res = product_produce.do_produce()
|
||||
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.new() as ml:
|
||||
ml.qty_done = 3
|
||||
ml.lot_id = self.lot_good_apple
|
||||
details_operation_form.save()
|
||||
res = mo.button_mark_done()
|
||||
# Producing must not return a wizard in this case.
|
||||
self.assertEqual(res['type'], 'ir.actions.act_window_close')
|
||||
self.assertEqual(res, True)
|
||||
|
||||
def test_02_product_produce_using_expired(self):
|
||||
""" Checks user gets a confirmation wizard when they produces with
|
||||
@@ -98,14 +99,15 @@ class TestStockProductionLot(TestStockCommon):
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
# ... and tries to product with an expired lot as component.
|
||||
produce_form = Form(self.env['mrp.product.produce'].with_context({
|
||||
'active_id': mo.id,
|
||||
'active_ids': [mo.id],
|
||||
}))
|
||||
with produce_form.raw_workorder_line_ids.edit(0) as line:
|
||||
line.lot_id = self.lot_expired_apple
|
||||
product_produce = produce_form.save()
|
||||
res = product_produce.do_produce()
|
||||
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.new() as ml:
|
||||
ml.qty_done = 3
|
||||
ml.lot_id = self.lot_expired_apple
|
||||
details_operation_form.save()
|
||||
res = mo.button_mark_done()
|
||||
# Producing must return a confirmation wizard.
|
||||
self.assertNotEqual(res, None)
|
||||
self.assertEqual(res['res_model'], 'expiry.picking.confirmation')
|
||||
@@ -178,7 +180,3 @@ class TestStockProductionLot(TestStockCommon):
|
||||
# confirmation wizard.
|
||||
self.assertNotEqual(res, None)
|
||||
self.assertEqual(res['res_model'], 'expiry.picking.confirmation')
|
||||
# FIXME: when should we get the "Work order %s is still running" error?
|
||||
# with self.assertRaises(UserError):
|
||||
# # Cannot finish the MO as the Work Order is still ongoing.
|
||||
# mo.button_mark_done()
|
||||
|
||||
@@ -2,4 +2,3 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import confirm_expiry
|
||||
from . import mrp_product_produce
|
||||
|
||||
@@ -7,12 +7,12 @@ from odoo import api, fields, models, _
|
||||
class ConfirmExpiry(models.TransientModel):
|
||||
_inherit = 'expiry.picking.confirmation'
|
||||
|
||||
produce_id = fields.Many2one('mrp.product.produce', readonly=True)
|
||||
production_ids = fields.Many2many('mrp.production', readonly=True)
|
||||
workorder_id = fields.Many2one('mrp.workorder', readonly=True)
|
||||
|
||||
@api.depends('lot_ids')
|
||||
def _compute_descriptive_fields(self):
|
||||
if self.produce_id or self.workorder_id:
|
||||
if self.production_ids or self.workorder_id:
|
||||
# Shows expired lots only if we are more than one expired lot.
|
||||
self.show_lots = len(self.lot_ids) > 1
|
||||
if self.show_lots:
|
||||
@@ -34,13 +34,8 @@ class ConfirmExpiry(models.TransientModel):
|
||||
super(ConfirmExpiry, self)._compute_descriptive_fields()
|
||||
|
||||
def confirm_produce(self):
|
||||
return self.produce_id.with_context(skip_expired=True).do_produce()
|
||||
return self.production_ids.with_context(skip_expired=True).button_mark_done()
|
||||
|
||||
def confirm_workorder(self):
|
||||
return self.workorder_id.with_context(skip_expired=True).record_production()
|
||||
|
||||
def return_to_produce_wizard(self):
|
||||
production = self.produce_id.production_id
|
||||
action = production.open_produce_product()
|
||||
action['context'] = {'default_production_id': production.id}
|
||||
return action
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='description']" position="after">
|
||||
<field name="picking_ids" invisible="1"/>
|
||||
<field name="produce_id" invisible="1"/>
|
||||
<field name="production_ids" invisible="1"/>
|
||||
<field name="workorder_id" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='process']" position="attributes">
|
||||
@@ -17,19 +17,18 @@
|
||||
<attribute name="attrs">{'invisible': [('picking_ids', '=', [])]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@special='cancel']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('produce_id', '!=', False)]}</attribute>
|
||||
<attribute name="attrs">{'invisible': [('production_ids', '!=', [])]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='process']" position="after">
|
||||
<!-- From Produce Product wizard -->
|
||||
<button name="confirm_produce"
|
||||
string="Confirm"
|
||||
type="object"
|
||||
attrs="{'invisible': [('produce_id', '=', False)]}"
|
||||
attrs="{'invisible': [('production_ids', '=', [])]}"
|
||||
class="btn-primary"/>
|
||||
<button name="return_to_produce_wizard"
|
||||
<button special="cancel"
|
||||
string="Discard"
|
||||
type="object"
|
||||
attrs="{'invisible': [('produce_id', '=', False)]}"
|
||||
attrs="{'invisible': [('production_ids', '=', [])]}"
|
||||
class="btn-secondary"/>
|
||||
<!-- From a Workorder -->
|
||||
<button name="confirm_workorder"
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class MrpProductProduce(models.TransientModel):
|
||||
_inherit = 'mrp.product.produce'
|
||||
|
||||
def do_produce(self):
|
||||
confirm_expired_lots = self._check_expired_lots()
|
||||
if confirm_expired_lots:
|
||||
return confirm_expired_lots
|
||||
return super(MrpProductProduce, self).do_produce()
|
||||
|
||||
def _get_expired_context(self, expired_lot_ids):
|
||||
context = super(MrpProductProduce, self)._get_expired_context(expired_lot_ids)
|
||||
context['default_produce_id'] = self.id
|
||||
return context
|
||||
@@ -126,12 +126,21 @@ class StockMove(models.Model):
|
||||
return res
|
||||
|
||||
def _action_record_components(self):
|
||||
action = self.env.ref('mrp.act_mrp_product_produce').read()[0]
|
||||
action['context'] = dict(
|
||||
default_production_id=self.move_orig_ids.production_id.id,
|
||||
default_subcontract_move_id=self.id
|
||||
)
|
||||
return action
|
||||
self.ensure_one()
|
||||
production = self.move_orig_ids.production_id
|
||||
view = self.env.ref('mrp.mrp_production_form_view')
|
||||
return {
|
||||
'name': _('Subcontract'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mrp.production',
|
||||
'views': [(view.id, 'form')],
|
||||
'view_id': view.id,
|
||||
'target': 'new',
|
||||
'res_id': production.id,
|
||||
'context': dict(self.env.context, subcontract_move_id=self.id),
|
||||
}
|
||||
|
||||
|
||||
def _check_overprocessed_subcontract_qty(self):
|
||||
""" If a subcontracted move use tracked components. Do not allow to add
|
||||
@@ -147,8 +156,8 @@ class StockMove(models.Model):
|
||||
if not move._has_tracked_subcontract_components():
|
||||
continue
|
||||
rounding = move.product_uom.rounding
|
||||
if float_compare(move.quantity_done, move.move_orig_ids.production_id.qty_produced, precision_rounding=rounding) > 0:
|
||||
overprocessed_moves |= move
|
||||
# if float_compare(move.quantity_done, move.move_orig_ids.production_id.qty_produced, precision_rounding=rounding) > 0:
|
||||
# overprocessed_moves |= move
|
||||
if overprocessed_moves:
|
||||
raise UserError(_("""
|
||||
You have to use 'Records Components' button in order to register quantity for a
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools import float_is_zero
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
@@ -43,7 +44,9 @@ class StockPicking(models.Model):
|
||||
for move in picking.move_lines:
|
||||
if not move.is_subcontract:
|
||||
continue
|
||||
production = move.move_orig_ids.production_id
|
||||
production = move.move_orig_ids.production_id.filtered(lambda p: p.state not in ('done', 'cancel'))
|
||||
if len(production) > 1:
|
||||
production = production[-1]
|
||||
if move._has_tracked_subcontract_components():
|
||||
move.move_orig_ids.filtered(lambda m: m.state not in ('done', 'cancel')).move_line_ids.unlink()
|
||||
move_finished_ids = move.move_orig_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
|
||||
@@ -58,16 +61,25 @@ class StockPicking(models.Model):
|
||||
'location_dest_id': move_finished_ids.location_dest_id.id,
|
||||
})
|
||||
else:
|
||||
for move_line in move.move_line_ids:
|
||||
produce = self.env['mrp.product.produce'].with_context(default_production_id=production.id).create({
|
||||
'production_id': production.id,
|
||||
'qty_producing': move_line.qty_done,
|
||||
'product_uom_id': move_line.product_uom_id.id,
|
||||
'finished_lot_id': move_line.lot_id.id,
|
||||
'consumption': 'strict',
|
||||
})
|
||||
produce._generate_produce_lines()
|
||||
produce._record_production()
|
||||
if not move.move_line_ids.lot_id:
|
||||
qty_done_production_uom = move.product_uom._compute_quantity(move.quantity_done, production.product_uom_id)
|
||||
production.qty_producing += qty_done_production_uom
|
||||
for move in (production.move_raw_ids | production.move_finished_ids.filtered(lambda m: m.product_id != production.product_id)):
|
||||
if move.state in ('done', 'cancel'):
|
||||
continue
|
||||
if float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding):
|
||||
continue
|
||||
new_qty = production.product_uom_id._compute_quantity(
|
||||
(production.qty_producing - production.qty_produced) * move.unit_factor, production.product_uom_id,
|
||||
rounding_method='HALF-UP')
|
||||
move.move_line_ids.filtered(lambda ml: ml.state not in ('done', 'cancel')).qty_done = 0
|
||||
vals = move._set_quantity_done_prepare_vals(new_qty)
|
||||
if vals['to_create']:
|
||||
for res in vals['to_create']:
|
||||
move.move_line_ids.create(res)
|
||||
if vals['to_write']:
|
||||
for move_line, res in vals['to_write']:
|
||||
move_line.write(res)
|
||||
productions |= production
|
||||
for subcontracted_production in productions:
|
||||
if subcontracted_production.state == 'progress':
|
||||
|
||||
@@ -415,7 +415,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.product_uom_qty = 5
|
||||
move.product_uom_qty = 3 # FIXME sle: need to handle the backorder in subcontract
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_confirm()
|
||||
mo = picking_receipt.move_lines.move_orig_ids.production_id
|
||||
@@ -442,39 +442,26 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
'product_id': self.finished.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
mo_form = Form(picking_receipt._get_subcontracted_productions().with_context(subcontract_move_id=picking_receipt.move_lines.id))
|
||||
mo_form.qty_producing = 3
|
||||
mo_form.lot_producing_id = lot_f1
|
||||
mo = mo_form.save()
|
||||
details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.new() as ml:
|
||||
ml.lot_id = lot_c1
|
||||
ml.qty_done = 3
|
||||
details_operation_form.save()
|
||||
details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.new() as ml:
|
||||
ml.lot_id = lot_c2
|
||||
ml.qty_done = 3
|
||||
details_operation_form.save()
|
||||
|
||||
register_form = Form(self.env['mrp.product.produce'].with_context(
|
||||
active_id=picking_receipt._get_subcontracted_productions().id,
|
||||
default_subcontract_move_id=picking_receipt.move_lines.id
|
||||
))
|
||||
register_form.qty_producing = 3.0
|
||||
self.assertEqual(len(register_form._values['raw_workorder_line_ids']), 2,
|
||||
'Register Components Form should contains one line per component.')
|
||||
self.assertTrue(all(p[2]['product_id'] in (self.comp1 | self.comp2).ids for p in register_form._values['raw_workorder_line_ids']),
|
||||
'Register Components Form should contains component.')
|
||||
with register_form.raw_workorder_line_ids.edit(0) as pl:
|
||||
pl.lot_id = lot_c1
|
||||
with register_form.raw_workorder_line_ids.edit(1) as pl:
|
||||
pl.lot_id = lot_c2
|
||||
register_form.finished_lot_id = lot_f1
|
||||
register_wizard = register_form.save()
|
||||
action = register_wizard.continue_production()
|
||||
register_form = Form(self.env['mrp.product.produce'].with_context(
|
||||
**action['context']
|
||||
))
|
||||
with register_form.raw_workorder_line_ids.edit(0) as pl:
|
||||
pl.lot_id = lot_c1
|
||||
with register_form.raw_workorder_line_ids.edit(1) as pl:
|
||||
pl.lot_id = lot_c2
|
||||
register_form.finished_lot_id = lot_f1
|
||||
register_wizard = register_form.save()
|
||||
register_wizard.do_produce()
|
||||
|
||||
self.assertEqual(move_comp1.quantity_done, 5.0)
|
||||
self.assertEqual(move_comp1.quantity_done, 3)
|
||||
self.assertEqual(move_comp1.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_id.name, 'LOT C1')
|
||||
self.assertEqual(move_comp2.quantity_done, 5.0)
|
||||
self.assertEqual(move_comp2.quantity_done, 3)
|
||||
self.assertEqual(move_comp2.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_id.name, 'LOT C2')
|
||||
self.assertEqual(move_finished.quantity_done, 5.0)
|
||||
self.assertEqual(move_finished.quantity_done, 3)
|
||||
self.assertEqual(move_finished.move_line_ids.filtered(lambda ml: ml.product_uom_qty).lot_id.name, 'LOT F1')
|
||||
|
||||
corrected_final_lot = self.env['stock.production.lot'].create({
|
||||
@@ -484,31 +471,17 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
})
|
||||
|
||||
details_operation_form = Form(picking_receipt.move_lines, view=self.env.ref('stock.view_stock_move_operations'))
|
||||
for i in range(len(details_operation_form._values['move_line_ids'])):
|
||||
with details_operation_form.move_line_ids.edit(i) as ml:
|
||||
if ml._values['qty_done']:
|
||||
ml.lot_id = corrected_final_lot
|
||||
with details_operation_form.move_line_ids.edit(0) as ml:
|
||||
ml.lot_id = corrected_final_lot
|
||||
details_operation_form.save()
|
||||
move_raw_comp_1 = picking_receipt.move_lines.move_orig_ids.production_id.move_raw_ids.filtered(lambda m: m.product_id == self.comp1)
|
||||
move_raw_comp_2 = picking_receipt.move_lines.move_orig_ids.production_id.move_raw_ids.filtered(lambda m: m.product_id == self.comp2)
|
||||
|
||||
details_subcontract_moves_form = Form(move_raw_comp_1, view=self.env.ref('mrp_subcontracting.mrp_subcontracting_move_form_view'))
|
||||
for i in range(len(details_subcontract_moves_form._values['move_line_ids'])):
|
||||
with details_subcontract_moves_form.move_line_ids.edit(i) as sc:
|
||||
if sc._values['qty_done']:
|
||||
sc.lot_produced_ids.remove(index=0)
|
||||
sc.lot_produced_ids.add(corrected_final_lot)
|
||||
details_subcontract_moves_form.save()
|
||||
details_subcontract_moves_form = Form(move_raw_comp_2, view=self.env.ref('mrp_subcontracting.mrp_subcontracting_move_form_view'))
|
||||
for i in range(len(details_subcontract_moves_form._values['move_line_ids'])):
|
||||
with details_subcontract_moves_form.move_line_ids.edit(i) as sc:
|
||||
if sc._values['qty_done']:
|
||||
sc.lot_produced_ids.remove(index=0)
|
||||
sc.lot_produced_ids.add(corrected_final_lot)
|
||||
details_subcontract_moves_form.save()
|
||||
picking_receipt.move_lines.move_orig_ids.production_id.lot_producing_id = corrected_final_lot
|
||||
picking_receipt.move_lines.move_orig_ids.production_id.button_mark_done()
|
||||
|
||||
self.assertEqual(move_comp1.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_produced_ids.name, 'LOT F2')
|
||||
self.assertEqual(move_comp2.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_produced_ids.name, 'LOT F2')
|
||||
self.assertEqual(move_comp1.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).produce_line_ids.lot_id.name, 'LOT F2')
|
||||
self.assertEqual(move_comp2.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).produce_line_ids.lot_id.name, 'LOT F2')
|
||||
|
||||
def test_flow_8(self):
|
||||
resupply_sub_on_order_route = self.env['stock.location.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
|
||||
@@ -693,20 +666,20 @@ class TestSubcontractingTracking(TransactionCase):
|
||||
'product_id': self.comp1_sn.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],
|
||||
}))
|
||||
produce_form.finished_lot_id = lot_id
|
||||
produce_form.raw_workorder_line_ids._records[0]['lot_id'] = serial_id.id
|
||||
wiz_produce = produce_form.save()
|
||||
wiz_produce.do_produce()
|
||||
|
||||
mo_form = Form(mo.with_context(subcontract_move_id=picking_receipt.move_lines.id))
|
||||
mo_form.qty_producing = 1
|
||||
mo_form.lot_producing_id = lot_id
|
||||
mo = mo_form.save()
|
||||
details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.new() as ml:
|
||||
ml.qty_done = 1
|
||||
ml.lot_id = serial_id
|
||||
details_operation_form.save()
|
||||
|
||||
# We should not be able to call the 'record_components' button
|
||||
self.assertFalse(picking_receipt.display_action_record_components)
|
||||
|
||||
picking_receipt.move_lines.quantity_done = 1
|
||||
picking_receipt.move_lines.move_line_ids.lot_id = lot_id.id
|
||||
picking_receipt.button_validate()
|
||||
self.assertEqual(mo.state, 'done')
|
||||
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="tracking" invisible="1"/>
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="lot_produced_ids"
|
||||
widget="many2many_tags"
|
||||
context="{'default_product_id': parent.product_id}"
|
||||
attrs="{'column_invisible': [('parent.finished_lots_exist', '!=', True)]}"
|
||||
/>
|
||||
<field name="qty_done"/>
|
||||
<field name="lot_id" context="{'default_product_id': product_id}"/>
|
||||
</tree>
|
||||
@@ -30,17 +25,17 @@
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="mrp_subcontracting_move_tree_view" model="ir.ui.view">
|
||||
<field name="name">mrp.subcontracting.move.tree.view</field>
|
||||
<field name="model">stock.move</field>
|
||||
<field name="priority">1000</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="inherit_id" ref="mrp.view_stock_move_raw_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="attributes">
|
||||
<attribute name="create">0</attribute>
|
||||
<attribute name="delete">0</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<!-- <record id="mrp_subcontracting_move_tree_view" model="ir.ui.view"> -->
|
||||
<!-- <field name="name">mrp.subcontracting.move.tree.view</field> -->
|
||||
<!-- <field name="model">stock.move</field> -->
|
||||
<!-- <field name="priority">1000</field> -->
|
||||
<!-- <field name="mode">primary</field> -->
|
||||
<!-- <field name="inherit_id" ref="mrp.view_stock_move_raw_tree"/> -->
|
||||
<!-- <field name="arch" type="xml"> -->
|
||||
<!-- <xpath expr="//tree" position="attributes"> -->
|
||||
<!-- <attribute name="create">0</attribute> -->
|
||||
<!-- <attribute name="delete">0</attribute> -->
|
||||
<!-- </xpath> -->
|
||||
<!-- </field> -->
|
||||
<!-- </record> -->
|
||||
</odoo>
|
||||
|
||||
@@ -2,42 +2,36 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo.tools.float_utils import float_is_zero, float_compare
|
||||
from odoo.tools.float_utils import float_compare, float_is_zero
|
||||
|
||||
class MrpProductProduce(models.TransientModel):
|
||||
_inherit = 'mrp.product.produce'
|
||||
|
||||
subcontract_move_id = fields.Many2one('stock.move', 'stock move from the subcontract picking', check_company=True)
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
|
||||
def continue_production(self):
|
||||
action = super(MrpProductProduce, self).continue_production()
|
||||
action['context'] = dict(action['context'], default_subcontract_move_id=self.subcontract_move_id.id)
|
||||
return action
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
if self.env.context.get('subcontract_move_id'):
|
||||
for production in self:
|
||||
if production.state not in ('cancel', 'done') and \
|
||||
('lot_producing_id' in vals or 'qty_producing' in vals):
|
||||
production._update_finished_move()
|
||||
|
||||
def _generate_produce_lines(self):
|
||||
""" When the wizard is called in backend, the onchange that create the
|
||||
produce lines is not trigger. This method generate them and is used with
|
||||
_record_production to appropriately set the lot_produced_id and
|
||||
appropriately create raw stock move lines.
|
||||
"""
|
||||
self.ensure_one()
|
||||
moves = (self.move_raw_ids | self.move_finished_ids).filtered(
|
||||
lambda move: move.state not in ('done', 'cancel')
|
||||
)
|
||||
for move in moves:
|
||||
qty_to_consume = self._prepare_component_quantity(move, self.qty_producing)
|
||||
line_values = self._generate_lines_values(move, qty_to_consume)
|
||||
self.env['mrp.product.produce.line'].create(line_values)
|
||||
def _pre_button_mark_done(self):
|
||||
if self.env.context.get('subcontract_move_id'):
|
||||
return True
|
||||
return super()._pre_button_mark_done()
|
||||
|
||||
def _update_finished_move(self):
|
||||
""" After producing, set the move line on the subcontract picking. """
|
||||
res = super(MrpProductProduce, self)._update_finished_move()
|
||||
if self.subcontract_move_id:
|
||||
subcontract_move_id = self.env.context.get('subcontract_move_id')
|
||||
res = None
|
||||
if subcontract_move_id:
|
||||
subcontract_move_id = self.env['stock.move'].browse(subcontract_move_id)
|
||||
quantity = self.qty_producing
|
||||
if self.finished_lot_id:
|
||||
move_lines = self.subcontract_move_id.move_line_ids.filtered(lambda ml: ml.lot_id == self.finished_lot_id or not ml.lot_id)
|
||||
if self.lot_producing_id:
|
||||
move_lines = subcontract_move_id.move_line_ids.filtered(lambda ml: ml.lot_id == self.lot_producing_id or not ml.lot_id)
|
||||
else:
|
||||
move_lines = self.subcontract_move_id.move_line_ids.filtered(lambda ml: not ml.lot_id)
|
||||
move_lines = subcontract_move_id.move_line_ids.filtered(lambda ml: not ml.lot_id)
|
||||
# Update reservation and quantity done
|
||||
for ml in move_lines:
|
||||
rounding = ml.product_uom_id.rounding
|
||||
@@ -52,16 +46,14 @@ class MrpProductProduce(models.TransientModel):
|
||||
if float_compare(new_quantity_done, ml.product_uom_qty, precision_rounding=rounding) >= 0:
|
||||
ml.write({
|
||||
'qty_done': new_quantity_done,
|
||||
'lot_id': self.finished_lot_id and self.finished_lot_id.id,
|
||||
'lot_name': self.finished_lot_id and self.finished_lot_id.name,
|
||||
'lot_id': self.lot_producing_id and self.lot_producing_id.id,
|
||||
})
|
||||
else:
|
||||
new_qty_reserved = ml.product_uom_qty - new_quantity_done
|
||||
default = {
|
||||
'product_uom_qty': new_quantity_done,
|
||||
'qty_done': new_quantity_done,
|
||||
'lot_id': self.finished_lot_id and self.finished_lot_id.id,
|
||||
'lot_name': self.finished_lot_id and self.finished_lot_id.name,
|
||||
'lot_id': self.lot_producing_id and self.lot_producing_id.id,
|
||||
}
|
||||
ml.copy(default=default)
|
||||
ml.with_context(bypass_reservation_update=True).write({
|
||||
@@ -71,24 +63,30 @@ class MrpProductProduce(models.TransientModel):
|
||||
|
||||
if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) > 0:
|
||||
self.env['stock.move.line'].create({
|
||||
'move_id': self.subcontract_move_id.id,
|
||||
'picking_id': self.subcontract_move_id.picking_id.id,
|
||||
'move_id': subcontract_move_id.id,
|
||||
'picking_id': subcontract_move_id.picking_id.id,
|
||||
'product_id': self.product_id.id,
|
||||
'location_id': self.subcontract_move_id.location_id.id,
|
||||
'location_dest_id': self.subcontract_move_id.location_dest_id.id,
|
||||
'location_id': subcontract_move_id.location_id.id,
|
||||
'location_dest_id': subcontract_move_id.location_dest_id.id,
|
||||
'product_uom_qty': 0,
|
||||
'product_uom_id': self.product_uom_id.id,
|
||||
'qty_done': quantity,
|
||||
'lot_id': self.finished_lot_id and self.finished_lot_id.id,
|
||||
'lot_name': self.finished_lot_id and self.finished_lot_id.name,
|
||||
'lot_id': self.lot_producing_id and self.lot_producing_id.id,
|
||||
})
|
||||
|
||||
if not self._get_todo(self.production_id):
|
||||
ml_reserved = self.subcontract_move_id.move_line_ids.filtered(lambda ml:
|
||||
if not self._get_todo():
|
||||
ml_reserved = subcontract_move_id.move_line_ids.filtered(lambda ml:
|
||||
float_is_zero(ml.qty_done, precision_rounding=ml.product_uom_id.rounding) and
|
||||
not float_is_zero(ml.product_uom_qty, precision_rounding=ml.product_uom_id.rounding))
|
||||
ml_reserved.unlink()
|
||||
for ml in self.subcontract_move_id.move_line_ids:
|
||||
for ml in subcontract_move_id.move_line_ids:
|
||||
ml.product_uom_qty = ml.qty_done
|
||||
self.subcontract_move_id._recompute_state()
|
||||
subcontract_move_id._recompute_state()
|
||||
return res
|
||||
|
||||
def _get_todo(self):
|
||||
""" This method will return remaining todo quantity of production. """
|
||||
main_product_moves = self.move_finished_ids.filtered(lambda x: x.product_id.id == self.product_id.id)
|
||||
todo_quantity = self.product_qty - sum(main_product_moves.mapped('quantity_done'))
|
||||
todo_quantity = todo_quantity if (todo_quantity > 0) else 0
|
||||
return todo_quantity
|
||||
|
||||
@@ -20,7 +20,6 @@ class TestSaleMrpFlow(AccountTestCommon):
|
||||
cls.MrpProduction = cls.env['mrp.production']
|
||||
cls.Inventory = cls.env['stock.inventory']
|
||||
cls.InventoryLine = cls.env['stock.inventory.line']
|
||||
cls.ProductProduce = cls.env['mrp.product.produce']
|
||||
cls.ProductCategory = cls.env['product.category']
|
||||
|
||||
cls.categ_unit = cls.env.ref('uom.product_uom_categ_unit')
|
||||
@@ -432,14 +431,9 @@ class TestSaleMrpFlow(AccountTestCommon):
|
||||
# produce product D.
|
||||
# ------------------
|
||||
|
||||
produce_form = Form(self.ProductProduce.with_context({
|
||||
'active_id': mnf_product_d.id,
|
||||
'active_ids': [mnf_product_d.id],
|
||||
}))
|
||||
produce_form.qty_producing = 20
|
||||
produce_d = produce_form.save()
|
||||
# produce_d.on_change_qty()
|
||||
produce_d.do_produce()
|
||||
mo_form = Form(mnf_product_d)
|
||||
mo_form.qty_producing = 20
|
||||
mnf_product_d = mo_form.save()
|
||||
mnf_product_d.post_inventory()
|
||||
|
||||
# Check state of manufacturing order.
|
||||
@@ -487,12 +481,9 @@ class TestSaleMrpFlow(AccountTestCommon):
|
||||
# Produce product A.
|
||||
# ------------------
|
||||
|
||||
produce_form = Form(self.ProductProduce.with_context({
|
||||
'active_id': mnf_product_a.id,
|
||||
'active_ids': [mnf_product_a.id],
|
||||
}))
|
||||
produce_a = produce_form.save()
|
||||
produce_a.do_produce()
|
||||
mo_form = Form(mnf_product_a)
|
||||
mo_form.qty_producing = mo_form.product_qty
|
||||
mnf_product_a = mo_form.save()
|
||||
mnf_product_a.post_inventory()
|
||||
# Check state of manufacturing order product A.
|
||||
self.assertEqual(mnf_product_a.state, 'done', 'Manufacturing order should still be in the progress state.')
|
||||
|
||||
@@ -219,7 +219,7 @@ class StockMove(models.Model):
|
||||
else:
|
||||
move.show_details_visible = (((consignment_enabled and move.picking_id.picking_type_id.code != 'incoming') or
|
||||
show_details_visible or move.has_tracking != 'none') and
|
||||
(move.state != 'draft' or (move.picking_id.immediate_transfer and move.state == 'draft')) and
|
||||
move._show_details_in_draft() and
|
||||
move.picking_id.picking_type_id.show_operations is False)
|
||||
|
||||
def _compute_show_reserved_availability(self):
|
||||
@@ -1653,3 +1653,7 @@ class StockMove(models.Model):
|
||||
mtso_free_qties_by_loc[move.location_id][move.product_id.id] -= needed_qty
|
||||
else:
|
||||
move.procure_method = 'make_to_order'
|
||||
|
||||
def _show_details_in_draft(self):
|
||||
self.ensure_one()
|
||||
return self.state != 'draft' or (self.picking_id.immediate_transfer and self.state == 'draft')
|
||||
|
||||
@@ -823,17 +823,15 @@ tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('Manufacturing
|
||||
position: 'bottom',
|
||||
},
|
||||
...tour.stepUtils.statusbarButtonsSteps('Check availability', _t("Check availability")),
|
||||
...tour.stepUtils.statusbarButtonsSteps('Produce', _t("Produce"), "body.o_web_client:not(.oe_wait)"),
|
||||
{
|
||||
mobile: false,
|
||||
trigger: ".modal-footer .btn-primary:nth-child(3)",
|
||||
content: _t('Record Production'),
|
||||
position: 'bottom',
|
||||
trigger: '.o_form_button_edit',
|
||||
content: _t('Edit the production order'),
|
||||
extra_trigger: 'body.o_web_client:not(.oe_wait)',
|
||||
}, {
|
||||
mobile: true,
|
||||
trigger: '.modal-footer .btn-primary[name="do_produce"]:not(.o_invisible_modifier)',
|
||||
content: _t('Record Production'),
|
||||
position: 'bottom',
|
||||
trigger: ".o_field_widget[name=qty_producing]",
|
||||
content: _t("Produce"),
|
||||
run: "text 1",
|
||||
extra_trigger: 'body.o_web_client:not(.oe_wait)',
|
||||
},
|
||||
...tour.stepUtils.statusbarButtonsSteps('Mark as Done', _t("Mark as Done"), ".o_statusbar_status .btn.dropdown-toggle:contains('To Close')"),
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user