odoo/ext/custom-addons/stock_batch_picking/models/stock_batch_picking.py

221 lines
7.1 KiB
Python

# Copyright 2012-2014 Alexandre Fayolle, Camptocamp SA
# Copyright 2018 Tecnativa - Carlos Dauden
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class StockBatchPicking(models.Model):
""" This object allow to manage multiple stock.picking at the same time.
"""
_name = 'stock.batch.picking'
name = fields.Char(
'Name',
required=True, index=True,
copy=False, unique=True,
states={'draft': [('readonly', False)]},
default=lambda self: self.env['ir.sequence'].next_by_code(
'stock.batch.picking'
),
)
state = fields.Selection([
('draft', 'Draft'),
('assigned', 'Available'),
('done', 'Done'),
('cancel', 'Cancelled')],
string='State',
readonly=True, index=True, copy=False,
default='draft',
help='the state of the batch picking. '
'Workflow is draft -> assigned -> done or cancel'
)
date = fields.Date(
'Date',
required=True, readonly=True, index=True,
states={
'draft': [('readonly', False)],
'assigned': [('readonly', False)]
},
default=fields.Date.context_today,
help='date on which the batch picking is to be processed'
)
picker_id = fields.Many2one(
'res.users', 'Picker',
readonly=True, index=True,
states={
'draft': [('readonly', False)],
'assigned': [('readonly', False)]
},
help='the user to which the pickings are assigned'
)
picking_ids = fields.One2many(
'stock.picking', 'batch_picking_id', 'Pickings',
readonly=True,
states={'draft': [('readonly', False)]},
help='List of picking managed by this batch.'
)
active_picking_ids = fields.One2many(
'stock.picking', 'batch_picking_id', 'Pickings',
readonly=True,
domain=[('state', 'not in', ('cancel', 'done'))],
)
notes = fields.Text('Notes', help='free form remarks')
move_lines = fields.Many2many(
'stock.move',
readonly=True,
string='Related stock moves',
compute='_compute_move_lines'
)
move_line_ids = fields.Many2many(
'stock.move.line',
string='Related pack operations',
compute='_compute_move_line_ids',
# HACK: Allow to write sml fields from this model
inverse=lambda self: self,
)
entire_package_ids = fields.Many2many(
comodel_name='stock.quant.package',
compute='_compute_entire_package_ids',
help='Those are the entire packages of a picking shown in the view of '
'operations',
)
entire_package_detail_ids = fields.Many2many(
comodel_name='stock.quant.package',
compute='_compute_entire_package_ids',
help='Those are the entire packages of a picking shown in the view of '
'detailed operations',
)
@api.depends('picking_ids')
def _compute_move_lines(self):
for batch in self:
batch.move_lines = batch.picking_ids.mapped("move_lines")
@api.depends('picking_ids')
def _compute_move_line_ids(self):
for batch in self:
batch.move_line_ids = batch.picking_ids.mapped(
'move_line_ids'
)
@api.depends('picking_ids')
def _compute_entire_package_ids(self):
for batch in self:
batch.update({
'entire_package_ids': batch.picking_ids.mapped(
'entire_package_ids'),
'entire_package_detail_ids': batch.picking_ids.mapped(
'entire_package_detail_ids'),
})
def get_not_empties(self):
""" Return all batches in this recordset
for which picking_ids is not empty.
:raise UserError: If all batches are empty.
"""
if not self.mapped('picking_ids'):
if len(self) == 1:
message = _('This Batch has no pickings')
else:
message = _('These Batches have no pickings')
raise UserError(message)
return self.filtered(lambda b: len(b.picking_ids) != 0)
def verify_state(self, expected_state=None):
""" Check if batches states must be changed based on pickings states.
If all pickings are canceled, batch must be canceled.
If all pickings are canceled or done, batch must be done.
If all pickings are canceled or done or *expected_state*,
batch must be *expected_state*.
:return: True if batches states has been changed.
"""
expected_states = {'done', 'cancel'}
if expected_state is not None:
expected_states.add(expected_state)
all_good = True
for batch in self.filtered(lambda b: b.state not in expected_states):
states = set(batch.mapped('picking_ids.state'))
if not states or states == {'cancel'}:
batch.state = 'cancel'
elif states == {'done'} or states == {'done', 'cancel'}:
batch.state = 'done'
elif states.issubset(expected_states):
batch.state = expected_state
else:
all_good = False
return all_good
@api.multi
def action_cancel(self):
""" Call action_cancel for all batches pickings
and set batches states to cancel too.
"""
for batch in self:
if not batch.picking_ids:
batch.write({'state': 'cancel'})
else:
if not batch.verify_state():
batch.picking_ids.action_cancel()
@api.multi
def action_assign(self):
""" Check if batches pickings are available.
"""
batches = self.get_not_empties()
if not batches.verify_state('assigned'):
batches.mapped('active_picking_ids').action_assign()
@api.multi
def action_transfer(self):
""" Make the transfer for all active pickings in these batches
and set state to done all picking are done.
"""
batches = self.get_not_empties()
for batch in batches:
if not batch.verify_state():
batch.active_picking_ids.force_transfer(
force_qty=all(
operation.qty_done == 0
for operation in batch.move_line_ids
)
)
@api.multi
def remove_undone_pickings(self):
""" Remove of this batch all pickings which state is not done / cancel.
"""
self.mapped('active_picking_ids').write({'batch_picking_id': False})
self.verify_state()
@api.multi
def action_view_stock_picking(self):
"""This function returns an action that display existing pickings of
given batch picking.
"""
self.ensure_one()
pickings = self.mapped('picking_ids')
action = self.env.ref('stock.action_picking_tree_all').read([])[0]
action['domain'] = [('id', 'in', pickings.ids)]
return action