diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/stock_picking_batch/models | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/stock_picking_batch/models')
| -rw-r--r-- | addons/stock_picking_batch/models/__init__.py | 5 | ||||
| -rw-r--r-- | addons/stock_picking_batch/models/stock_picking.py | 34 | ||||
| -rw-r--r-- | addons/stock_picking_batch/models/stock_picking_batch.py | 242 |
3 files changed, 281 insertions, 0 deletions
diff --git a/addons/stock_picking_batch/models/__init__.py b/addons/stock_picking_batch/models/__init__.py new file mode 100644 index 00000000..a5f3f556 --- /dev/null +++ b/addons/stock_picking_batch/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import stock_picking +from . import stock_picking_batch diff --git a/addons/stock_picking_batch/models/stock_picking.py b/addons/stock_picking_batch/models/stock_picking.py new file mode 100644 index 00000000..be5bb59f --- /dev/null +++ b/addons/stock_picking_batch/models/stock_picking.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + batch_id = fields.Many2one( + 'stock.picking.batch', string='Batch Transfer', + check_company=True, + states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, + help='Batch associated to this transfer', copy=False) + + @api.model + def create(self, vals): + res = super().create(vals) + if vals.get('batch_id'): + res.batch_id._sanity_check() + return res + + def write(self, vals): + res = super().write(vals) + if vals.get('batch_id'): + if not self.batch_id.picking_type_id: + self.batch_id.picking_type_id = self.picking_type_id[0] + self.batch_id._sanity_check() + return res + + def _should_show_transfers(self): + if len(self.batch_id) == 1 and self == self.batch_id.picking_ids: + return False + return super()._should_show_transfers() diff --git a/addons/stock_picking_batch/models/stock_picking_batch.py b/addons/stock_picking_batch/models/stock_picking_batch.py new file mode 100644 index 00000000..df23497e --- /dev/null +++ b/addons/stock_picking_batch/models/stock_picking_batch.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError +from odoo.tools.float_utils import float_compare, float_is_zero, float_round + +class StockPickingBatch(models.Model): + _inherit = ['mail.thread', 'mail.activity.mixin'] + _name = "stock.picking.batch" + _description = "Batch Transfer" + _order = "name desc" + + name = fields.Char( + string='Batch Transfer', default='New', + copy=False, required=True, readonly=True, + help='Name of the batch transfer') + user_id = fields.Many2one( + 'res.users', string='Responsible', tracking=True, check_company=True, + readonly=True, states={'draft': [('readonly', False)], 'in_progress': [('readonly', False)]}, + help='Person responsible for this batch transfer') + company_id = fields.Many2one( + 'res.company', string="Company", required=True, readonly=True, + index=True, default=lambda self: self.env.company) + picking_ids = fields.One2many( + 'stock.picking', 'batch_id', string='Transfers', readonly=True, + domain="[('id', 'in', allowed_picking_ids)]", check_company=True, + states={'draft': [('readonly', False)], 'in_progress': [('readonly', False)]}, + help='List of transfers associated to this batch') + show_check_availability = fields.Boolean( + compute='_compute_move_ids', + help='Technical field used to compute whether the check availability button should be shown.') + allowed_picking_ids = fields.One2many('stock.picking', compute='_compute_allowed_picking_ids') + move_ids = fields.One2many( + 'stock.move', string="Stock moves", compute='_compute_move_ids') + move_line_ids = fields.One2many( + 'stock.move.line', string='Stock move lines', + compute='_compute_move_ids', inverse='_set_move_line_ids', readonly=True, + states={'draft': [('readonly', False)], 'in_progress': [('readonly', False)]}) + state = fields.Selection([ + ('draft', 'Draft'), + ('in_progress', 'In progress'), + ('done', 'Done'), + ('cancel', 'Cancelled')], default='draft', + store=True, compute='_compute_state', + copy=False, tracking=True, required=True, readonly=True) + picking_type_id = fields.Many2one( + 'stock.picking.type', 'Operation Type', check_company=True, copy=False, + readonly=True, states={'draft': [('readonly', False)]}) + scheduled_date = fields.Datetime( + 'Scheduled Date', copy=False, store=True, readonly=False, compute="_compute_scheduled_date", + states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, + help="""Scheduled date for the transfers to be processed. + - If manually set then scheduled date for all transfers in batch will automatically update to this date. + - If not manually changed and transfers are added/removed/updated then this will be their earliest scheduled date + but this scheduled date will not be set for all transfers in batch.""") + + @api.depends('company_id', 'picking_type_id', 'state') + def _compute_allowed_picking_ids(self): + allowed_picking_states = ['waiting', 'confirmed', 'assigned'] + cancelled_batchs = self.env['stock.picking.batch'].search_read( + [('state', '=', 'cancel')], ['id'] + ) + cancelled_batch_ids = [batch['id'] for batch in cancelled_batchs] + + for batch in self: + domain_states = list(allowed_picking_states) + # Allows to add draft pickings only if batch is in draft as well. + if batch.state == 'draft': + domain_states.append('draft') + domain = [ + ('company_id', '=', batch.company_id.id), + ('immediate_transfer', '=', False), + ('state', 'in', domain_states), + '|', + '|', + ('batch_id', '=', False), + ('batch_id', '=', batch.id), + ('batch_id', 'in', cancelled_batch_ids), + ] + if batch.picking_type_id: + domain += [('picking_type_id', '=', batch.picking_type_id.id)] + batch.allowed_picking_ids = self.env['stock.picking'].search(domain) + + @api.depends('picking_ids', 'picking_ids.move_line_ids', 'picking_ids.move_lines', 'picking_ids.move_lines.state') + def _compute_move_ids(self): + for batch in self: + batch.move_ids = batch.picking_ids.move_lines + batch.move_line_ids = batch.picking_ids.move_line_ids + batch.show_check_availability = any(m.state not in ['assigned', 'done'] for m in batch.move_ids) + + @api.depends('picking_ids', 'picking_ids.state') + def _compute_state(self): + batchs = self.filtered(lambda batch: batch.state not in ['cancel', 'done']) + for batch in batchs: + if not batch.picking_ids: + return + # Cancels automatically the batch picking if all its transfers are cancelled. + if all(picking.state == 'cancel' for picking in batch.picking_ids): + batch.state = 'cancel' + # Batch picking is marked as done if all its not canceled transfers are done. + elif all(picking.state in ['cancel', 'done'] for picking in batch.picking_ids): + batch.state = 'done' + + @api.depends('picking_ids', 'picking_ids.scheduled_date') + def _compute_scheduled_date(self): + self.scheduled_date = min(self.picking_ids.filtered('scheduled_date').mapped('scheduled_date'), default=False) + + @api.onchange('scheduled_date') + def onchange_scheduled_date(self): + if self.scheduled_date: + self.picking_ids.scheduled_date = self.scheduled_date + + def _set_move_line_ids(self): + new_move_lines = self[0].move_line_ids + for picking in self.picking_ids: + old_move_lines = picking.move_line_ids + picking.move_line_ids = new_move_lines.filtered(lambda ml: ml.picking_id.id == picking.id) + move_lines_to_unlink = old_move_lines - new_move_lines + if move_lines_to_unlink: + move_lines_to_unlink.unlink() + + # ------------------------------------------------------------------------- + # CRUD + # ------------------------------------------------------------------------- + @api.model + def create(self, vals): + if vals.get('name', '/') == '/': + vals['name'] = self.env['ir.sequence'].next_by_code('picking.batch') or '/' + return super().create(vals) + + def write(self, vals): + res = super().write(vals) + if vals.get('picking_type_id'): + self._sanity_check() + if vals.get('picking_ids'): + batch_without_picking_type = self.filtered(lambda batch: not batch.picking_type_id) + if batch_without_picking_type: + picking = self.picking_ids and self.picking_ids[0] + batch_without_picking_type.picking_type_id = picking.picking_type_id.id + return res + + def unlink(self): + if any(batch.state != 'draft' for batch in self): + raise UserError(_("You can only delete draft batch transfers.")) + return super().unlink() + + def onchange(self, values, field_name, field_onchange): + """Override onchange to NOT to update all scheduled_date on pickings when + scheduled_date on batch is updated by the change of scheduled_date on pickings. + """ + result = super().onchange(values, field_name, field_onchange) + if field_name == 'picking_ids' and 'value' in result: + for line in result['value'].get('picking_ids', []): + if line[0] < 2 and 'scheduled_date' in line[2]: + del line[2]['scheduled_date'] + return result + + # ------------------------------------------------------------------------- + # Action methods + # ------------------------------------------------------------------------- + def action_confirm(self): + """Sanity checks, confirm the pickings and mark the batch as confirmed.""" + self.ensure_one() + if not self.picking_ids: + raise UserError(_("You have to set some pickings to batch.")) + self.picking_ids.action_confirm() + self._check_company() + self.state = 'in_progress' + return True + + def action_cancel(self): + self.ensure_one() + self.state = 'cancel' + return True + + def action_print(self): + self.ensure_one() + return self.env.ref('stock_picking_batch.action_report_picking_batch').report_action(self) + + def action_done(self): + self.ensure_one() + self._check_company() + pickings = self.mapped('picking_ids').filtered(lambda picking: picking.state not in ('cancel', 'done')) + if any(picking.state not in ('assigned', 'confirmed') for picking in pickings): + raise UserError(_('Some transfers are still waiting for goods. Please check or force their availability before setting this batch to done.')) + + for picking in pickings: + picking.message_post( + body="<b>%s:</b> %s <a href=#id=%s&view_type=form&model=stock.picking.batch>%s</a>" % ( + _("Transferred by"), + _("Batch Transfer"), + picking.batch_id.id, + picking.batch_id.name)) + return pickings.button_validate() + + def action_assign(self): + self.ensure_one() + self.picking_ids.action_assign() + + def action_put_in_pack(self): + """ Action to put move lines with 'Done' quantities into a new pack + This method follows same logic to stock.picking. + """ + self.ensure_one() + if self.state not in ('done', 'cancel'): + picking_move_lines = self.move_line_ids + + move_line_ids = picking_move_lines.filtered(lambda ml: + float_compare(ml.qty_done, 0.0, precision_rounding=ml.product_uom_id.rounding) > 0 + and not ml.result_package_id + ) + if not move_line_ids: + move_line_ids = picking_move_lines.filtered(lambda ml: float_compare(ml.product_uom_qty, 0.0, + precision_rounding=ml.product_uom_id.rounding) > 0 and float_compare(ml.qty_done, 0.0, + precision_rounding=ml.product_uom_id.rounding) == 0) + if move_line_ids: + res = self.picking_ids[0]._pre_put_in_pack_hook(move_line_ids) + if not res: + res = self.picking_ids[0]._put_in_pack(move_line_ids, False) + return res + else: + raise UserError(_("Please add 'Done' quantities to the batch picking to create a new pack.")) + + # ------------------------------------------------------------------------- + # Miscellaneous + # ------------------------------------------------------------------------- + def _sanity_check(self): + for batch in self: + if not batch.picking_ids <= batch.allowed_picking_ids: + erroneous_pickings = batch.picking_ids - batch.allowed_picking_ids + raise UserError(_( + "The following transfers cannot be added to batch transfer %s. " + "Please check their states and operation types, if they aren't immediate " + "transfers or if they're not already part of another batch transfer.\n\n" + "Incompatibilities: %s", batch.name, ', '.join(erroneous_pickings.mapped('name')))) + + def _track_subtype(self, init_values): + if 'state' in init_values: + return self.env.ref('stock_picking_batch.mt_batch_state') + return super()._track_subtype(init_values) + |
