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
 |