# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). # Copyright 2019 Odoo # Copyright 2020 Tecnativa - Alexandre Díaz # Copyright 2020 Tecnativa - Pedro M. Baeza from datetime import timedelta from odoo import api, fields, models from odoo.tools import float_is_zero class StockPicking(models.Model): _inherit = 'stock.picking' display_action_record_components = fields.Boolean( compute='_compute_display_action_record_components') @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'): picking.display_action_record_components = False continue if not picking._is_subcontract(): picking.display_action_record_components = False continue # Hide if no components are track subcontracted_productions = picking\ ._get_subcontracted_productions() subcontracted_moves = subcontracted_productions.mapped( 'move_raw_ids') if all(subcontracted_move.has_tracking == 'none' for subcontracted_move in subcontracted_moves): picking.display_action_record_components = False continue # Hide if the production is to close if not subcontracted_productions.filtered(lambda mo: ( not mo.check_to_done and mo.state != 'done' )): picking.display_action_record_components = False continue picking.display_action_record_components = True # ------------------------------------------------------------------------- # Action methods # ------------------------------------------------------------------------- def action_done(self): productions = self.env['mrp.production'] for picking in self: for move in picking.move_lines: production = move.move_orig_ids.mapped('production_id') if not move.is_subcontract or production.state in ('done', 'cancel'): continue 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')) for ml in move.move_line_ids: if float_is_zero( ml.qty_done, precision_rounding=ml.product_uom_id.rounding ): continue ml.copy({ 'picking_id': False, 'production_id': move_finished_ids.production_id.id, 'move_id': move_finished_ids.id, 'qty_done': ml.qty_done, 'result_package_id': False, 'location_id': move_finished_ids.location_id.id, 'location_dest_id': move_finished_ids.location_dest_id.id, }) else: 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 produce = self.env['mrp.product.produce'].with_context( default_production_id=production.id, active_id=production.id).create({ 'production_id': production.id, 'product_id': production.product_id.id, 'product_qty': move_line.qty_done, 'product_uom_id': move_line.product_uom_id.id, 'lot_id': move_line.lot_id.id, }) produce._onchange_product_qty() produce.do_produce() productions |= production for subcontracted_production in productions: if subcontracted_production.check_to_done: subcontracted_production.button_mark_done() else: subcontracted_production.post_inventory() res = super(StockPicking, self).action_done() for subcontracted_production in productions: # For consistency, set the date on production move before the # date on picking. (Traceability report + Product Moves menu # item) minimum_date = min(picking.move_line_ids.mapped('date')) production_moves = subcontracted_production.move_raw_ids\ | subcontracted_production.move_finished_ids production_moves.write({ 'date': minimum_date - timedelta(seconds=1), }) production_moves.mapped('move_line_ids').write({ 'date': minimum_date - timedelta(seconds=1), }) return res def action_record_components(self): self.ensure_one() for move in self.move_lines: if not move._has_tracked_subcontract_components(): continue production = move.move_orig_ids.mapped("production_id") if not production or production.state in ('done', 'to_close'): continue return move._action_record_components() # ------------------------------------------------------------------------- # Subcontract helpers # ------------------------------------------------------------------------- def _is_subcontract(self): self.ensure_one() return self.picking_type_id.code == 'incoming' and any( m.is_subcontract for m in self.move_lines) def _get_subcontracted_productions(self): self.ensure_one() return self.move_lines.mapped('move_orig_ids.production_id') def _get_warehouse(self, subcontract_move): return subcontract_move.warehouse_id\ or self.picking_type_id.warehouse_id def _prepare_subcontract_mo_vals(self, subcontract_move, bom): subcontract_move.ensure_one() group = self.env['procurement.group'].create({ 'name': self.name, 'partner_id': self.partner_id.id, }) product = subcontract_move.product_id warehouse = self._get_warehouse(subcontract_move) vals = { 'company_id': subcontract_move.company_id.id, 'procurement_group_id': group.id, '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.with_context( force_company=subcontract_move.company_id.id) .property_stock_subcontractor.id, 'location_dest_id': subcontract_move.picking_id.partner_id.with_context( force_company=subcontract_move.company_id.id) .property_stock_subcontractor.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'].with_context( force_company=move.company_id.id ).create(self._prepare_subcontract_mo_vals(move, bom)) # 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()