summaryrefslogtreecommitdiff
path: root/addons/stock_picking_batch/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/stock_picking_batch/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/stock_picking_batch/models')
-rw-r--r--addons/stock_picking_batch/models/__init__.py5
-rw-r--r--addons/stock_picking_batch/models/stock_picking.py34
-rw-r--r--addons/stock_picking_batch/models/stock_picking_batch.py242
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)
+