##############################################################################
#
# 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 AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Products.ERP5.Document.Invoice import Invoice
from Products.ERP5Type.Utils import cartesianProduct
from zLOG import LOG

class PaySheetTransaction(Invoice):
  """
  A paysheet will store data about the salary of an employee
  """

  meta_type = 'ERP5 Pay Sheet Transaction'
  portal_type = 'Pay Sheet Transaction'
  add_permission = Permissions.AddPortalContent
  isPortalContent = 1
  isRADContent = 1

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

  # Global variables
  _transaction_line_portal_type = 'Pay Sheet Transaction Line'
  
  # Default Properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.SimpleItem
                    , PropertySheet.CategoryCore
                    , PropertySheet.Task
                    , PropertySheet.Arrow
                    , PropertySheet.Delivery
                    , PropertySheet.PaySheet
                    , PropertySheet.Movement
                    , PropertySheet.Amount
                    , PropertySheet.XMLObject
                    , PropertySheet.TradeCondition
                    , PropertySheet.DefaultAnnotationLine
                    )

  # Declarative Interface
  __implements__ = ( )



  security.declareProtected(Permissions.AccessContentsInformation,
                          'getRatioQuantityFromReference')
  def getRatioQuantityFromReference(self, ratio_reference=None):
    """
    return the ratio value correponding to the ratio_reference,
    None if ratio_reference not found
    """
    object_ratio_list = self.contentValues(portal_type=\
        'Pay Sheet Model Ratio Line')
    for object in object_ratio_list:
      if object.getReference() == ratio_reference:
        return object.getQuantity()
    return None 

  security.declareProtected(Permissions.AccessContentsInformation,
                          'getRatioQuantityList')
  def getRatioQuantityList(self, ratio_reference_list):
    """
    Return a list of reference_ratio_list correponding values.
    reference_ratio_list is a list of references to the ratio lines
    we want to get.
    """
    if not isinstance(ratio_reference_list, list):
      return [self.getRatioQuantityFromReference(ratio_reference_list)]
    return [self.getRatioQuantityFromReference(reference) \
        for reference in ratio_reference_list]


  security.declareProtected(Permissions.AddPortalContent,
                          'createPaySheetLine')
  def createPaySheetLine(self, cell_list, title='', res='', desc='', 
      base_amount_list=None, int_index=None, **kw):
    '''
    This function register all paysheet informations in paysheet lines and 
    cells. Select good cells only
    '''
    good_cell_list = []
    for cell in cell_list:
      if cell['quantity'] or cell['price']:
        good_cell_list.append(cell)
    if len(good_cell_list) == 0:
      return
    # Get all variation categories used in cell_list
    var_cat_list = []
    for cell in good_cell_list:
      # Don't add a variation category if already in it
      for category in cell['axe_list']:
        if category not in var_cat_list: 
          var_cat_list.append(category)

    # Construct the description
    description = None
    if len(desc) > 0:
      description = desc#'\n'.join(desc)
    # Add a new Pay Sheet Line
    payline = self.newContent(
             portal_type                  = 'Pay Sheet Line',
             title                        = title,
             description                  = description,
             destination                  = self.getSourceSection(),
             source_section               =  \
                self.getPortalObject().restrictedTraverse(res).getSource(),
             resource                     = res,
             destination_section          = self.getDestinationSection(),
             variation_base_category_list = ('tax_category', 'salary_range'),
             variation_category_list      = var_cat_list,
             base_amount_list             = base_amount_list,
             int_index                    = int_index,
             **kw)

    base_id = 'movement'
    a = payline.updateCellRange(script_id = 'PaySheetLine_asCellRange',
                                base_id   = base_id)
    # create cell_list
    for cell in good_cell_list:
      cell_cat_list = cell['axe_list']
      paycell = payline.newCell(base_id = base_id, *cell_cat_list)
      # if the price aven't be completed, it should be set to 1 (=100%)
      if cell['price']:
        price = cell['price']
      else: 
        price = 1
      paycell.edit( mapped_value_property_list = ('price', 'quantity'),
                    quantity                   = cell['quantity'],
                    price                      = price,
                    force_update               = 1,
                    category_list              = cell_cat_list,
                  )

    return payline



  security.declareProtected(Permissions.AddPortalContent,
                          'createEditablePaySheetLineList')
  def createEditablePaySheetLineList(self, listbox, **kw):
    '''
      this script is called by the preview form to ask to the accountable 
      the values  of the editables lines and create corresponding 
      PaySheetLines with this values
    '''
    paysheet = self 
    item_dict = {}
    model_line_id_list = []
    for cell in listbox:
      model_line = paysheet.getPortalObject().restrictedTraverse(\
                                                          cell['model_line'])
      model_line_id = model_line.getId()
      service      = model_line.getResourceValue()
      service_id   = service.getId()
      quantity     = cell['quantity']
      price        = cell['price']
      tax_category = cell['tax_category_relative_url']
      salary_range = cell['salary_range_relative_url']

      variation_category_list = model_line.getVariationCategoryList(\
                                            base_category_list='salary_range')
      salary_range_categories = []
      #for category in resource_variation_category_list:
      for category in variation_category_list:
        if category.startswith('salary_range/'): 
          salary_range_categories.append(category)

      new_cell = { 'axe_list' : [tax_category,salary_range],
                   'quantity' : quantity,
                   'price'    : price,
                 }

      if item_dict.has_key(model_line_id):
        # an item for this model_line_id already exists
        item_dict[model_line_id]['cell_list'].append(new_cell)
      else:
        if model_line.getDescription():
          desc = model_line.getDescription()
        else:
          desc = model_line.getResourceValue().getDescription()

        model_line_id_list.append(model_line_id)
        # create a new item
        item_dict[model_line_id]={\
              'title' : model_line.getTitleOrId(),
              'res' : model_line.getResourceValue().getRelativeUrl(),
              'desc' : desc,
              'cell_list' : [new_cell],
              'int_index' : model_line.getFloatIndex(),
              'base_amount_list' : model_line.getBaseAmountList(),
            }

    for model_line_id in model_line_id_list:
      item = item_dict[model_line_id]
      paysheet.createPaySheetLine(title     = item['title'],
                                  res       = item['res'],
                                  desc      = item['desc'],
                                  cell_list = item['cell_list'],
                                  int_index = item['int_index'],
                                  base_amount_list = item['base_amount_list'])

        
        

  security.declareProtected(Permissions.ModifyPortalContent,
                          'createNotEditablePaySheetLineList')
  def createNotEditablePaySheetLineList(self, **kw):
    '''
      get all data required to create not editable paysheet lines and create it
      editable paysheet lines have been created by a script
    '''

    # Get Precision
    precision = self.getPriceCurrencyValue().getQuantityPrecision()


    # in this dictionary will be saved the current amount corresponding to 
    # the tuple (tax_category, base_amount) :
    # current_amount = base_amount_dict[base_amount][share]
    base_amount_dict = {}

    def sortByIntIndex(a, b):
      return cmp(a.getIntIndex(),
                 b.getIntIndex())


    base_amount_list = self.portal_categories['base_amount'].contentValues()
    base_amount_list.sort(sortByIntIndex)

    # it's important to get the editable lines to know if they contribute to
    # a base_amount (this is required to do the calcul later)

    # get edited lines:
    paysheetline_list = self.contentValues(portal_type = ['Pay Sheet Line'])

    for paysheetline in paysheetline_list:
      service = paysheetline.getResourceValue()
      base_amount_list = service.getBaseAmountList(base=1)
      for base_amount in base_amount_list:
        paysheetcell_list = paysheetline.contentValues(portal_type = \
                                                            ['Pay Sheet Cell'])
        for paysheetcell in paysheetcell_list:
          tax_category = paysheetcell.getTaxCategory(base=1)
          if tax_category and paysheetcell.getQuantity():
            if base_amount_dict.has_key(base_amount) and \
                base_amount_dict[base_amount].has_key(tax_category):
              old_val = base_amount_dict[base_amount][tax_category]
            else:
              old_val = 0
            new_val = old_val + paysheetcell.getQuantity()
            if not base_amount_dict.has_key(base_amount):
              base_amount_dict[base_amount]={}
            # increment the corresponding amount
            base_amount_dict[base_amount][tax_category] = new_val

    # get not editables model lines
    model = self.getSpecialiseValue()
    model_line_list = model.contentValues(portal_type='Pay Sheet Model Line',
                                          sort_on='int_index')
    model_line_list = [line for line in model_line_list \
        if not line.getEditable()]

    pay_sheet_line_list = []

    # main loop : find all informations and create cell and PaySheetLines
    for model_line in model_line_list:
      cell_list       = []
      # test with predicate if this model line could be applied
      if not model_line.test(self,):
        # This line should not be used
        continue

      service          = model_line.getResourceValue()
      title            = model_line.getTitleOrId()
      int_index        = model_line.getFloatIndex()
      id               = model_line.getId()
      base_amount_list = model_line.getBaseAmountList()
      res              = service.getRelativeUrl()
      if model_line.getDescription():
        desc = ''.join(model_line.getDescription())
      # if the model_line description is empty, the payroll service 
      # description is used
      else: 
        desc = ''.join(service.getDescription())



      base_category_list = model_line.getVariationBaseCategoryList()
      list_of_list = []
      for base_cat in base_category_list:
        list = model_line.getVariationCategoryList(base_category_list=base_cat)
        list_of_list.append(list)
      cartesian_product = cartesianProduct(list_of_list)

      slice = None
      indice = 0
      for tuple in cartesian_product:
        indice += 1
        cell = model_line.getCell(*tuple)
        if cell is None:
          continue

        tuple_dict = {}
        for item in tuple:
          # the dict key is the base category and value is the category path
          tuple_dict[item.split('/')[0]] = \
              self.portal_categories.restrictedTraverse(item).getTitle()
          tuple_dict[item.split('/')[0]+'_relative_url']=item

        #get the slice
        if tuple_dict.has_key('salary_range'):
          slice = tuple_dict['salary_range_relative_url']

        #get the share
        if tuple_dict.has_key('tax_category'):
          share = tuple_dict['tax_category_relative_url']
    
        # get the slice :
        model_slice = model_line.getParentValue().getCell(slice)
        quantity = 0.0
        price = 0.0
        model_slice_min = 0
        model_slice_max = 0
        if model_slice is None:
          LOG('createNotEditablePaySheetLineList :', 0, 'model_slice %s is None'
              % slice)
        else:
          model_slice_min = model_slice.getQuantityRangeMin()
          model_slice_max = model_slice.getQuantityRangeMax()

        ######################
        # calculation part : #
        ######################

        # get script in this order
        # 1 - model_line script
        # 2 - model script
        # 3 - get the default calculation script

        # get the model line script
        script_name = model_line.getCalculationScriptId()
        if script_name is None:
          # if model line script is None, get the default model script
          script_name = model.getDefaultCalculationScriptId()

        if script_name is None:
          # if no calculation script found, use a default script :
          script_name = 'PaySheetTransaction_defaultCalculationScript'

        if getattr(self, script_name, None) is None:
          raise ValueError, "Unable to find `%s` calculation script" % \
                                                           script_name
        calculation_script = getattr(self, script_name, None)
        quantity=0
        price=0
        #LOG('script_name :', 0, script_name)
        result = calculation_script(\
          base_amount_dict=base_amount_dict,
          model_slice_min=model_slice_min, 
          model_slice_max=model_slice_max, 
          cell=cell,)

        quantity = result['quantity']
        price = result['price']

        # Cell creation :
        # Define an empty new cell
        new_cell = { 'axe_list' : tuple, # share, slice
                     'quantity' : quantity,
                     'price'    : price,
                   }
        cell_list.append(new_cell)


        
        base_participation_list = service.getBaseAmountList(base=1)
        for base_participation in base_participation_list:
          if quantity:
            if base_amount_dict.has_key(base_participation) and \
                base_amount_dict[base_participation].has_key(share):
              old_val = base_amount_dict[base_participation][share]
            else:
              old_val = 0
            new_val = old_val + quantity
            if not base_amount_dict.has_key(base_participation):
              base_amount_dict[base_participation]={}

            if price:
              new_val = round((old_val + quantity*price), precision) 
            base_amount_dict[base_participation][share] = new_val

      if cell_list:
        # create the PaySheetLine
        pay_sheet_line = self.createPaySheetLine(
                                    title     = title,
                                    res       = res,
                                    int_index = int_index,
                                    desc      = desc,
                                    base_amount_list = base_amount_list,
                                    cell_list = cell_list,
                                  )
        pay_sheet_line_list.append(pay_sheet_line)


    # this script is used to add a line that permit to have good accounting 
    # lines
    post_calculation_script = getattr(self,
                                'PaySheetTransaction_postCalculation', None)
    if post_calculation_script:
      post_calculation_script()

    return pay_sheet_line_list