221 lines
7.1 KiB
Python
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
|