InventoryCell.py 12.8 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2
##############################################################################
#
Kevin Deldycke's avatar
Kevin Deldycke committed
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
#
# 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.
#
##############################################################################

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

32
from Products.ERP5Type import Permissions, PropertySheet, Interface
Jean-Paul Smets's avatar
Jean-Paul Smets committed
33 34 35

from Products.ERP5.Document.DeliveryCell import DeliveryCell

Kevin Deldycke's avatar
Kevin Deldycke committed
36

Jean-Paul Smets's avatar
Jean-Paul Smets committed
37 38
class InventoryCell(DeliveryCell):
    """
Kevin Deldycke's avatar
Kevin Deldycke committed
39 40
      An InventoryCell allows to define specific inventory
      for each variation of a resource in an inventory line.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
41 42 43 44
    """

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

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

    # Declarative interfaces
    __implements__ = ( Interface.Variated, )

    # Declarative properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.CategoryCore
                      , PropertySheet.Amount
                      , PropertySheet.Inventory
                      , PropertySheet.Task
                      , PropertySheet.Movement
                      , PropertySheet.Price
                      , PropertySheet.Predicate
                      , PropertySheet.Domain
                      , PropertySheet.MappedValue
                      , PropertySheet.ItemAggregation
                      )

71 72 73 74 75 76 77 78 79 80 81 82
    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']
      DeliveryCell._edit(self, REQUEST=REQUEST, force_update = force_update, **kw)
      # Update consumption last
      if item_id_list is not None:
        self._setItemIdList(item_id_list)
83
      if produced_item_id_list is not None :
84
        self._setProducedItemIdList(produced_item_id_list)
85
      if consumed_item_id_list is not None :
86
        self._setConsumedItemIdList(consumed_item_id_list)
87

Jean-Paul Smets's avatar
Jean-Paul Smets committed
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
    security.declareProtected(Permissions.AccessContentsInformation, 'getQuantity')
    def getQuantity(self):
      """
        Computes a quantity which allows to reach inventory

        Bug fix method for Coramy purpose - Coramy used production_quantity as property
        list of mapped value which generated errors of stock. It can be safely removed in
        the near future.
      """
      aself = aq_base(self)
      if hasattr(aself, 'production_quantity') or  hasattr(aself, 'consumption_quantity'):
        # Error - we must fix this
        if getattr(aself, 'production_quantity', 0.0) > 0.0:
          self.setProductionQuantity(aself.production_quantity)
        elif getattr(aself, 'consumption_quantity', 0.0) > 0.0:
          self.setConsumptionQuantity(aself.consumption_quantity)
        if hasattr(aself, 'production_quantity'):
          delattr(self, 'production_quantity')
        if hasattr(aself, 'consumption_quantity'):
          delattr(self, 'consumption_quantity')
        if hasattr(self, 'mapped_value_property_list'):
          if 'consumption_quantity' in self.mapped_value_property_list:
