# -*- coding: utf-8 -*- from odoo import api, fields, models,_ from odoo.tools.float_utils import float_compare from odoo.exceptions import UserError class StockPicking(models.Model): _inherit = "stock.picking" @api.depends('move_lines') def _compute_alert(self): ''' This function computes the number of quality alerts generated from given picking. ''' for picking in self: alerts = self.env['quality.alert'].search([('picking_id', '=', picking.id)]) picking.alert_ids = alerts picking.alert_count = len(alerts) @api.multi def quality_alert_action(self): ''' This function returns an action that display existing quality alerts generated from a given picking. ''' action = self.env.ref('quality_assurance.quality_alert_action') result = action.read()[0] # override the context to get rid of the default filtering on picking type result.pop('id', None) result['context'] = {} alert_ids = sum([picking.alert_ids.ids for picking in self], []) # choose the view_mode accordingly if len(alert_ids) > 1: result['domain'] = "[('id','in',[" + ','.join(map(str, alert_ids)) + "])]" elif len(alert_ids) == 1: res = self.env.ref('quality_assurance.quality_alert_form', False) result['views'] = [(res and res.id or False, 'form')] result['res_id'] = alert_ids and alert_ids[0] or False return result alert_count = fields.Integer(compute='_compute_alert', string='Quality Alerts', default=0) alert_ids = fields.Many2many('quality.alert', compute='_compute_alert', string='Quality Alerts', copy=False) @api.multi def generate_quality_alert(self): ''' This function generates quality alerts for the products mentioned in move_lines of given picking and also have quality measures configured. ''' quality_alert = self.env['quality.alert'] quality_measure = self.env['quality.measure'] for move in self.move_lines: measures = quality_measure.search([('product_id', '=', move.product_id.id), ('trigger_time', 'in', self.picking_type_id.id)]) if measures: quality_alert.create({ 'name': self.env['ir.sequence'].next_by_code('quality.alert') or _('New'), 'product_id': move.product_id.id, 'picking_id': self.id, 'origin': self.name, 'company_id': self.company_id.id, }) @api.multi def action_confirm(self): if self.alert_count == 0: self.generate_quality_alert() res = super(StockPicking, self).action_confirm() return res @api.multi def force_assign(self): if self.alert_count == 0: self.generate_quality_alert() res = super(StockPicking, self).force_assign() return res @api.multi def do_transfer(self): """ If no pack operation, we do simple action_done of the picking. Otherwise, do the pack operations. """ # TDE CLEAN ME: reclean me, please self._create_lots_for_picking() no_pack_op_pickings = self.filtered(lambda picking: not picking.pack_operation_ids) no_pack_op_pickings.action_done() other_pickings = self - no_pack_op_pickings for picking in other_pickings: need_rereserve, all_op_processed = picking.picking_recompute_remaining_quantities() todo_moves = self.env['stock.move'] toassign_moves = self.env['stock.move'] # create extra moves in the picking (unexpected product moves coming from pack operations) if not all_op_processed: todo_moves |= picking._create_extra_moves() if need_rereserve or not all_op_processed: moves_reassign = any(x.origin_returned_move_id or x.move_orig_ids for x in picking.move_lines if x.state not in ['done', 'cancel']) if moves_reassign and picking.location_id.usage not in ("supplier", "production", "inventory"): # unnecessary to assign other quants than those involved with pack operations as they will be unreserved anyways. picking.with_context(reserve_only_ops=True, no_state_change=True).rereserve_quants( move_ids=picking.move_lines.ids) picking.do_recompute_remaining_quantities() # split move lines if needed for move in picking.move_lines: rounding = move.product_id.uom_id.rounding remaining_qty = move.remaining_qty if move.state in ('done', 'cancel'): # ignore stock moves cancelled or already done continue elif move.state == 'draft': toassign_moves |= move if float_compare(remaining_qty, 0, precision_rounding=rounding) == 0: if move.state in ('draft', 'assigned', 'confirmed'): todo_moves |= move elif float_compare(remaining_qty, 0, precision_rounding=rounding) > 0 and float_compare(remaining_qty, move.product_qty, precision_rounding=rounding) < 0: # TDE FIXME: shoudl probably return a move - check for no track key, by the way new_move_id = move.split(remaining_qty) new_move = self.env['stock.move'].with_context(mail_notrack=True).browse(new_move_id) todo_moves |= move # Assign move as it was assigned before toassign_moves |= new_move # TDE FIXME: do_only_split does not seem used anymore if todo_moves and not self.env.context.get('do_only_split'): for move in todo_moves: alerts = self.env['quality.alert'].search([('picking_id', '=', self.id), ('product_id', '=', move.product_id.id)]) for alert in alerts: if alert.final_status == 'wait': raise UserError(_('There are items still in quality test')) if alert.final_status == 'fail': raise UserError(_('There are items failed in quality test')) todo_moves.action_done() elif self.env.context.get('do_only_split'): picking = picking.with_context(split=todo_moves.ids) picking._create_backorder() return True