Files

173 lines
7.5 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, _
from odoo.exceptions import UserError
from odoo.tools.misc import OrderedSet
from odoo.tools.float_utils import float_is_zero
class StockPicking(models.Model):
_inherit = "stock.picking"
check_ids = fields.One2many('quality.check', 'picking_id', 'Checks')
quality_check_todo = fields.Boolean('Pending checks', compute='_compute_check', search='_search_quality_check_todo')
quality_check_fail = fields.Boolean(compute='_compute_check')
quality_alert_ids = fields.One2many('quality.alert', 'picking_id', 'Alerts')
quality_alert_count = fields.Integer(compute='_compute_quality_alert_count')
def _compute_check(self):
for picking in self:
todo = False
fail = False
# Only prefetch needed QC fields to avoid to bloat the cache by fetching other QC data.
checks = picking.check_ids
checks.fetch(['quality_state'])
for check in checks:
if check.quality_state == 'none':
todo = True
elif check.quality_state == 'fail':
fail = True
if fail and todo:
break
picking.quality_check_fail = fail
picking.quality_check_todo = todo
def _search_quality_check_todo(self, operator, value):
if operator != 'in':
return NotImplemented
domain = [('picking_id', '!=', False), ('quality_state', '=', 'none')]
query_check_picking = self.env['quality.check']._search(domain)
return [('id', 'in', query_check_picking.subselect('picking_id'))]
def _compute_quality_alert_count(self):
for picking in self:
picking.quality_alert_count = len(picking.quality_alert_ids)
def _checks_to_do(self):
check_ids_to_do = OrderedSet()
checkable_lines_ids = OrderedSet()
for picking in self:
has_picked = True
if all(not move.picked for move in picking.move_ids):
checkable_lines = picking.move_line_ids
has_picked = False
else:
checkable_lines = picking.move_line_ids.filtered(
lambda ml: ml._is_checkable(check_picked=has_picked)
)
checkable_products = checkable_lines.product_id
checks_to_do = self.check_ids.filtered(
lambda qc: qc._is_to_do(checkable_products, check_picked=has_picked)
)
check_ids_to_do.update(checks_to_do.ids)
checkable_lines_ids.update(checkable_lines.ids)
# raise user error for checkable by quantity, picked and tracked products where lot is not set
all_checks_to_do = self.env['quality.check'].browse(check_ids_to_do)
all_checkable_lines = self.env['stock.move.line'].browse(checkable_lines_ids)
check_to_do_on_move_lines = all_checks_to_do.filtered(lambda check: check.measure_on == "move_line")
tracked_checkable_lines_without_lot = all_checkable_lines.filtered(
lambda ml: ml.product_id.tracking in ["serial", "lot"] and
(ml.picking_type_use_create_lots or ml.picking_type_use_existing_lots) and
not ml.lot_id and not ml.lot_name and
ml.product_id in check_to_do_on_move_lines.product_id
)
if tracked_checkable_lines_without_lot:
products_list = "\n".join(f"- {product.display_name}" for product in tracked_checkable_lines_without_lot.product_id)
raise UserError(
_(
"You need to supply a Lot/Serial Number for product:\n%(products)s",
products=products_list,
),
)
return all_checks_to_do
def check_quality(self):
checks = self._checks_to_do()
if checks:
return checks.action_open_quality_check_wizard()
return True
def _create_backorder(self, backorder_moves=None):
res = super(StockPicking, self)._create_backorder(backorder_moves=backorder_moves)
if self.env.context.get('skip_check'):
return res
for backorder in res:
# Do not link the QC of move lines with quantity of 0 in backorder.
backorder.move_line_ids.filtered(lambda ml: not float_is_zero(ml.quantity, precision_rounding=ml.product_uom_id.rounding)).check_ids.picking_id = backorder
if backorder.backorder_id.state in ('done', 'cancel'):
backorder.backorder_id.check_ids.filtered(lambda qc: qc.quality_state == 'none').sudo().unlink()
backorder.move_ids._create_quality_checks()
return res
def _action_done(self):
if self._check_for_quality_checks():
raise UserError(_('You still need to do the quality checks!'))
return super(StockPicking, self)._action_done()
def _pre_action_done_hook(self):
res = super()._pre_action_done_hook()
if res is True:
return self.check_quality()
return res
def _check_for_quality_checks(self):
quality_pickings = self.env['stock.picking']
for picking in self:
if picking._checks_to_do():
quality_pickings |= picking
return quality_pickings
def action_cancel(self):
res = super(StockPicking, self).action_cancel()
self.sudo().mapped('check_ids').filtered(lambda x: x.quality_state == 'none').unlink()
return res
def action_open_quality_check_picking(self):
action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_check_action_picking")
action['context'] = self.env.context.copy()
action['context'].update({
'search_default_picking_id': [self.id],
'default_picking_id': self.id,
'show_lots_text': self.show_lots_text,
})
return action
def action_open_on_demand_quality_check(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_check_action_main")
action['views'] = [(False, 'form')]
action['context'] = {
**self.env.context,
'default_product_id': self.product_id.id if len(self.move_ids.ids) == 1 else False,
'default_product_tmpl_id': self.move_ids.product_tmpl_id.ids,
'default_picking_id': self.id,
}
return action
def button_quality_alert(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_alert_action_check")
action['views'] = [(False, 'form')]
action['context'] = {
'default_product_id': self.product_id.id,
'default_product_tmpl_id': self.product_id.product_tmpl_id.id,
'default_picking_id': self.id,
}
return action
def open_quality_alert_picking(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_alert_action_check")
action['context'] = {
'default_product_id': self.product_id.id,
'default_product_tmpl_id': self.product_id.product_tmpl_id.id,
'default_picking_id': self.id,
}
action['domain'] = [('id', 'in', self.quality_alert_ids.ids)]
action['views'] = [(False, 'list'), (False, 'form')]
if self.quality_alert_count == 1:
action['views'] = [(False, 'form')]
action['res_id'] = self.quality_alert_ids.id
return action