110 111
            self.mapped_value_property_list = filter(lambda s: s != 'consumption_quantity'
                                                             , self.mapped_value_property_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
112
          if 'production_quantity' in self.mapped_value_property_list:
113 114
            self.mapped_value_property_list = filter(lambda s: s != 'production_quantity'
                                                             , self.mapped_value_property_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
115 116 117 118 119 120
          if 'quantity' not in self.mapped_value_property_list:
            self.mapped_value_property_list = list(self.mapped_value_property_list) + ['quantity']
      # First check if quantity already exists
      quantity = self._baseGetQuantity()
      if quantity not in (0.0, 0, None):
        return quantity
121 122 123
      # 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
124
      # Find total of movements in the past - XXX
125 126 127 128
      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
129 130 131 132 133 134 135
        current_inventory = resource_value.getInventory( \
                                at_date          = self.getStartDate()
                              , variation_text   = self.getVariationText()
                              , node             = self.getDestination()
                              , section_category = self.getDestinationSection()  # We want to consolidate
                              , simulation_state = self.getPortalCurrentInventoryStateList()
                              )
136
        inventory = self.getInventory()
137 138
        if current_inventory in (None, ''):
          current_inventory = 0.0
139 140
        return self.getInventory() - current_inventory
      return self.getInventory()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
141

142 143 144
    security.declareProtected( Permissions.AccessContentsInformation, 'getInventory' )
    def getInventory(self):
      """
145
        No acquisition for inventories: either defined or None
146 147
      """
      if 'inventory' in self.getMappedValuePropertyList([]):
148
        return getattr(aq_base(self), 'inventory', None)
149 150 151
      else:
        return None # return None

Jean-Paul Smets's avatar
Jean-Paul Smets committed
152 153 154 155 156
    def _setItemIdList(self, value):
      """
        Computes total_quantity of all given items and stores this total_quantity
        in the inventory attribute of the cell
      """
157 158
      if value is None:
        return
159
      previous_item_list = self.getAggregateValueList()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
160 161
      given_item_id_list = value
      item_object_list = []
162 163 164 165
      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
166
            object = item_result_list[0].getObject()
167
          except:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
168
            object = None
169
        else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
170
          object = None
171
        if object is not None:
172
          # if item was in previous_item_list keep it
173
          if object in previous_item_list:
174 175 176
            # 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
177 178
          elif (self.getResource() == object.getResource()) \
           and (self.getVariationCategoryList() == object.getVariationCategoryList()):
179 180
            # 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
181 182 183
      # update item_id_list and build relation
      self.setAggregateValueList(item_object_list)
      # update inventory if needed
184
      if len(item_object_list) > 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
185
        quantity = 0
186
        for object_item in item_object_list:
187
          quantity += object_item.getRemainingQuantity()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
188 189 190 191 192 193 194
        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
      """
195 196
      if value is None:
        return
197
      previous_item_list = self.getAggregateValueList()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
198 199
      given_item_id_list = value
      item_object_list = []
200 201 202 203
      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
204
            object = item_result_list[0].getObject()
205
          except:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
206
            object = None
207
        else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
208
          object = None
209
        if object is not None:
210
          # if item was in previous_item_list keep it
211
          if object in previous_item_list:
212 213 214
            # 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
215 216
          elif (self.getResource() == object.getResource()) \
           and (self.getVariationCategoryList() == object.getVariationCategoryList()):
217 218
            # now verify if item can be moved (not already done)
            last_location_title = object.getLastLocationTitle()
219
            if self.getDestinationTitle() != last_location_title or last_location_title == '':
220 221
              # 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
222 223 224
      # update item_id_list and build relation
      self.setAggregateValueList(item_object_list)
      # update inventory if needed
225
      if len(item_object_list) > 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
226
        quantity = 0
227
        for object_item in item_object_list:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
228 229 230 231 232 233 234 235
          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
      """
236 237
      if value is None:
        return
238
      previous_item_list = self.getAggregateValueList()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
239 240
      given_item_id_list = value
      item_object_list = []
241 242 243
      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:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
244 245 246 247 248 249
          try :
            object = item_result_list[0].getObject()
          except :
            object = None
        else :
          object = None
250
        if object is not None:
251
          # if item was in previous_item_list keep it
252
          if object in previous_item_list:
253 254 255
            # 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
256 257
          elif (self.getResource() == object.getResource()) \
           and (self.getVariationCategoryList() == object.getVariationCategoryList()):
258 259
            # now verify if item can be moved (not already done)
            last_location_title = object.getLastLocationTitle()
260
            if self.getDestinationTitle() == last_location_title or last_location_title == '':
261 262
              # 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
263 264 265
      # update item_id_list and build relation
      self.setAggregateValueList(item_object_list)
      # update inventory if needed
266
      if len(item_object_list) > 0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
267
        quantity = 0
268
        for object_item in item_object_list:
269
          quantity += object_item.getRemainingQuantity()
270 271
          # we reset the location of the item
          object_item.setLocation('')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
272 273 274 275 276 277
        self.setConsumptionQuantity(quantity)

    def getProducedItemIdList(self):
      """
        Returns list of items if production_quantity != 0.0
      """
278
      if self.getProductionQuantity() != 0.0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
279
        return self.getItemIdList()
280
      else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
281 282 283 284 285 286
        return []

    def getConsumedItemIdList(self):
      """
        Returns list of items if consumption_quantity != 0.0
      """
287
      if self.getConsumptionQuantity() != 0.0:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
288
        return self.getItemIdList()
289
      else:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
290
        return []
291 292 293 294 295 296 297 298

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