##############################################################################
#
# 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 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,
    or description if ratio value is empty,
    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,
             source_section               =  \
                self.getPortalObject().restrictedTraverse(res).getSource(),
             resource                     = res,
             destination_section          = self.getDestinationSection(),
             destination                  = 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 quantity 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 

    paysheet_items = {}
    for line in listbox:
      model_line = paysheet.getPortalObject().restrictedTraverse(\
                                                          line['model_line'])
      service      = model_line.getResourceValue()
      service_id   = service.getId()
      quantity     = line['quantity']
      price        = line['price']
      tax_category = line['tax_category_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)

      # perhaps here it will be necesary to raise an error ?
      if not paysheet_items.has_key(service_id):
        paysheet_items[service_id] = { 
                           'title'            : model_line.getTitleOrId(),
                           'desc'             : [],
                           'base_amount_list' : model_line.getBaseAmountList(),
                           'res'              : service.getRelativeUrl(),
                           'int_index'        : model_line.getFloatIndex(),
                           'cell_list'        : []
                         }
      
      # create cells if a value has been entered 
      if quantity or price:
        for salary_range in salary_range_categories: 
          # Define an empty new cell
          new_cell = None
          new_cell = { 'axe_list' : [tax_category,salary_range]
                     , 'quantity' : quantity
                     , 'price'    : price
                     }
          # Add the cell to the conresponding paysheet item 
          if new_cell: 
            paysheet_items[service_id]['cell_list'].append(new_cell) 
            # Save the comment as description 
            if model_line.getDescription():
              paysheet_items[service_id]['desc'].append(\
                                                  model_line.getDescription())
            else:
              resource_description = \
                                model_line.getResourceValue().getDescription()
              paysheet_items[service_id]['desc'].append(resource_description)

    # Create a paysheet item for each service with user data in it
    for item in paysheet_items.values():
      if item['cell_list']:
        if len(item['desc']) > 0: 
          desc = '\n'.join(item['desc'])
        else: 
          desc = None
        paysheet.createPaySheetLine( 
                                    title     = item['title'],
                                    res       = item['res'],
                                    desc      = 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_current_value_dict[base_amount]
    base_amount_current_value_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_current_value_dict.has_key(base_amount):
              old_val = base_amount_current_value_dict[base_amount]
            else:
              old_val = 0
            new_val = old_val + paysheetcell.getQuantity()
            # increment the corresponding amount
            base_amount_current_value_dict[base_amount] = 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 = []
    employee_tax_amount = 0

    # 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())
      
      variation_share_list = model_line.getVariationCategoryList(\
                                      base_category_list=['tax_category',])
      variation_slice_list = model_line.getVariationCategoryList(\
                                      base_category_list=['salary_range',])

      for share in variation_share_list:
        for slice in variation_slice_list:
          cell = model_line.getCell(slice, share)
          if cell is None:
            LOG('createNotEditablePaySheetLineList : cell is None')
            continue
          # get the slice :
          model_slice = None
          model_slice = model_line.getParentValue().getCell(slice)
          quantity = 0.0
          price = 0.0
          if model_slice is None:
            LOG('createNotEditablePaySheetLineList : model_slice %s is None' %\
                                                                          slice)
            continue
          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_current_value_dict=base_amount_current_value_dict,
            share=share,
            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' : [share, slice],
                       'quantity' : quantity,
                       'price'    : price,
                     }
          cell_list.append(new_cell)

          #XXX this is a hack to have the net salary
          base_list = model_line.getResourceValue().getBaseAmountList()
          if price is not None and 'employee_share' in share and\
              ('deductible_tax' in base_list or\
               'non_deductible_tax' in base_list):
            employee_tax_amount += round((price * quantity), precision)

          # update base participation
          base_participation_list = service.getBaseAmountList(base=1)
          for base_participation in base_participation_list:
            if quantity:
              if base_amount_current_value_dict.has_key(base_participation):
                old_val = base_amount_current_value_dict[base_participation]
              else:
                old_val = 0
              new_val = old_val + quantity

              if price:
                if old_val != 0:
                  new_val = round((old_val + quantity*price), precision) 
              base_amount_current_value_dict[base_participation] = 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
    localized_add_end_line_script = getattr(self,
                                'PaySheetTransaction_postCalculation', None)
    if localized_add_end_line_script:
      localized_add_end_line_script(employee_tax_amount)

    return pay_sheet_line_list