InventoryLine.py 12.1 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2
##############################################################################
#
3 4
# Copyright (c) 2002-2005 Nexedi SARL and Contributors. All Rights Reserved.
#                         Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from AccessControl import ClassSecurityInfo
30
from Acquisition import aq_base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31

32
from Products.ERP5Type import Permissions, PropertySheet, Interface
Jean-Paul Smets's avatar
Jean-Paul Smets committed
33 34 35 36 37 38 39 40
from Products.ERP5Type.XMLMatrix import XMLMatrix
from Products.ERP5.Document.DeliveryLine import DeliveryLine
from Products.ERP5.Document.Movement import Movement

from zLOG import LOG

class InventoryLine(DeliveryLine):
    """
41
      An Inventory Line describe the inventory of a resource, by variations.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42 43 44 45
    """

    meta_type = 'ERP5 Inventory Line'
    portal_type = 'Inventory Line'
46
    add_permission = Permissions.AddPortalContent
Jean-Paul Smets's avatar
Jean-Paul Smets committed
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    isPortalContent = 1
    isRADContent = 1

    # Declarative security
    security = ClassSecurityInfo()
    security.declareObjectProtected(Permissions.View)

    # Declarative interfaces
    __implements__ = ( Interface.Variated, )

    # Declarative properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
                      , PropertySheet.CategoryCore
                      , PropertySheet.Amount
                      , PropertySheet.Inventory
                      , PropertySheet.Task
                      , PropertySheet.Arrow
                      , PropertySheet.Movement
                      , PropertySheet.VariationRange
                      , PropertySheet.ItemAggregation
                      )

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    def _edit(self, REQUEST=None, force_update = 0, **kw):
      kw = kw.copy()
      item_id_list = kw.get('item_id_list', None)
      if item_id_list is not None: del kw['item_id_list']
      produced_item_id_list = kw.get('produced_item_id_list', None)
      if produced_item_id_list is not None: del kw['produced_item_id_list']
      consumed_item_id_list = kw.get('consumed_item_id_list', None)
      if consumed_item_id_list is not None: del kw['consumed_item_id_list']
      DeliveryLine._edit(self, REQUEST=REQUEST, force_update = force_update, **kw)
      # Update consumption last
      if item_id_list is not None:
        self._setItemIdList(item_id_list)
      if produced_item_id_list is not None :
        self._setProducedItemIdList(produced_item_id_list)
      if consumed_item_id_list is not None :
        self._setConsumedItemIdList(consumed_item_id_list)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
87 88 89 90 91 92 93 94 95 96
    security.declareProtected(Permissions.AccessContentsInformation, 'getTotalInventory')
    def getTotalInventory(self):
      """
        Returns the inventory if no cell or the total inventory if cells
      """
      if not self.hasCellContent():
        return self.getInventory()
      else:
        # Use MySQL
        aggregate = self.InventoryLine_zGetTotal()[0]
97
        return aggregate.total_inventory or 0.0
Jean-Paul Smets's avatar
Jean-Paul Smets committed
98 99 100 101 102 103 104 105 106 107 108

    security.declareProtected(Permissions.AccessContentsInformation, 'getQuantity')
    def getQuantity(self):
      """
        Computes a quantity which allows to reach inventory
      """
      if not self.hasCellContent():
        # First check if quantity already exists
        quantity = self._baseGetQuantity()
        if quantity not in (0.0, 0, None):
          return quantity
109 110 111
        # Make sure inventory is defined somewhere (here or parent)
        if getattr(aq_base(self), 'inventory', None) is None:
          return 0.0 # No inventory defined, so no quantity
Jean-Paul Smets's avatar
Jean-Paul Smets committed
112
        # Find total of movements in the past - XXX
113 114 115 116
        resource_value = self.getResourceValue()
        if resource_value is not None:
          # Inventories can only be done in "real" locations / sectinos, not categories thereof
          #  -> therefore we use node and section
117 118 119 120 121 122 123
          current_inventory = resource_value.getInventory( \
                                  at_date          = self.getStartDate()
                                , variation_text   = self.getVariationText()
                                , node             = self.getDestination()
                                , section_category = self.getDestinationSection()
                                , simulation_state = self.getPortalCurrentInventoryStateList()
                                )
124
          inventory = self.getInventory()
125 126
          if current_inventory in (None, ''):
            current_inventory = 0.0
127 128
          return self.getInventory() - current_inventory
        return self.getInventory()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
129 130 131
      else:
        return None

132 133 134 135 136
    ### (kev) This method can't be deleted as long as Delivery.newCellContent() has its own
    # XMLMatrix.newCellContent() method. The latter is kept because we fear its deletion will
    # break existing scripts and code in ERP5. (kev)
    security.declareProtected(Permissions.ModifyPortalContent, 'newCellContent')
    newCellContent = XMLMatrix.newCellContent
Jean-Paul Smets's avatar
Jean-Paul Smets committed
137 138 139 140 141 142

    def _setItemIdList(self, value):
      """
        Computes total_quantity of all given items and stores this total_quantity
        in the inventory attribute of the cell
      """
143 144
      if value is None:
        return
145
      previous_item_list = self.getAggregateValueList()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
146 147
      given_item_id_list = value
      item_object_list = []
148 149 150 151
      for item in given_item_id_list:
        item_result_list = self.portal_catalog(id=item, portal_type="Piece Tissu")
        if len(item_result_list) == 1:
          try:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
