# -*- coding: utf-8 -*- ############################################################################## # # Copyright 2015 Vauxoo # Author: Luis Torres, Osval Reyes # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # ############################################################################## from openerp import _, exceptions, models class StockMove(models.Model): _inherit = 'stock.move' def get_operations_as_action_done(self, cr, uid, ids, context=None): """Get stock.pack.operation from stock move Copied from 'action_done' original method I don't know if use directly move.linked_move_operation_ids or move.pack_operation_ids is a good idea. But I prefer use native extract data. Move don't exists 'lot_id', then this is necessary to get it """ # Search operations that are linked to the moves operations = set() for move in self.browse(cr, uid, ids, context=context): for link in move.linked_move_operation_ids: operations.add(link.operation_id) # Sort operations according to entire packages first, # then package + lot, package only, lot only operations = list(operations) # This part not is necessary # operations.sort(key=lambda x: ( # (x.package_id and not x.product_id) and -4 or 0) \ # + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0)) return operations def check_after_action_done(self, cr, uid, operation_or_move, context=None): """ Method to check operation or move plus lot_id easiest inherit cases after action done """ return True def check_before_action_done(self, cr, uid, operation_or_move, context=None): """ Method to check operation or move plus lot_id easiest inherit cases before action done """ self.check_before_done_no_negative( cr, uid, operation_or_move.product_id.id, operation_or_move.product_uom_id.id, operation_or_move.product_qty, operation_or_move.location_id.id, lot_id=operation_or_move.lot_id.id, context=context) return True def action_done(self, cr, uid, ids, context=None): """ Method to enable check operation or move We need this check after of process move to get real quantity after of this moves and we need this check before of process move to validate with no real quantity """ # operations before done operations_before_done = self.get_operations_as_action_done( cr, uid, ids, context=context) for operation in operations_before_done: self.check_before_action_done( cr, uid, operation, context=context) res = super(StockMove, self).action_done( cr, uid, ids, context=context) # operations after done operations_after_done = self.get_operations_as_action_done( cr, uid, ids, context=context) for operation in operations_after_done: self.check_after_action_done( cr, uid, operation, context=context) return res def check_before_done_no_negative( self, cr, uid, product_id, product_uom, product_qty, location_id, lot_id=None, context=None): """ Check quantity no negative from a location. if product has active check_no_negative and location is internal Use lot_id if exists to get quantity available @param self: The object pointer. @param cr: A database cursor @param uid: ID of the user currently logged in @param product_id: integer with product.product to check @param location_id: integer with stock.location to check @param lot_id: optional integer with stock.production.lot to check @param context: A standard dictionary @return: True if quantity available >= 0 triggers an exception if quantity available <0 """ product_pool = self.pool.get('product.product') location_pool = self.pool.get('stock.location') lot_pool = self.pool.get('stock.production.lot') product_uom_obj = self.pool.get('product.uom') product_data = product_pool.read( cr, uid, [product_id], ['name', 'check_no_negative', 'uom_id'], context=context)[0] if product_data['check_no_negative']: location_data = location_pool.read( cr, uid, [location_id], ['complete_name', 'usage'], context=context)[0] if location_data['usage'] == 'internal': ctx = context.copy() ctx.update({ 'lot_id': lot_id, 'location': location_id, 'compute_child': True, }) qty_available_before_done = product_pool.read( cr, uid, [product_id], ['qty_available'], context=ctx)[0]['qty_available'] default_uom_id = product_data['uom_id'][0] qty_computed = product_uom_obj._compute_qty( cr, uid, product_uom, product_qty, default_uom_id) qty_available_after_done = \ qty_available_before_done - qty_computed if qty_available_after_done < 0: lot_msg_str = "" if lot_id: lot_data = lot_pool.read( cr, uid, [lot_id], ['name'], context=context)[0] lot_msg_str = _( " with the lot/serial '%s' " ) % lot_data['name'] raise exceptions.ValidationError(_( "Product '%s' has active " "'check no negative' \n" "but with this move " "you will have a quantity of " "'%s' \n%sin location \n'%s'" ) % (product_data['name'], qty_available_after_done, lot_msg_str, location_data['complete_name'],)) return True