From 74046a432748cccba974cc9933eeb689596c49e8 Mon Sep 17 00:00:00 2001
From: Fabien Morin <fabien@nexedi.com>
Date: Mon, 3 Dec 2007 14:39:30 +0000
Subject: [PATCH] add 3 tests files that was incude in bt before

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@17960 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5/tests/testNexediPayroll.py   |  81 +++
 product/ERP5/tests/testPayroll.py         | 805 ++++++++++++++++++++++
 product/ERP5/tests/testPayroll_l10n_fr.py |  81 +++
 3 files changed, 967 insertions(+)
 create mode 100644 product/ERP5/tests/testNexediPayroll.py
 create mode 100644 product/ERP5/tests/testPayroll.py
 create mode 100644 product/ERP5/tests/testPayroll_l10n_fr.py

diff --git a/product/ERP5/tests/testNexediPayroll.py b/product/ERP5/tests/testNexediPayroll.py
new file mode 100644
index 0000000000..042bb951ef
--- /dev/null
+++ b/product/ERP5/tests/testNexediPayroll.py
@@ -0,0 +1,81 @@
+##############################################################################
+#
+# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
+#          Fabien Morin <fabien.morin@gmail.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.
+#
+##############################################################################
+"""
+  Tests paysheet creation using paysheet model.
+
+TODO:
+  this test currently just verify that this bt could be installed,
+  test method must be add to verify things specific to this localised bt.
+"""
+
+from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
+from AccessControl.SecurityManagement import newSecurityManager
+from Acquisition import aq_parent
+
+class TestNexediPayroll(ERP5TypeTestCase):
+
+  run_all_test = 1
+  quiet = 1
+
+  def getTitle(self):
+    return "NexediPayroll"
+
+  def afterSetUp(self):
+    """Prepare the test."""
+    self.portal = self.getPortal()
+    self.login()
+
+  def login(self, quiet=0, run=1):
+    uf = self.getPortal().acl_users
+    uf._doAddUser('admin', 'admin', ['Manager', 'Assignee', 'Assignor',
+                               'Associate', 'Auditor', 'Author'], [])
+    user = uf.getUserById('admin').__of__(uf)
+    newSecurityManager(None, user)
+
+  def getBusinessTemplateList(self):
+    """ """
+    return ('erp5_base', 'erp5_pdm', 'erp5_trade', 'erp5_accounting',
+        'erp5_payroll_ng', 'erp5_payroll_l10n_fr', 'nexedi_payroll')
+
+
+  def test_01_btInstallation(self, quiet=0, run=run_all_test):
+    '''
+      this test must be replace with real test
+      it's just here because a test method must be present to launch test
+    '''
+    if not run: return
+    if not quiet:
+      self.logMessage('BT Installation')
+    pass
+
+import unittest
+def test_suite():
+  suite = unittest.TestSuite()
+  suite.addTest(unittest.makeSuite(TestNexediPayroll))
+  return suite
+ 
diff --git a/product/ERP5/tests/testPayroll.py b/product/ERP5/tests/testPayroll.py
new file mode 100644
index 0000000000..e452677ec5
--- /dev/null
+++ b/product/ERP5/tests/testPayroll.py
@@ -0,0 +1,805 @@
+##############################################################################
+#
+# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
+#          Fabien Morin <fabien.morin@gmail.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.
+#
+##############################################################################
+"""
+  Tests paysheet creation using paysheet model.
+
+TODO:
+  - in the test test_04_paySheetCalculation, add sub_object (annotation_line, 
+  ratio_line and payment conditioni), and verify that before the script 
+  'PaySheetTransaction_applyModel' is called, subobjects are not in the 
+  paysheet, and after that there are copied in.
+  - use ratio settings and test it (there is a method getRatioQuantityList, see
+  the file Document/PaySheetModelLine.py)
+  - test with bonus which participate on the base_salary and see if the 
+  contribution are applied on the real base_salary or on the base_salary + bonus
+  (it should).
+
+"""
+
+from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
+from AccessControl.SecurityManagement import newSecurityManager
+from Testing import ZopeTestCase
+from Acquisition import aq_parent
+
+class TestPayrollMixin(ERP5TypeTestCase):
+
+  paysheet_model_portal_type        = 'Pay Sheet Model'
+  paysheet_model_line_portal_type   = 'Pay Sheet Model Line'
+  paysheet_transaction_portal_type  = 'Pay Sheet Transaction'
+  paysheet_line_portal_type         = 'Pay Sheet Line'
+  payroll_service_portal_type       = 'Payroll Service'
+  currency_portal_type              = 'Currency'
+  person_portal_type                = 'Person'
+  organisation_portal_type          = 'Organisation'
+
+
+  default_region                    = 'europe/west/france'
+  france_settings_forfait           = 'france/forfait'
+  france_settings_slice_a           = 'france/tranche_a'
+  france_settings_slice_b           = 'france/tranche_b'
+  france_settings_slice_c           = 'france/tranche_c'
+  tax_category_employer_share       = 'employer_share'
+  tax_category_employee_share       = 'employee_share'
+  base_amount_deductible_tax        = 'deductible_tax'
+  base_amount_non_deductible_tax    = 'deductible_tax'
+  base_amount_bonus                 = 'bonus'
+  base_amount_base_salary           = 'base_salary'
+  grade_worker                      = 'worker'
+  grade_engineer                    = 'engineer'
+
+  plafond = 2682.0
+
+  model = None
+  model_id                          = 'model_one'
+  model_title                       = 'Model One'
+  person_id                         = 'one'
+  person_title                      = 'One'
+  person_career_grade               = 'worker'
+  organisation_id                   = 'company_one'
+  organisation_title                = 'Company One'
+  variation_settings_category_list  = ['salary_range/france',]
+  price_currency                    = 'currency_module/EUR'
+
+  def getTitle(self):
+    return "Payroll"
+
+  def afterSetUp(self):
+    """Prepare the test."""
+    self.portal = self.getPortal()
+    self.organisation_module = self.portal.organisation_module
+    self.person_module = self.portal.person_module
+    self.payroll_service_module = self.portal.payroll_service_module
+    self.paysheet_model_module = self.portal.paysheet_model_module 
+    self.createCategories()
+    self.createCurrencies()
+
+    self.model = self.createModel(self.model_id, self.model_title, 
+        self.person_id, self.person_title, self.person_career_grade, 
+        self.organisation_id, self.organisation_title, 
+        self.variation_settings_category_list, self.price_currency)
+
+    self.login()
+
+    # creation of payroll services
+    self.urssaf_id = 'sickness_insurance'
+    self.labour_id = 'labour'
+
+    self.urssaf_slice_list = ['salary_range/'+self.france_settings_slice_a, 
+                              'salary_range/'+self.france_settings_slice_b, 
+                              'salary_range/'+self.france_settings_slice_c]
+
+    self.urssaf_share_list = ['tax_category/'+self.tax_category_employee_share,
+                              'tax_category/'+self.tax_category_employer_share]
+
+    self.salary_slice_list = ['salary_range/'+self.france_settings_forfait,]
+    self.salary_share_list = ['tax_category/'+self.tax_category_employee_share,]
+
+
+    payroll_service_organisation = self.createOrganisation(id='urssaf', 
+        title='URSSAF')
+    self.urssaf=self.createPayrollService(id=self.urssaf_id, 
+        organisation=payroll_service_organisation, 
+        base_amount_list=['deductible_tax',],
+        variation_base_category_list=['tax_category', 'salary_range'],
+        variation_category_list=self.urssaf_slice_list + \
+                                self.urssaf_share_list)
+
+    self.labour=self.createPayrollService(id=self.labour_id, 
+        organisation=None, 
+        base_amount_list=['base_salary', 'gross_salary'],
+        variation_base_category_list=['tax_category', 'salary_range'],
+        variation_category_list=self.salary_slice_list +\
+                                self.salary_share_list)
+
+  def _safeTic(self):
+    """Like tic, but swallowing errors, usefull for teardown"""
+    try:
+      get_transaction().commit()
+      self.tic()
+    except RuntimeError:
+      pass
+
+  def beforeTearDown(self):
+    """Clear everything for next test."""
+    self._safeTic()
+    for module in [ 'organisation_module',
+                    'person_module',
+                    'currency_module',
+                    'payroll_service_module',
+                    'paysheet_model_module',
+                    'accounting_module']:
+      folder = getattr(self.getPortal(), module, None)
+      if folder:
+        [x.unindexObject() for x in folder.objectValues()]
+        self._safeTic()
+        folder.manage_delObjects([x.getId() for x in folder.objectValues()])
+    self._safeTic()
+    # cancel remaining messages
+    activity_tool = self.getPortal().portal_activities
+    for message in activity_tool.getMessageList():
+      activity_tool.manageCancel(message.object_path, message.method_id)
+      ZopeTestCase._print('\nCancelling active message %s.%s()\n'
+                          % (message.object_path, message.method_id) )
+    get_transaction().commit()
+
+  def login(self, quiet=0, run=1):
+    uf = self.getPortal().acl_users
+    uf._doAddUser('admin', 'admin', ['Manager', 'Assignee', 'Assignor',
+                               'Associate', 'Auditor', 'Author'], [])
+    user = uf.getUserById('admin').__of__(uf)
+    newSecurityManager(None, user)
+
+  def createCategories(self):
+    """Create the categories for our test. """
+    # create categories
+    for cat_string in self.getNeededCategoryList() :
+      base_cat = cat_string.split("/")[0]
+      # if base_cat not exist, create it
+      if getattr(self.getPortal().portal_categories, base_cat, None) == None:
+        self.getPortal().portal_categories.newContent(\
+                                          portal_type='Base Category',
+                                          id=base_cat)
+        get_transaction().commit()
+        self.tic()
+      path = self.getPortal().portal_categories[base_cat]
+      for cat in cat_string.split("/")[1:] :
+        if not cat in path.objectIds() :
+          path = path.newContent(
+                    portal_type='Category',
+                    id=cat,
+                    title=cat.replace('_', ' ').title(),)
+        else:
+          path = path[cat]
+    get_transaction().commit()
+    self.tic()
+    # check categories have been created
+    for cat_string in self.getNeededCategoryList() :
+      self.assertNotEquals(None,
+                self.getCategoryTool().restrictedTraverse(cat_string),
+                cat_string)
+
+  def getNeededCategoryList(self):
+    """return a list of categories that should be created."""
+    return ('region/%s' % self.default_region,
+            'salary_range/%s' % self.france_settings_forfait,
+            'salary_range/%s' % self.france_settings_slice_a,
+            'salary_range/%s' % self.france_settings_slice_b,
+            'salary_range/%s' % self.france_settings_slice_c,
+            'tax_category/%s' % self.tax_category_employer_share,
+            'tax_category/%s' % self.tax_category_employee_share,
+            'base_amount/%s' % self.base_amount_deductible_tax,
+            'base_amount/%s' % self.base_amount_non_deductible_tax,
+            'base_amount/%s' % self.base_amount_bonus,
+            'base_amount/%s' % self.base_amount_base_salary,
+            'grade/%s' % self.grade_worker,
+            'grade/%s' % self.grade_engineer,
+           )
+
+  def createCurrencies(self):
+    """Create some currencies.
+    This script will reuse existing currencies, because we want currency ids 
+    to be stable, as we use them as categories.
+    """
+    currency_module = self.getCurrencyModule()
+    if not hasattr(currency_module, 'EUR'):
+      self.EUR = currency_module.newContent(
+          portal_type = self.currency_portal_type,
+          reference = "EUR", id = "EUR", base_unit_quantity=0.001 )
+      self.USD = currency_module.newContent(
+          portal_type = self.currency_portal_type,
+          reference = "USD", id = "USD" )
+      self.YEN = currency_module.newContent(
+          portal_type = self.currency_portal_type,
+          reference = "YEN", id = "YEN" )
+      get_transaction().commit()
+      self.tic()
+    else:
+      self.EUR = currency_module.EUR
+      self.USD = currency_module.USD
+      self.YEN = currency_module.YEN
+
+  def getBusinessTemplateList(self):
+    """ """
+    return ('erp5_base', 'erp5_pdm', 'erp5_trade', 'erp5_accounting', 
+        'erp5_payroll_ng',)
+
+  def createPerson(self, id='one', title='One', 
+      career_subordination_value=None, career_grade=None, **kw):
+    """
+      Create some Pesons so that we have something to feed.
+      (we create only one because we'd have sorting problems)
+    """
+    person_module = self.portal.getDefaultModule(portal_type=\
+                                                 self.person_portal_type)
+    if hasattr(person_module, id):
+      person_module.manage_delObjects([id])
+    person = person_module.newContent(portal_type=self.person_portal_type, 
+                                      id=id)
+    person.edit(
+        title=title,
+        career_subordination_value=career_subordination_value,
+        career_grade=career_grade,
+               )
+    get_transaction().commit()
+    person.reindexObject()
+    self.tic()
+    return person
+
+  def createOrganisation(self, id='company_one', title='Company One', **kw):
+    if hasattr(self.organisation_module, id):
+      self.organisation_module.manage_delObjects([id])
+    organisation = self.organisation_module.newContent( \
+                                   portal_type=self.organisation_portal_type,
+                                   id=id,
+                                   title=title)
+    get_transaction().commit()
+    organisation.reindexObject()
+    self.tic()
+    return organisation
+
+  def createPayrollService(self, id='', organisation='', 
+      base_amount_list=None, variation_base_category_list=None, 
+      variation_category_list=None, **kw):
+    payroll_service_portal_type = 'Payroll Service'
+    payroll_service_module = self.portal.getDefaultModule(\
+                                    portal_type=payroll_service_portal_type)
+
+    if base_amount_list == None: 
+      base_amount_list=[]
+    if variation_category_list == None: 
+      variation_category_list=[]
+    if variation_base_category_list == None: 
+      variation_category_list=[]
+    if hasattr(payroll_service_module, id):
+      payroll_service_module.manage_delObjects([id])
+
+    payroll_service = payroll_service_module.newContent(\
+        portal_type                  = self.payroll_service_portal_type,
+        id                           = id,
+        source_value                 = organisation,
+        quantity_unit                = 'time/month',
+        product_line                 = 'social_service/state_insurance',
+        base_amount_list             = base_amount_list)
+    payroll_service.setVariationBaseCategoryList(variation_base_category_list)
+    payroll_service.setVariationCategoryList(variation_category_list)
+    get_transaction().commit()
+    payroll_service.reindexObject()
+    self.tic()
+    return payroll_service
+
+  def createModel(self, id, title='', person_id='', 
+      person_title='', person_career_grade='', 
+      organisation_id='', organisation_title='',
+      variation_settings_category_list=None,
+      price_currency=''):
+    """
+      Create a model
+    """
+    if variation_settings_category_list == None:
+      variation_settings_category_list = []
+
+    organisation = self.createOrganisation(organisation_id, organisation_title)
+    person = self.createPerson(id=person_id, title=person_title, 
+                               career_subordination_value=organisation,
+                               career_grade=person_career_grade)
+
+    if hasattr(self.paysheet_model_module, id):
+      self.paysheet_model_module.manage_delObjects([id])
+    paysheet_model = self.paysheet_model_module.newContent( \
+                                portal_type=self.paysheet_model_portal_type,
+                                id=id)
+    paysheet_model.edit(\
+        title=title,
+        variation_settings_category_list=variation_settings_category_list,
+        destination_section_value=organisation,
+        source_section_value=person,)
+    paysheet_model.setPriceCurrency(price_currency)
+    get_transaction().commit()
+    paysheet_model.reindexObject()
+    self.tic()
+
+    return paysheet_model
+
+  def addSlice(self, model, slice, min_value, max_value, base_id='cell'):
+    '''
+      add a new slice in the model
+    '''
+    slice = model.newCell(slice, portal_type='Pay Sheet Model Slice', 
+        base_id=base_id)
+    if slice is not None:
+      slice.setQuantityRangeMax(max_value)
+      slice.setQuantityRangeMin(min_value)
+
+      get_transaction().commit()
+      slice.reindexObject()
+      self.tic()
+      return slice
+    return None
+
+  def addAllSlices(self, model):
+    '''
+      create all usefull slices with min and max values
+    '''
+    model.updateCellRange(base_id='cell')
+    slice_list = []
+    slice_list.append(self.addSlice(model, 'salary_range/%s' % \
+        self.france_settings_forfait, 0, 9999999999999))
+    slice_list.append(self.addSlice(model, 'salary_range/%s' % \
+        self.france_settings_slice_a, 0, self.plafond))
+    slice_list.append(self.addSlice(model, 'salary_range/%s' % \
+        self.france_settings_slice_b, self.plafond, self.plafond*4))
+    slice_list.append(self.addSlice(model, 'salary_range/%s' % \
+        self.france_settings_slice_c, self.plafond*4, self.plafond*8))
+    return slice_list
+
+  def createModelLine(self, model, id, variation_category_list, 
+      resource, slice_list, share_list, values, editable=False,
+      base_amount_list=['base_salary']):
+    '''
+      test the function addModelLine and test if the model line has been 
+      well created.
+      explaination for values :
+      if slice_list is ('slice_a', 'slice_b') and share list is ('employer', 
+      'employee') and if you want to put 100 % of 1000 for slice_a for the 
+      employee and employer, and 50% of the base_application for slice_b 
+      employer and and 2000 for slice_b employee, the value list will look 
+      like this :
+      values = [[[1000, 1], [1000, 1]], [[2000, None], [None, 0.5]]]
+
+      next, two representations to well understand :
+      
+       'employee_share', 'employer_share'
+      [[  1470, None  ], [  2100, None  ]] 
+       'salary_range/france/forfait'
+
+    'employee_share',  'employer_share'   'employee_share',  'employer_share'
+[ [   None, 0.01   ], [   None, 0.02   ],[   None, 0.01  ], [   None, 0.02  ] ]
+'salary_range/france/tranche_a''salary_range/france/tranche_b'
+    '''
+    
+    # verify if category used in this model line are selected in the resource 
+    resource_list = resource.getVariationCategoryList(base=1)
+    msg='%r != %r' % (resource_list, variation_category_list)
+    for i in variation_category_list:
+      self.failUnless(i in resource_list, msg)
+
+    if hasattr(model, id):
+      model.manage_delObjects([id])
+    model_line = model.newContent(\
+        portal_type                  = self.paysheet_model_line_portal_type,
+        id                           = id,
+        resource_value               = resource,
+        source_section_value         = model.getSourceSectionValue(),
+        editable                     = editable,
+        base_amount_list             = base_amount_list,
+        variation_category_list      = variation_category_list,)
+    get_transaction().commit()
+    model_line.reindexObject()
+    self.tic()
+
+    # put values in Model Line cells
+    model_line.updateCellRange(base_id='movement')
+    for slice in slice_list:
+      for share in share_list:
+        cell = model_line.newCell(\
+            slice, share, portal_type='Pay Sheet Cell', base_id='movement')
+        cell.setMappedValuePropertyList(['quantity', 'price'])
+        amount = values[share_list.index(share)][slice_list.index(slice)][0]
+        percent = values[share_list.index(share)][slice_list.index(slice)][1]
+        if amount != None:
+          cell.setQuantity(amount)
+        if percent != None:
+          cell.setPrice(percent)
+        get_transaction().commit()
+        cell.reindexObject()
+        self.tic()
+
+    return model_line
+
+  def createPaySheet(self, model, id='my_paysheet'):
+    '''
+      create a Pay Sheet with the model specialisation
+    '''
+    paysheet_module = self.portal.getDefaultModule(\
+                            portal_type=self.paysheet_transaction_portal_type)
+    if hasattr(paysheet_module, id):
+      paysheet_module.manage_delObjects([id])
+    paysheet = paysheet_module.newContent(\
+        portal_type               = self.paysheet_transaction_portal_type,
+        id                        = id,
+        title                     = id,
+        specialise_value          = model,
+        source_section_value      = model.getSourceSectionValue(),
+        destination_section_value = model.getDestinationSectionValue(),)
+    paysheet.setPriceCurrency('currency_module/EUR')
+    get_transaction().commit()
+    paysheet.reindexObject()
+    self.tic()
+    return paysheet
+
+  def calculatePaySheet(self, paysheet):
+    '''
+      Calcul the given paysheet like if you hace click on the 'Calculation of 
+      the Pay Sheet Transaction' action button.
+      XXX Editable line are not yet take into account
+    '''
+    paysheet_line_list = \
+        paysheet.createNotEditablePaySheetLineList()
+    portal_type_list = ['Annotation Line', 'Payment Condition',
+                        'Pay Sheet Model Ratio Line']
+    paysheet.PaySheetTransaction_copySubObject(portal_type_list)
+    get_transaction().commit()
+    paysheet.recursiveReindexObject()
+    self.tic()
+    return paysheet_line_list
+
+  def assertEqualAmounts(self, pay_sheet_line, correct_value_slice_list,
+      base_salary, i):
+    slice_list = pay_sheet_line.getVariationCategoryList(\
+        base_category_list='base_salary')
+    share_list = pay_sheet_line.getVariationCategoryList(\
+        base_category_list='tax_category')
+    for slice in slice_list:
+      for share in share_list: 
+        cell = pay_sheet_line.getCell(share, slice)
+        value = cell.getQuantity()
+        min_slice = correct_value_slice_list[i-1]
+        max_slice = correct_value_slice_list[i]
+
+        if base_salary <= max_slice:
+          correct_value = base_salary - min_slice
+        else:
+          correct_value = max_slice - min_slice
+        self.assertEqual(correct_value, value)
+      i += 1
+
+  def planPaySheet(self, paysheet, **kw) :
+    """ put the paysheet in the `confirmed` state, which will 
+      start the validateTransactionLines and confirm scripts """
+    self.getPortal().portal_workflow.doActionFor(
+      paysheet, 'plan_action',
+      wf_id = 'accounting_workflow',
+      skip_period_validation = 1
+    )
+    self.assertEquals(paysheet.getSimulationState(), 'planned')
+
+  def confirmPaySheet(self, paysheet, **kw) :
+    """ put the paysheet in the `confirmed` state, which will 
+      start the validateTransactionLines and confirm scripts """
+    self.getPortal().portal_workflow.doActionFor(
+      paysheet, 'confirm_action',
+      wf_id = 'accounting_workflow',
+      skip_period_validation = 1
+    )
+    self.assertEquals(paysheet.getSimulationState(), 'confirmed')
+
+
+class TestPayroll(TestPayrollMixin):
+  RUN_ALL_TESTS = 1
+  QUIET = 0
+
+  def test_01_modelCreation(self, quiet=QUIET, run=RUN_ALL_TESTS):
+    '''
+      test the function createModel and test if the model has been well created
+    '''
+    if not run: return
+    if not quiet:
+      self.logMessage('Model Creation')
+
+    if hasattr(self.paysheet_model_module, self.model_id):
+      self.paysheet_model_module.manage_delObjects([self.model_id])
+    
+    model_count_before_add = \
+        len(self.paysheet_model_module.contentValues(portal_type=\
+        self.paysheet_model_portal_type))
+
+    self.model = self.createModel(self.model_id, 
+                                  self.model_title, 
+                                  self.person_id, 
+                                  self.person_title, 
+                                  self.person_career_grade, 
+                                  self.organisation_id, 
+                                  self.organisation_title, 
+                                  self.variation_settings_category_list, 
+                                  self.price_currency)
+
+    model_count_after_add = \
+        len(self.paysheet_model_module.contentValues(portal_type=\
+        self.paysheet_model_portal_type))
+
+    # check that the number of model_lines has been incremented
+    self.assertEqual(model_count_before_add+1, model_count_after_add)
+
+    #check model have been well created
+    self.model = self.paysheet_model_module._getOb(self.model_id)
+    self.assertEqual(self.model_id, self.model.getId())
+    self.assertEqual(self.model_title, self.model.getTitle())
+    self.assertEqual(self.organisation_title, 
+        self.model.getDestinationSectionTitle())
+    self.assertEqual(self.person_title, self.model.getSourceSectionTitle())
+    self.assertEqual(self.variation_settings_category_list, 
+        self.model.getVariationSettingsCategoryList(base=1))
+
+  def test_02_addModelLine(self, quiet=QUIET, run=RUN_ALL_TESTS):
+    '''
+      create a Model Line and test if it has been well created
+    '''
+    if not run: return
+    if not quiet:
+      self.logMessage('Model Line Creation')
+    
+    #model = self.createModel()
+    self.addAllSlices(self.model)
+
+    payroll_service_portal_type = 'Payroll Service'
+    payroll_service_module = self.portal.getDefaultModule(\
+                                    portal_type=payroll_service_portal_type)
+
+    model_line_id = 'URSSAF'
+
+    variation_category_list = self.urssaf_share_list + self.urssaf_slice_list
+
+    model_line_count_before_add = len(self.model.contentValues(portal_type=\
+        self.paysheet_model_line_portal_type))
+
+    returned_model_line = self.createModelLine(model=self.model, 
+        id=model_line_id, variation_category_list=variation_category_list, 
+        resource=self.urssaf, share_list=self.urssaf_share_list, 
+        slice_list=self.urssaf_slice_list,
+        values=[[[None, 0.01], [None, 0.02],[None, 0.03]], [[None, 0.04], 
+                 [None, 0.05], [None, 0.06]]])
+
+    model_line_count_after_add = len(self.model.contentValues(portal_type=\
+        self.paysheet_model_line_portal_type))
+
+    # check that the number of model_lines has been incremented
+    self.assertEqual(model_line_count_before_add+1, model_line_count_after_add)
+
+    model_line = self.model._getOb(model_line_id)
+    self.assertEqual(returned_model_line, model_line)
+    self.assertEqual(model_line_id, model_line.getId())
+    payroll_service_portal_type = 'Payroll Service'
+    payroll_service_module = self.portal.getDefaultModule(\
+        portal_type=payroll_service_portal_type)
+    resource = payroll_service_module._getOb(self.urssaf_id)
+    self.assertEqual(resource, model_line.getResourceValue())
+    self.assertEqual(variation_category_list, 
+        model_line.getVariationCategoryList())
+
+  def test_03_createPaySheet(self, quiet=QUIET, run=RUN_ALL_TESTS):
+    '''
+      create a Pay Sheet with the model specialisation and verify it was well
+      created
+    '''
+    if not run: return
+    if not quiet:
+      self.logMessage('PaySheet Creation')
+    
+    paysheet_id = 'my_paysheet'
+    paysheet_returned = self.createPaySheet(self.model, paysheet_id)
+    paysheet_module = self.portal.getDefaultModule(\
+                          portal_type=self.paysheet_transaction_portal_type)
+    paysheet = paysheet_module._getOb(paysheet_id)
+    self.assertEqual(paysheet_returned, paysheet)
+    self.assertEqual(paysheet_id, paysheet.getId())
+    self.assertEqual(paysheet.getDestinationSectionTitle(), 
+        self.model.getDestinationSectionTitle())
+    self.assertEqual(paysheet.getSourceSectionTitle(), 
+        self.model.getSourceSectionTitle())
+    self.assertEqual(paysheet.getSpecialiseValue(), self.model) 
+
+  def test_04_paySheetCalculation(self, quiet=QUIET, run=RUN_ALL_TESTS):
+    '''
+      test if the scripts called by the 'Calculation of the Pay Sheet 
+      Transaction' action create the paysheet lines
+    '''
+    if not run: return
+    if not quiet:
+      self.logMessage('PaySheet Calculation')
+    
+    self.addAllSlices(self.model)
+
+    model_line_id1 = 'urssaf'
+    model_line_id2 = 'salary'
+    base_salary = 10000
+
+    urssaf_slice_list = [ 'salary_range/'+self.france_settings_slice_a, 
+                          'salary_range/'+self.france_settings_slice_b, 
+                          'salary_range/'+self.france_settings_slice_c]
+
+    urssaf_share_list = [ 'tax_category/'+self.tax_category_employee_share, 
+                          'tax_category/'+self.tax_category_employer_share]
+
+    salary_slice_list = ['salary_range/'+self.france_settings_forfait,]
+    salary_share_list = ['tax_category/'+self.tax_category_employee_share,]
+
+    variation_category_list_urssaf = urssaf_share_list + urssaf_slice_list
+    variation_category_list_salary = salary_share_list + salary_slice_list
+
+    model_line1 = self.createModelLine(model=self.model, 
+        id=model_line_id1,
+        variation_category_list=variation_category_list_urssaf, 
+        resource=self.urssaf, share_list=self.urssaf_share_list, 
+        slice_list=self.urssaf_slice_list,
+        values=[[[None, 0.01], [None, 0.02], [None, 0.03]], [[None, 0.04], 
+          [None, 0.05], [None, 0.06]]])
+
+    model_line2 = self.createModelLine(model=self.model, 
+        id=model_line_id2,
+        variation_category_list=variation_category_list_salary, 
+        resource=self.labour, share_list=self.salary_share_list, 
+        slice_list=salary_slice_list, base_amount_list=[],
+        values=[[[base_salary, None]],])
+
+    pay_sheet_line_count = len(self.model.contentValues(portal_type=\
+        self.paysheet_line_portal_type)) + 2 # because in this test, 2 lines
+                                             # are added
+
+    paysheet = self.createPaySheet(self.model)
+
+    paysheet_line_count_before_calculation = \
+        len(paysheet.contentValues(portal_type= \
+        self.paysheet_line_portal_type))
+
+    # apply the model
+    #paysheet.PaySheetTransaction_applyModel()
+
+    # calculate the pay sheet
+    pay_sheet_line_list = self.calculatePaySheet(paysheet=paysheet)
+
+    paysheet_line_count_after_calculation = \
+        len(paysheet.contentValues(portal_type= \
+        self.paysheet_line_portal_type))
+    self.assertEqual(paysheet_line_count_before_calculation, 0)
+    self.assertEqual(paysheet_line_count_after_calculation, 
+        pay_sheet_line_count)
+
+    # check the amount in the cells of the created paysheet lines
+    for pay_sheet_line in pay_sheet_line_list:
+      service = pay_sheet_line.getResourceId()
+      if service == self.urssaf_id:
+        i = 1
+        correct_value_slice_list = [0, self.plafond, self.plafond*4, 
+                                    self.plafond*8]
+
+        self.assertEqualAmounts(pay_sheet_line, correct_value_slice_list,
+            base_salary, i)
+
+      elif service == self.labour_id:
+        cell = pay_sheet_line.getCell(\
+            'tax_category/'+ self.tax_category_employee_share,
+            'salary_range/'+ self.france_settings_forfait)
+        value = cell.getTotalPrice()
+        self.assertEqual(base_salary, value)
+
+      else:
+        self.fail("Unknown service for line %s" % pay_sheet_line)
+
+  def test_05_caculationWithANonNullMinimumValueSlice(self, quiet=QUIET, 
+      run=RUN_ALL_TESTS):
+    '''
+      if the is only slice B (without previous slice A), test that
+      the amount paid for this tax is correct
+    '''
+    if not run: return
+    if not quiet:
+      self.logMessage('Calculation With A Non Null Minimum Value Slice')
+    
+    self.addAllSlices(self.model)
+
+    model_line_id1 = 'urssaf'
+    model_line_id2 = 'salary'
+    base_salary = 10000
+
+    urssaf_slice_list = ['salary_range/'+self.france_settings_slice_b,]
+    variation_category_list_urssaf = self.urssaf_share_list + urssaf_slice_list
+    variation_category_list_salary = self.salary_share_list + \
+        self.salary_slice_list
+
+    model_line1 = self.createModelLine(model=self.model, 
+        id=model_line_id1,
+        variation_category_list=variation_category_list_urssaf, 
+        resource=self.urssaf, share_list=self.urssaf_share_list, 
+        slice_list=urssaf_slice_list,
+        values=[[[None, 0.03]], [[None, 0.04]],])
+
+    model_line2 = self.createModelLine(model=self.model, 
+        id=model_line_id2,
+        variation_category_list=variation_category_list_salary, 
+        resource=self.labour, share_list=self.salary_share_list, 
+        slice_list=self.salary_slice_list, base_amount_list=[],
+        values=[[[base_salary, None]],])
+
+    pay_sheet_line_count = len(self.model.contentValues(portal_type=\
+        self.paysheet_line_portal_type)) + 2 # because in this test, 2 lines
+                                             # are added
+
+    paysheet = self.createPaySheet(self.model)
+
+    paysheet_line_count_before_calculation = \
+        len(paysheet.contentValues(portal_type= \
+        self.paysheet_line_portal_type))
+
+    # calculate the pay sheet
+    pay_sheet_line_list = self.calculatePaySheet(paysheet=paysheet)
+
+    paysheet_line_count_after_calculation = \
+        len(paysheet.contentValues(portal_type= \
+        self.paysheet_line_portal_type))
+    self.assertEqual(paysheet_line_count_before_calculation, 0)
+    self.assertEqual(paysheet_line_count_after_calculation, 
+        pay_sheet_line_count)
+    
+    # check the amount in the cells of the created paysheet lines
+    for pay_sheet_line in pay_sheet_line_list:
+      service = pay_sheet_line.getResourceId()
+      if service == self.urssaf_id:
+        i = 2 # the begining max slice
+        correct_value_slice_list = [0, self.plafond, self.plafond*4, 
+                                    self.plafond*8]
+
+        self.assertEqualAmounts(pay_sheet_line, correct_value_slice_list,
+            base_salary, i)
+
+      elif service == self.labour_id:
+        cell = pay_sheet_line.getCell('tax_category/'+\
+            self.tax_category_employee_share,
+            'salary_range/'+ self.france_settings_forfait)
+        value = cell.getTotalPrice()
+        self.assertEqual(base_salary, value)
+
+      else:
+        self.fail("Unknown service for line %s" % pay_sheet_line)
+
+
+import unittest
+def test_suite():
+  suite = unittest.TestSuite()
+  suite.addTest(unittest.makeSuite(TestPayroll))
+  return suite
+ 
diff --git a/product/ERP5/tests/testPayroll_l10n_fr.py b/product/ERP5/tests/testPayroll_l10n_fr.py
new file mode 100644
index 0000000000..fcc18bece9
--- /dev/null
+++ b/product/ERP5/tests/testPayroll_l10n_fr.py
@@ -0,0 +1,81 @@
+##############################################################################
+#
+# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
+#          Fabien Morin <fabien.morin@gmail.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.
+#
+##############################################################################
+"""
+  Tests paysheet creation using paysheet model.
+
+TODO:
+  this test currently just verify that this bt could be installed,
+  test method must be add to verify things specific to this localised bt.
+"""
+
+from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
+from AccessControl.SecurityManagement import newSecurityManager
+from Acquisition import aq_parent
+
+class TestPayroll_l10n_fr(ERP5TypeTestCase):
+
+  run_all_test = 1
+  quiet = 1
+
+  def getTitle(self):
+    return "Payroll_l10n_fr"
+
+  def afterSetUp(self):
+    """Prepare the test."""
+    self.portal = self.getPortal()
+    self.login()
+
+  def login(self, quiet=0, run=1):
+    uf = self.getPortal().acl_users
+    uf._doAddUser('admin', 'admin', ['Manager', 'Assignee', 'Assignor',
+                               'Associate', 'Auditor', 'Author'], [])
+    user = uf.getUserById('admin').__of__(uf)
+    newSecurityManager(None, user)
+
+  def getBusinessTemplateList(self):
+    """ """
+    return ('erp5_base', 'erp5_pdm', 'erp5_trade', 'erp5_accounting',
+        'erp5_payroll_ng', 'erp5_payroll_l10n_fr')
+
+
+  def test_01_btInstallation(self, quiet=0, run=run_all_test):
+    '''
+      this test must be replace with real test
+      it's just here because a test method must be present to launch test
+    '''
+    if not run: return
+    if not quiet:
+      self.logMessage('BT Installation')
+    pass
+
+import unittest
+def test_suite():
+  suite = unittest.TestSuite()
+  suite.addTest(unittest.makeSuite(TestPayroll_l10n_fr))
+  return suite
+ 
-- 
2.30.9