152 153 154
            object = item_result_list[0].getObject()
          except :
            object = None
155
        else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
156
          object = None
157
        if object is not None:
158
          # if item was in previous_item_list keep it
159
          if object in previous_item_list:
160 161 162
            # we can add this item to the list of aggregated items
            item_object_list.append(object)
          # if new item verify if variated_resource of item == variated_resource of movement
163 164
          elif (self.getResource() == object.getResource()) \
           and (self.getVariationCategoryList() == object.getVariationCategoryList()):
165 166
            # we can add this item to the list of aggregated items
            item_object_list.append(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
167 168 169
      # update item_id_list and build relation
      self.setAggregateValueList(item_object_list)
      # update inventory if needed
170
      if len(item_object_list) > 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
171
        quantity = 0
172
        for object_item in item_object_list:
173
          quantity += object_item.getQuantity()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
174 175 176 177 178 179 180
        self.setInventory(quantity)

    def _setProducedItemIdList(self, value):
      """
        Computes total_quantity of all given items and stores this total_quantity
        in the quantity attribute of the cell
      """
181 182
      if value is None:
        return
183
      previous_item_list = self.getAggregateValueList()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
184 185
      given_item_id_list = value
      item_object_list = []
186 187 188 189
      for item in given_item_id_list:
        item_result_list = self.portal_catalog(id=item, portal_type="Piece Tissu")
        if len(item_result_list) == 1:
          try:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
190
            object = item_result_list[0].getObject()
191
          except:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
192
            object = None
193
        else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
194
          object = None
195
        if object is not None:
196
          # if item was in previous_item_list keep it
197
          if object in previous_item_list:
198 199 200
            # we can add this item to the list of aggregated items
            item_object_list.append(object)
          # if new item verify if variated_resource of item == variated_resource of movement
201 202
          elif (self.getResource() == object.getResource()) \
           and (self.getVariationCategoryList() == object.getVariationCategoryList()):
203 204
            # now verify if item can be moved (not already done)
            last_location_title = object.getLastLocationTitle()
205
            if self.getDestinationTitle() != last_location_title or last_location_title == '':
206 207
              # we can add this item to the list of aggregated items
              item_object_list.append(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
208 209 210
      # update item_id_list and build relation
      self.setAggregateValueList(item_object_list)
      # update inventory if needed
211
      if len(item_object_list) > 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
212
        quantity = 0
213
        for object_item in item_object_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
214 215 216 217 218 219 220 221
          quantity += object_item.getQuantity()
        self.setProductionQuantity(quantity)

    def _setConsumedItemIdList(self, value):
      """
        Computes total_quantity of all given items and stores this total_quantity
        in the quantity attribute of the cell
      """
222 223
      if value is None:
        return
224
      previous_item_list = self.getAggregateValueList()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
225 226
      given_item_id_list = value
      item_object_list = []
227 228 229 230
      for item in given_item_id_list:
        item_result_list = self.portal_catalog(id=item, portal_type="Piece Tissu")
        if len(item_result_list) == 1:
          try:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
231
            object = item_result_list[0].getObject()
232
          except:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
233
            object = None
234
        else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
235
          object = None
236
        if object is not None:
237
          # if item was in previous_item_list keep it
238
          if object in previous_item_list:
239 240 241
            # we can add this item to the list of aggregated items
            item_object_list.append(object)
          # if new item verify if variated_resource of item == variated_resource of movement
242 243
          elif (self.getResource() == object.getResource()) \
           and (self.getVariationCategoryList() == object.getVariationCategoryList()):
244 245
            # now verify if item can be moved (not already done)
            last_location_title = object.getLastLocationTitle()
246
            if self.getDestinationTitle() == last_location_title or last_location_title == '':
247 248
              # we can add this item to the list of aggregated items
              item_object_list.append(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
249 250 251
      # update item_id_list and build relation
      self.setAggregateValueList(item_object_list)
      # update inventory if needed
252
      if len(item_object_list) > 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
253
        quantity = 0
254
        for object_item in item_object_list:
255
          quantity += object_item.getRemainingQuantity()
256 257
          # we reset the location of the item
          object_item.setLocation('')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
258 259 260 261 262 263
        self.setConsumptionQuantity(quantity)

    def getProducedItemIdList(self):
      """
        Returns list of items if production_quantity != 0.0
      """
264
      if self.getProductionQuantity() != 0.0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
265
        return self.getItemIdList()
266
      else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
267 268 269 270 271 272
        return []

    def getConsumedItemIdList(self):
      """
        Returns list of items if consumption_quantity != 0.0
      """
273
      if self.getConsumptionQuantity() != 0.0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
274
        return self.getItemIdList()
275
      else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
276
        return []
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293

    # Inventory cataloging
    security.declareProtected(Permissions.AccessContentsInformation, 'getConvertedInventory')
    def getConvertedInventory(self):
      """
        provides a default inventory value - None since
        no inventory was defined.
      """
      return self.getInventory() # XXX quantity unit is missing

    # Required for indexing
    security.declareProtected(Permissions.AccessContentsInformation, 'getInventoriatedQuantity')
    def getInventoriatedQuantity(self):
      """
        Take into account efficiency in converted target quantity
      """
      return Movement.getInventoriatedQuantity(self)