stock_batch_picking.py 6.93 KB
Newer Older
François C. committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
# -*- coding: utf-8 -*-
# © 2012-2014 Alexandre Fayolle, Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import _, api, fields, models

from openerp.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_ids = fields.One2many(
        'stock.move',
        readonly=True,
        string='Related stock moves',
        compute='_compute_move_ids'
    )

    pack_operation_ids = fields.One2many(
        'stock.pack.operation',
        readonly=True,
        string='Related pack operations',
        compute='_compute_pack_operation_ids'
    )

    pack_operation_product_ids = fields.One2many(
        'stock.pack.operation',
        string='Related pack operations',
        compute='_compute_pack_operation_product_ids',
        # We need an inverse (even if empty lambda) so that pack operation
        # can be saved (when modifying qty_done).
        inverse=lambda *args, **kwargs: None,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}
    )

    pack_operation_pack_ids = fields.One2many(
        'stock.pack.operation',
        string='Related pack operations',
        compute='_compute_pack_operation_pack_ids',
        inverse=lambda *args, **kwargs: None,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}
    )

    @api.depends('picking_ids')
    def _compute_move_ids(self):
        for batch in self:
            batch.move_ids = batch.picking_ids.mapped("move_lines")

    @api.depends('picking_ids')
    def _compute_pack_operation_ids(self):
        for batch in self:
            batch.pack_operation_ids = batch.picking_ids.mapped(
                'pack_operation_ids'
            )

    @api.depends('picking_ids')
    def _compute_pack_operation_product_ids(self):
        for batch in self:
            batch.pack_operation_product_ids = batch.picking_ids.mapped(
                'pack_operation_product_ids'
            )

    @api.depends('picking_ids')
    def _compute_pack_operation_pack_ids(self):
        for batch in self:
            batch.pack_operation_pack_ids = batch.picking_ids.mapped(
                'pack_operation_pack_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.pack_operation_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()