##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# 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 Globals import InitializeClass, PersistentMapping
from AccessControl import ClassSecurityInfo
from Acquisition import aq_base

from Products.CMFCore.WorkflowCore import WorkflowAction
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface

from Products.ERP5.ERP5Globals import current_inventory_state_list
from Products.ERP5.Document.OrderLine import OrderLine
from Products.ERP5.Document.Movement import Movement
from Products.ERP5.Document.SetMappedValue import SetMappedValue

from zLOG import LOG

class DeliveryCell(SetMappedValue, Movement):
    """
      A DeliveryCell allows to define specific quantities
      for each variation of a resource in a delivery line.
    """

    meta_type = 'ERP5 Delivery Cell'
    portal_type = 'Delivery Cell'
    add_permission = Permissions.AddPortalContent
    isPortalContent = 1
    isRADContent = 1
    isCell = 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.Arrow
                      , PropertySheet.Amount
                      , PropertySheet.Task
                      , PropertySheet.Movement
                      , PropertySheet.Price
                      , PropertySheet.Predicate
                      , PropertySheet.Domain
                      , PropertySheet.MappedValue
                      , PropertySheet.ItemAggregation
                      )

    # Factory Type Information
    factory_type_information = \
      {    'id'             : portal_type
         , 'meta_type'      : meta_type
         , 'description'    : """\
Une ligne tarifaire."""
         , 'icon'           : 'order_line_icon.gif'
         , 'product'        : 'ERP5'
         , 'factory'        : 'addDeliveryCell'
         , 'immediate_view' : 'delivery_cell_view'
         , 'allow_discussion'     : 1
         , 'allowed_content_types': ('',
                                      )
         , 'filter_content_types' : 1
         , 'global_allow'   : 1
         , 'actions'        :
        ( { 'id'            : 'view'
          , 'name'          : 'View'
          , 'category'      : 'object_view'
          , 'action'        : 'delivery_cell_view'
          , 'permissions'   : (
              Permissions.View, )
          }
        , { 'id'            : 'list'
          , 'name'          : 'Object Contents'
          , 'category'      : 'object_action'
          , 'action'        : 'folder_contents'
          , 'permissions'   : (
              Permissions.View, )
          }
        , { 'id'            : 'print'
          , 'name'          : 'Print'
          , 'category'      : 'object_print'
          , 'action'        : 'delivery_cell_print'
          , 'permissions'   : (
              Permissions.View, )
          }
        , { 'id'            : 'metadata'
          , 'name'          : 'Metadata'
          , 'category'      : 'object_view'
          , 'action'        : 'metadata_edit'
          , 'permissions'   : (
              Permissions.View, )
          }
        , { 'id'            : 'translate'
          , 'name'          : 'Translate'
          , 'category'      : 'object_action'
          , 'action'        : 'translation_template_view'
          , 'permissions'   : (
              Permissions.TranslateContent, )
          }
        )
      }

    security.declarePrivate( '_edit' )
    def _edit(self, REQUEST=None, force_update = 0, **kw):
      SetMappedValue._edit(self, REQUEST=REQUEST, force_update = force_update, **kw)
      # This one must be the last
      if kw.has_key('item_id_list'):
        self._setItemIdList( kw['item_id_list'] )

    security.declareProtected( Permissions.ModifyPortalContent, 'hasCellContent' )
    def hasCellContent(self, base_id='movement'):
      """
          This method can be overriden
      """
      return 0

    security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable')
    def isAccountable(self):
      """
        Returns 1 if this needs to be accounted
        Only account movements which are not associated to a delivery
        Whenever delivery is there, delivery has priority
      """
      return self.aq_parent.aq_parent.isAccountable()

    security.declareProtected( Permissions.AccessContentsInformation, 'getProperty' )
    def getProperty(self, key, d=None):
      """
        Generic accessor. First we check if the value
        exists. Else we call the real accessor
      """

      #try:
      if 1:
        # If mapped_value_property_list is not set
        # then it creates an exception
        if key in self.getMappedValuePropertyList([]):
          if getattr(self, key, None) is not None:
            LOG("Found Prop",0,"")
            return getattr(self, key)
          else:
            LOG("Not Found Prop",0,"")
            return self.aq_parent.getProperty(key)
      #except:
      #  LOG("WARNING: ERP5", 0, 'Could not access mapped value property %s' % key)
      #  return None
      # Standard accessor
      try:
        result = Movement.getProperty(self, key, d=d)
      except:
        result = None
      return result

    security.declareProtected( Permissions.ModifyPortalContent, 'updatePrice' )
    def updatePrice(self):
      if 'price' in self.getMappedValuePropertyList([]):
        # Try to compute an average price by accessing simulation movements
        # This should always return 0 in the case of OrderCell
        total_quantity = 0.0
        total_price = 0.0
        for m in self.getDeliveryRelatedValueList(portal_type="Simulation Movement"):
          order = m.getOrderValue()
          if order is not None:
            # Price is defined in an order
            price = m.getPrice()
            quantity = m.getQuantity()
            try:
              price = float(price)
              quantity = float(quantity)
            except:
              price = 0.0
              quantity = 0.0
            total_quantity += quantity
            total_price += quantity * price
        if total_quantity:
          # Update local price
          # self._setPrice(total_price / total_quantity)
          self.setPrice( total_price / total_quantity )

    security.declareProtected( Permissions.AccessContentsInformation, 'getPrice' )
    def getPrice(self, context=None, REQUEST=None, **kw):
      """
        Returns the price if defined on the cell
        or acquire it
      """
      # Call a script on the context
      if 'price' in self.getMappedValuePropertyList([]):
        if getattr(aq_base(self), 'price', None) is not None:
          return getattr(self, 'price') # default returns a price defined by the mapped value
        else:
          return self.aq_parent.getProperty('price') # Price is acquired
      else:
        return None

    security.declareProtected( Permissions.AccessContentsInformation, 'getQuantity' )
    def getQuantity(self):
      """
        Returns the quantity if defined on the cell
        or acquire it
      """
      # Call a script on the context
      if 'quantity' in self.getMappedValuePropertyList([]):
        if getattr(aq_base(self), 'quantity', None) is not None:
          return getattr(self, 'quantity')
        else:
          return self.aq_parent.getProperty('quantity')
      else:
        return self.getTargetQuantity() # We have acquisition here which me should mimic
        # return None

    security.declareProtected( Permissions.AccessContentsInformation, 'getTargetQuantity' )
    def getTargetQuantity(self):
      """
        Returns the target quantity if defined on the cell
        or acquire it
      """
      # Call a script on the context
      if 'target_quantity' in self.getMappedValuePropertyList([]):
        if getattr(aq_base(self), 'target_quantity', None) is not None:
          return getattr(self, 'target_quantity')
        else:
          return self.aq_parent.getProperty('target_quantity')
      else:
        return None

    def _setItemIdList(self, value):
      """
        Computes total_quantity of all given items and stores this total_quantity
        in the quantity attribute of the cell
      """
      previous_item_list = self.getAggregateValueList()
      given_item_id_list = value
      item_object_list = []
      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 :
            object = item_result_list[0].getObject()
          except :
            object = None
        else :
          object = None

        if object is not None :
          # if item was in previous_item_list keep it
          if object in previous_item_list :
            # 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
          elif (self.getResource() == object.getResource()) and (self.getVariationCategoryList() == object.getVariationCategoryList()) :
            # now verify if item can be moved (not already done)
            last_location_title = object.getLastLocationTitle()
            if self.getDestinationTitle() != last_location_title or last_location_title == '' :
              # we can add this item to the list of aggregated items
              item_object_list.append(object)

      # update item_id_list and build relation
      self.setAggregateValueList(item_object_list)

      # update quantity if needed
      if len(item_object_list)>0 :
        quantity = 0

        for object_item in item_object_list :
          if self.aq_parent.aq_parent.getPortalType() in ('Purchase Packing List', ) :
            quantity += object_item.getQuantity()
          else :
            quantity += object_item.getRemainingQuantity()
            # we reset the location of the item
            object_item.setLocation('')

        self.setTargetQuantity(quantity)

    security.declareProtected(Permissions.ModifyPortalContent, 'applyTargetSolver')
    def applyTargetSolver(self, solver):
      for my_simulation_movement in self.getDeliveryRelatedValueList(portal_type = 'Simulation Movement'):
        self.portal_simulation.applyTargetSolver(my_simulation_movement, solver)

    # Required for indexing
    security.declareProtected(Permissions.AccessContentsInformation, 'getInventoriatedQuantity')
    def getInventoriatedQuantity(self):
      """
        Take into account efficiency in converted target quantity
      """
      if self.getSimulationState() in current_inventory_state_list:
        # When an order is delivered, the target quantity should be considered
        # rather than the quantity
        return Movement.getNetConvertedTargetQuantity(self)
      else:
        return Movement.getInventoriatedQuantity(self)

    security.declareProtected(Permissions.AccessContentsInformation, 'getStartDate')
    def getStartDate(self):
      """
        Take into account efficiency in converted target quantity
      """
      if self.getSimulationState() in current_inventory_state_list:
        # When an order is delivered, the target quantity should be considered
        # rather than the quantity
        return Movement.getTargetStartDate(self)
      else:
        return Movement.getStartDate(self)

    security.declareProtected(Permissions.AccessContentsInformation, 'getStopDate')
    def getStopDate(self):
      """
        Take into account efficiency in converted target quantity
      """
      if self.getSimulationState() in current_inventory_state_list:
        # When an order is delivered, the target quantity should be considered
        # rather than the quantity
        return Movement.getTargetStopDate(self)
      else:
        return Movement.getStopDate(self)

    # Simulation Consistency Check
    def getRelatedQuantity(self):
      """
          Computes the quantities in the simulation
      """
      if isinstance(self, OrderLine):
        result = self.OrderLine_zGetRelatedQuantity(uid=self.getUid())
        if len(result) > 0:
          return result[0].target_quantity
        return None
      else:
        result = self.DeliveryLine_zGetRelatedQuantity(uid=self.getUid())
        if len(result) > 0:
          return result[0].quantity
        return None

    def getRelatedTargetQuantity(self):
      """
          Computes the target quantities in the simulation
      """
      if isinstance(self, OrderLine):
        result = self.OrderLine_zGetRelatedQuantity(uid=self.getUid())
        if len(result) > 0:
          return result[0].target_quantity
        return None
      else:
        result = self.DeliveryLine_zGetRelatedQuantity(uid=self.getUid())
        if len(result) > 0:
          return result[0].target_quantity
        return None
