stock_move.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Copyright 2015 Vauxoo
  5. # Author: Luis Torres, Osval Reyes
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as
  9. # published by the Free Software Foundation, either version 3 of the
  10. # License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. from openerp import _, exceptions, models
  22. class StockMove(models.Model):
  23. _inherit = 'stock.move'
  24. def get_operations_as_action_done(self, cr, uid, ids,
  25. context=None):
  26. """Get stock.pack.operation from stock move
  27. Copied from 'action_done' original method
  28. I don't know if use directly move.linked_move_operation_ids
  29. or move.pack_operation_ids is a good idea.
  30. But I prefer use native extract data.
  31. Move don't exists 'lot_id', then this is necessary to get it
  32. """
  33. # Search operations that are linked to the moves
  34. operations = set()
  35. for move in self.browse(cr, uid, ids, context=context):
  36. for link in move.linked_move_operation_ids:
  37. operations.add(link.operation_id)
  38. # Sort operations according to entire packages first,
  39. # then package + lot, package only, lot only
  40. operations = list(operations)
  41. # This part not is necessary
  42. # operations.sort(key=lambda x: (
  43. # (x.package_id and not x.product_id) and -4 or 0) \
  44. # + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0))
  45. return operations
  46. def check_after_action_done(self, cr, uid, operation_or_move,
  47. context=None):
  48. """
  49. Method to check operation or move plus lot_id
  50. easiest inherit cases after action done
  51. """
  52. return True
  53. def check_before_action_done(self, cr, uid, operation_or_move,
  54. context=None):
  55. """
  56. Method to check operation or move plus lot_id
  57. easiest inherit cases before action done
  58. """
  59. self.check_before_done_no_negative(
  60. cr, uid, operation_or_move.product_id.id,
  61. operation_or_move.product_uom_id.id,
  62. operation_or_move.product_qty,
  63. operation_or_move.location_id.id,
  64. lot_id=operation_or_move.lot_id.id,
  65. context=context)
  66. return True
  67. def action_done(self, cr, uid, ids, context=None):
  68. """
  69. Method to enable check operation or move
  70. We need this check after of process move to get
  71. real quantity after of this moves and we need this check
  72. before of process move to validate with no real quantity
  73. """
  74. # operations before done
  75. operations_before_done = self.get_operations_as_action_done(
  76. cr, uid, ids, context=context)
  77. for operation in operations_before_done:
  78. self.check_before_action_done(
  79. cr, uid, operation, context=context)
  80. res = super(StockMove, self).action_done(
  81. cr, uid, ids, context=context)
  82. # operations after done
  83. operations_after_done = self.get_operations_as_action_done(
  84. cr, uid, ids, context=context)
  85. for operation in operations_after_done:
  86. self.check_after_action_done(
  87. cr, uid, operation, context=context)
  88. return res
  89. def check_before_done_no_negative(
  90. self, cr, uid,
  91. product_id, product_uom, product_qty,
  92. location_id, lot_id=None,
  93. context=None):
  94. """
  95. Check quantity no negative from a location.
  96. if product has active check_no_negative and
  97. location is internal
  98. Use lot_id if exists to get quantity available
  99. @param self: The object pointer.
  100. @param cr: A database cursor
  101. @param uid: ID of the user currently logged in
  102. @param product_id: integer with product.product to check
  103. @param location_id: integer with stock.location to check
  104. @param lot_id: optional integer with
  105. stock.production.lot to check
  106. @param context: A standard dictionary
  107. @return: True if quantity available >= 0
  108. triggers an exception if quantity available <0
  109. """
  110. product_pool = self.pool.get('product.product')
  111. location_pool = self.pool.get('stock.location')
  112. lot_pool = self.pool.get('stock.production.lot')
  113. product_uom_obj = self.pool.get('product.uom')
  114. product_data = product_pool.read(
  115. cr, uid,
  116. [product_id], ['name', 'check_no_negative', 'uom_id'],
  117. context=context)[0]
  118. if product_data['check_no_negative']:
  119. location_data = location_pool.read(
  120. cr, uid, [location_id],
  121. ['complete_name', 'usage'],
  122. context=context)[0]
  123. if location_data['usage'] == 'internal':
  124. ctx = context.copy()
  125. ctx.update({
  126. 'lot_id': lot_id,
  127. 'location': location_id,
  128. 'compute_child': True,
  129. })
  130. qty_available_before_done = product_pool.read(
  131. cr, uid,
  132. [product_id], ['qty_available'],
  133. context=ctx)[0]['qty_available']
  134. default_uom_id = product_data['uom_id'][0]
  135. qty_computed = product_uom_obj._compute_qty(
  136. cr, uid, product_uom, product_qty, default_uom_id)
  137. qty_available_after_done = \
  138. qty_available_before_done - qty_computed
  139. if qty_available_after_done < 0:
  140. lot_msg_str = ""
  141. if lot_id:
  142. lot_data = lot_pool.read(
  143. cr, uid,
  144. [lot_id], ['name'],
  145. context=context)[0]
  146. lot_msg_str = _(
  147. " with the lot/serial '%s' "
  148. ) % lot_data['name']
  149. raise exceptions.ValidationError(_(
  150. "Product '%s' has active "
  151. "'check no negative' \n"
  152. "but with this move "
  153. "you will have a quantity of "
  154. "'%s' \n%sin location \n'%s'"
  155. ) % (product_data['name'],
  156. qty_available_after_done,
  157. lot_msg_str,
  158. location_data['complete_name'],))
  159. return True