# Copyright 2015 Mikel Arregi - AvanzOSC # Copyright 2015 Oihane Crucelaegui - AvanzOSC # Copyright 2018 Fanha Giang # Copyright 2018 Tecnativa - Vicent Cubells # Copyright 2018 Tecnativa - Pedro M. Baeza # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html from odoo import _, api, fields, models from odoo.addons import decimal_precision as dp from odoo.exceptions import ValidationError from odoo.tools.float_utils import float_compare class AssignManualQuants(models.TransientModel): _name = 'assign.manual.quants' _description = "Assign Manual Quants" @api.multi @api.constrains('quants_lines') def _check_qty(self): precision_digits = self.env[ 'decimal.precision'].precision_get('Product Unit of Measure') for record in self.filtered('quants_lines'): if float_compare(record.lines_qty, record.move_id.product_qty, precision_digits=precision_digits) > 0: raise ValidationError( _('Quantity is higher than the needed one')) @api.depends('move_id', 'quants_lines', 'quants_lines.qty') def _compute_qties(self): for record in self: record.lines_qty = sum( record.quants_lines.filtered('selected').mapped('qty') ) record.move_qty = record.move_id.product_qty - record.lines_qty lines_qty = fields.Float( string='Reserved qty', compute='_compute_qties', digits=dp.get_precision('Product Unit of Measure')) move_qty = fields.Float(string='Remaining qty', compute='_compute_qties', digits=dp.get_precision('Product Unit of Measure')) quants_lines = fields.One2many('assign.manual.quants.lines', 'assign_wizard', string='Quants') move_id = fields.Many2one( comodel_name='stock.move', string='Move', ) @api.multi def assign_quants(self): move = self.move_id move._do_unreserve() for line in self.quants_lines: line._assign_quant_line() # Auto-fill all lines as done for ml in move.move_line_ids: ml.qty_done = ml.product_qty move._recompute_state() move.mapped('picking_id')._compute_state() return {} @api.model def default_get(self, fields): res = super(AssignManualQuants, self).default_get(fields) move = self.env['stock.move'].browse(self.env.context['active_id']) available_quants = self.env['stock.quant'].search([ ('location_id', 'child_of', move.location_id.id), ('product_id', '=', move.product_id.id), ('quantity', '>', 0), ]) quants_lines = [] for quant in available_quants: line = {} line['quant_id'] = quant.id line['on_hand'] = quant.quantity line['location_id'] = quant.location_id.id line['lot_id'] = quant.lot_id.id line['package_id'] = quant.package_id.id line['owner_id'] = quant.owner_id.id line['selected'] = False move_lines = move.move_line_ids.filtered( lambda ml: (ml.location_id == quant.location_id and ml.lot_id == quant.lot_id and ml.owner_id == quant.owner_id and ml.package_id == quant.package_id) ) line['qty'] = sum(move_lines.mapped('product_uom_qty')) line['selected'] = bool(line['qty']) line['reserved'] = quant.reserved_quantity - line['qty'] quants_lines.append(line) res.update({ 'quants_lines': [(0, 0, x) for x in quants_lines], 'move_id': move.id, }) return res class AssignManualQuantsLines(models.TransientModel): _name = 'assign.manual.quants.lines' _description = "Assign Manual Quants Lines" _rec_name = 'quant_id' assign_wizard = fields.Many2one( comodel_name='assign.manual.quants', string='Move', required=True, ondelete='cascade') quant_id = fields.Many2one( comodel_name='stock.quant', string='Quant', required=True, ondelete='cascade', oldname='quant') location_id = fields.Many2one( comodel_name='stock.location', string='Location', related='quant_id.location_id', groups="stock.group_stock_multi_locations" ) lot_id = fields.Many2one( comodel_name='stock.production.lot', string='Lot', related='quant_id.lot_id', groups="stock.group_production_lot") package_id = fields.Many2one( comodel_name='stock.quant.package', string='Package', related='quant_id.package_id', groups="stock.group_tracking_lot") owner_id = fields.Many2one( comodel_name='res.partner', string='Owner', related='quant_id.owner_id', groups="stock.group_tracking_owner", ) # This is not correctly shown as related or computed, so we make it regular on_hand = fields.Float( readonly=True, string='On Hand', digits=dp.get_precision('Product Unit of Measure'), ) reserved = fields.Float( string='Others Reserved', digits=dp.get_precision('Product Unit of Measure')) selected = fields.Boolean(string='Select') qty = fields.Float( string='QTY', digits=dp.get_precision('Product Unit of Measure')) @api.onchange('selected') def _onchange_selected(self): for record in self: if not record.selected: record.qty = 0 elif not record.qty: # This takes current "snapshot" situation, so that we don't # have to compute each time if current reserved quantity is # for this current move. If other operations change available # quantity on quant, a constraint would be raised later on # validation. quant_qty = record.on_hand - record.reserved remaining_qty = record.assign_wizard.move_qty record.qty = min(quant_qty, remaining_qty) @api.multi @api.constrains('qty') def _check_qty(self): precision_digits = self.env[ 'decimal.precision' ].precision_get('Product Unit of Measure') for record in self.filtered('qty'): quant = record.quant_id move_lines = record.assign_wizard.move_id.move_line_ids.filtered( lambda ml: (ml.location_id == quant.location_id and ml.lot_id == quant.lot_id) ) reserved = ( quant.reserved_quantity - sum( move_lines.mapped('product_uom_qty')) ) if float_compare(record.qty, record.quant_id.quantity - reserved, precision_digits=precision_digits) > 0: raise ValidationError( _('Selected line quantity is higher than the available ' 'one. Maybe an operation with this product has been ' 'done meanwhile or you have manually increased the ' 'suggested value.') ) def _assign_quant_line(self): self.ensure_one() quant = self.env['stock.quant'] precision_digits = self.env[ 'decimal.precision'].precision_get('Product Unit of Measure') move = self.assign_wizard.move_id if float_compare(self.qty, 0.0, precision_digits=precision_digits) > 0: available_quantity = quant._get_available_quantity( move.product_id, self.location_id, lot_id=self.lot_id, package_id=self.package_id, owner_id=self.owner_id, ) if float_compare( available_quantity, 0.0, precision_digits=precision_digits ) <= 0: return move._update_reserved_quantity( self.qty, available_quantity, self.location_id, lot_id=self.lot_id, package_id=self.package_id, owner_id=self.owner_id, strict=True )