In a few words, it allows to send components to a subcontractor partner and receive a finished product. The production is managed through classical manufacturing orders where the raw and finished location is the subcontracting location. When creating a receipt picking for a subcontractor partner, the location_id of the moves will be set to the subcontracting locations by using the property_stock_customer and property_stock_supplier of the partner. An override of _action_confirm will create the manufacturing order. When a subcontracted bom contains tracking, the produce wizard is available from the picking to record which lots were produced/consumed. New type on the BoM: subcontract New type on res.partner: subcontactor New location per company (we still own the components stored at the subcontractor so they're still valued) New routes created to resupply the subcontracting location on order or not (through a reordering rule). Override of res.partner to set the property_stock_customer/supplier on subcontactor partners. Proper tests were added too. Joint work with: Arnaud Baes <arb@odoo.com> task 1831382
134 lines
6.4 KiB
Python
134 lines
6.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, fields, models
|
|
|
|
|
|
class StockPicking(models.Model):
|
|
_inherit = 'stock.picking'
|
|
|
|
display_action_record_components = fields.Boolean(compute='_compute_display_action_record_components')
|
|
display_view_subcontracted_move_lines = fields.Boolean(compute='_compute_display_view_subcontracted_move_lines')
|
|
|
|
@api.depends('state')
|
|
def _compute_display_action_record_components(self):
|
|
for picking in self:
|
|
# Hide if not encoding state
|
|
if picking.state in ('draft', 'cancel', 'done'):
|
|
continue
|
|
if not picking._is_subcontract():
|
|
continue
|
|
# Hide if no move is tracked
|
|
subcontracted_productions = picking._get_subcontracted_productions()
|
|
subcontracted_moves = subcontracted_productions.mapped('move_raw_ids')
|
|
subcontracted_moves |= subcontracted_productions.mapped('move_finished_ids')
|
|
if all(subcontracted_move.has_tracking == 'none' for subcontracted_move in subcontracted_moves):
|
|
continue
|
|
# Hide if the production is to close
|
|
if not subcontracted_productions.filtered(lambda mo: mo.state not in ('to_close', 'done')):
|
|
continue
|
|
picking.display_action_record_components = True
|
|
|
|
@api.depends('state')
|
|
def _compute_display_view_subcontracted_move_lines(self):
|
|
for picking in self:
|
|
# Hide if not encoding state
|
|
if picking.state in ('draft', 'cancel'):
|
|
continue
|
|
if not picking._is_subcontract():
|
|
continue
|
|
# Hide until state done if no move is tracked, if tracked until something was produced
|
|
subcontracted_productions = picking._get_subcontracted_productions()
|
|
subcontracted_moves = subcontracted_productions.mapped('move_raw_ids')
|
|
subcontracted_moves |= subcontracted_productions.mapped('move_finished_ids')
|
|
if all(subcontracted_move.has_tracking == 'none' for subcontracted_move in subcontracted_moves):
|
|
if picking.state != 'done':
|
|
continue
|
|
# Hide if nothing was produced
|
|
if all(subcontracted_move.quantity_done == 0 for subcontracted_move in subcontracted_moves):
|
|
continue
|
|
picking.display_view_subcontracted_move_lines = True
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Action methods
|
|
# -------------------------------------------------------------------------
|
|
def action_done(self):
|
|
for picking in self:
|
|
subcontracted_productions = picking._get_subcontracted_productions()
|
|
for subcontracted_production in subcontracted_productions:
|
|
subcontracted_production.button_mark_done()
|
|
return super(StockPicking, self).action_done()
|
|
|
|
def action_view_subcontracted_move_lines(self):
|
|
""" Returns a list view with the move lines of the subcontracted products. To find them, we
|
|
look on the origin moves of the move lines of the picking if there is a manufacturing order.
|
|
"""
|
|
self.ensure_one()
|
|
subcontracted_productions = self._get_subcontracted_productions()
|
|
subcontracted_move_lines = self.env['stock.move.line']
|
|
for subcontracted_production in subcontracted_productions:
|
|
subcontracted_move_lines |= subcontracted_production.move_raw_ids.mapped('move_line_ids')
|
|
subcontracted_move_lines |= subcontracted_production.move_finished_ids.mapped('move_line_ids')
|
|
action = self.env.ref('stock.stock_move_line_action').read()[0]
|
|
action['context'] = {}
|
|
action['domain'] = [('id', 'in', subcontracted_move_lines.ids)]
|
|
return action
|
|
|
|
def action_record_components(self):
|
|
self.ensure_one()
|
|
subcontracted_productions = self._get_subcontracted_productions()
|
|
to_register = subcontracted_productions.filtered(lambda mo: mo.state not in ('to_close', 'done'))
|
|
if to_register:
|
|
mo = to_register[0]
|
|
action = self.env.ref('mrp.act_mrp_product_produce').read()[0]
|
|
action['context'] = dict(self.env.context, active_id=mo.id)
|
|
return action
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Subcontract helpers
|
|
# -------------------------------------------------------------------------
|
|
def _is_subcontract(self):
|
|
self.ensure_one()
|
|
if self.partner_id.type == 'subcontractor' and \
|
|
self.picking_type_id.code == 'incoming':
|
|
return True
|
|
return False
|
|
|
|
def _get_subcontracted_productions(self):
|
|
self.ensure_one()
|
|
return self.move_lines.mapped('move_orig_ids.production_id')
|
|
|
|
def _prepare_subcontract_mo_vals(self, subcontract_move, bom):
|
|
subcontract_move.ensure_one()
|
|
product = subcontract_move.product_id
|
|
warehouse = subcontract_move.warehouse_id or self.picking_type_id.warehouse_id
|
|
vals = {
|
|
'product_id': product.id,
|
|
'product_uom_id': subcontract_move.product_uom.id,
|
|
'bom_id': bom.id,
|
|
'location_src_id': subcontract_move.picking_id.partner_id.property_stock_supplier.id,
|
|
'location_dest_id': subcontract_move.location_id.id,
|
|
'product_qty': subcontract_move.product_uom_qty,
|
|
'picking_type_id': warehouse.subcontracting_type_id.id
|
|
}
|
|
return vals
|
|
|
|
def _subcontracted_produce(self, subcontract_details):
|
|
self.ensure_one()
|
|
for move, bom in subcontract_details:
|
|
mo = self.env['mrp.production'].create(self._prepare_subcontract_mo_vals(move, bom))
|
|
self.env['stock.move'].create(mo._get_moves_raw_values())
|
|
mo.action_confirm()
|
|
|
|
# Link the finished to the receipt move.
|
|
finished_move = mo.move_finished_ids.filtered(lambda m: m.product_id == move.product_id)
|
|
finished_move.write({'move_dest_ids': [(4, move.id, False)]})
|
|
mo.action_assign()
|
|
|
|
# Only skip the produce wizard if no moves is tracked
|
|
moves = mo.mapped('move_raw_ids') + mo.mapped('move_finished_ids')
|
|
if any(move.has_tracking != 'none' for move in moves):
|
|
continue
|
|
for m in moves:
|
|
m.quantity_done = m.product_uom_qty
|