Step to reproduce: - Create a subcontracting BoM by partner A, for a tracked product B (by serial or lots) - Create a incoming transfer coming from A and mark as todo. - Fill the move with move lines with lot + quantity. - It is impossible to validate the transfer, "You need to supply lot..." Also at the creation of the picking a warning popover indicate that the previous operation (the hidden MO) is set after the transfer. These issues comes from the refactor of mrp for v14 and the part of subcontracting wasn't complete obviously. Fixes done: - Rewrite the code of the `_action_done` for subcontracting. It is for the case of tracked finished product without any tracked component, there wasn't any code to manage that. Now manage it by backorder MO feature. Fix the main bug - Set finished_date_planned before the transfer to avoid the alert popover. - Avoid to write activity in channel about the hidden MO in case of cancelling. - Fix `action_record_components` to manage multiple subcontracting products with tracked component. - Remove the `priority` field from the wizard MO (in case of tracked product. Also the wizard is not really a wizard, just a weird hybrid MO form) PR: odoo/odoo#61606 task-2357115 closes odoo/odoo#62109 X-original-commit: 1a34e46eaca14aec981dd0f162e8014c7cb0abea Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com> Signed-off-by: Rémy Voet <ryv-odoo@users.noreply.github.com>
133 lines
6.8 KiB
Python
133 lines
6.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from collections import defaultdict
|
|
from odoo import fields, models, _, api
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools.float_utils import float_compare, float_is_zero
|
|
|
|
|
|
class MrpProduction(models.Model):
|
|
_inherit = 'mrp.production'
|
|
|
|
move_line_raw_ids = fields.One2many(
|
|
'stock.move.line', string="Detail Component", readonly=False,
|
|
inverse='_inverse_move_line_raw_ids', compute='_compute_move_line_raw_ids'
|
|
)
|
|
|
|
@api.depends('move_raw_ids.move_line_ids')
|
|
def _compute_move_line_raw_ids(self):
|
|
for production in self:
|
|
production.move_line_raw_ids = production.move_raw_ids.move_line_ids
|
|
|
|
def _inverse_move_line_raw_ids(self):
|
|
for production in self:
|
|
line_by_product = defaultdict(lambda: self.env['stock.move.line'])
|
|
for line in production.move_line_raw_ids:
|
|
line_by_product[line.product_id] |= line
|
|
for move in production.move_raw_ids:
|
|
move.move_line_ids = line_by_product.pop(move.product_id, self.env['stock.move.line'])
|
|
for product_id, lines in line_by_product.items():
|
|
qty = sum(line.product_uom_id._compute_quantity(line.qty_done, product_id.uom_id) for line in lines)
|
|
move = production._get_move_raw_values(product_id, qty, product_id.uom_id)
|
|
move['additional'] = True
|
|
production.move_raw_ids = [(0, 0, move)]
|
|
production.move_raw_ids.filtered(lambda m: m.product_id == product_id)[:1].move_line_ids = lines
|
|
|
|
def subcontracting_record_component(self):
|
|
self.ensure_one()
|
|
assert self.env.context.get('subcontract_move_id')
|
|
if float_is_zero(self.qty_producing, precision_rounding=self.product_uom_id.rounding):
|
|
return {'type': 'ir.actions.act_window_close'}
|
|
for sml in self.move_raw_ids.move_line_ids:
|
|
if sml.tracking != 'none' and not sml.lot_id:
|
|
raise UserError(_('You must enter a serial number for each line of %s') % sml.product_id.name)
|
|
self._update_finished_move()
|
|
quantity_issues = self._get_quantity_produced_issues()
|
|
if quantity_issues:
|
|
backorder = self._generate_backorder_productions(close_mo=False)
|
|
# No qty to consume to avoid propagate additional move
|
|
# TODO avoid : stock move created in backorder with 0 as qty
|
|
backorder.move_raw_ids.filtered(lambda m: m.additional).product_uom_qty = 0.0
|
|
|
|
backorder.qty_producing = backorder.product_qty
|
|
backorder._set_qty_producing()
|
|
|
|
self.product_qty = self.qty_producing
|
|
subcontract_move_id = self.env['stock.move'].browse(self.env.context.get('subcontract_move_id'))
|
|
action = subcontract_move_id._action_record_components()
|
|
action.update({'res_id': backorder.id})
|
|
return action
|
|
return {'type': 'ir.actions.act_window_close'}
|
|
|
|
def _pre_button_mark_done(self):
|
|
if self.env.context.get('subcontract_move_id'):
|
|
return True
|
|
return super()._pre_button_mark_done()
|
|
|
|
def _update_finished_move(self):
|
|
""" After producing, set the move line on the subcontract picking. """
|
|
self.ensure_one()
|
|
subcontract_move_id = self.env.context.get('subcontract_move_id')
|
|
if subcontract_move_id:
|
|
subcontract_move_id = self.env['stock.move'].browse(subcontract_move_id)
|
|
quantity = self.qty_producing
|
|
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 = 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
|
|
if float_compare(quantity, 0, precision_rounding=rounding) <= 0:
|
|
break
|
|
quantity_to_process = min(quantity, ml.product_uom_qty - ml.qty_done)
|
|
quantity -= quantity_to_process
|
|
|
|
new_quantity_done = (ml.qty_done + quantity_to_process)
|
|
|
|
# on which lot of finished product
|
|
if float_compare(new_quantity_done, ml.product_uom_qty, precision_rounding=rounding) >= 0:
|
|
ml.write({
|
|
'qty_done': new_quantity_done,
|
|
'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.lot_producing_id and self.lot_producing_id.id,
|
|
}
|
|
ml.copy(default=default)
|
|
ml.with_context(bypass_reservation_update=True).write({
|
|
'product_uom_qty': new_qty_reserved,
|
|
'qty_done': 0
|
|
})
|
|
|
|
if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) > 0:
|
|
self.env['stock.move.line'].create({
|
|
'move_id': subcontract_move_id.id,
|
|
'picking_id': subcontract_move_id.picking_id.id,
|
|
'product_id': self.product_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.lot_producing_id and self.lot_producing_id.id,
|
|
})
|
|
if not self._get_quantity_to_backorder():
|
|
ml_reserved = subcontract_move_id.move_line_ids.filtered(lambda ml:
|
|
float_is_zero(ml.qty_done, precision_rounding=ml.product_uom_id.rounding) and
|
|
not float_is_zero(ml.product_uom_qty, precision_rounding=ml.product_uom_id.rounding))
|
|
ml_reserved.unlink()
|
|
for ml in subcontract_move_id.move_line_ids:
|
|
ml.product_uom_qty = ml.qty_done
|
|
subcontract_move_id._recompute_state()
|
|
|
|
def _subcontracting_filter_to_done(self):
|
|
""" Filter subcontracting production where composant is already recorded and should be consider to be validate """
|
|
mos = self.filtered(lambda mo: mo.state not in ('done', 'cancel'))
|
|
return mos.filtered(lambda pro: all(line.lot_id for line in pro.move_raw_ids.filtered(lambda sm: sm.has_tracking != 'none').move_line_ids))
|