From ff8f63be18fb0d63a4dc35d1396e8ec676ecabea Mon Sep 17 00:00:00 2001
From: Kevin Deldycke <kevin@nexedi.com>
Date: Tue, 24 Oct 2006 14:42:37 +0000
Subject: [PATCH] Support dynamic update of gross salary calculation on pay
 sheet preview

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@10913 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 .../PaySheetTransaction_initializePreview.xml | 129 +++++++++++++-----
 ...ransaction_preCalculation_l10n_fr_2006.xml |  47 +++++--
 .../PaySheetTransaction_updateCalculation.xml |  41 ++++--
 3 files changed, 164 insertions(+), 53 deletions(-)

diff --git a/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_initializePreview.xml b/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_initializePreview.xml
index 8937fce59c..e47c1d2e72 100644
--- a/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_initializePreview.xml
+++ b/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_initializePreview.xml
@@ -68,15 +68,19 @@
         </item>
         <item>
             <key> <string>_body</string> </key>
-            <value> <string>"""\n
+            <value> <string encoding="cdata"><![CDATA[
+
+"""\n
   This script create the data structure of the pay sheet preview (= the PaySheetTransaction_viewPreview fast input).\n
   Then it fill this data structure with rates and amounts of money from the localized pre calculation script.\n
 """\n
 \n
-import random\n
 from Products.ERP5Type.Document import newTempBase\n
 from string import zfill\n
 \n
+# Get Precision\n
+precision = context.getResourceValue().getQuantityPrecision()\n
+r_ = lambda x: context.Base_getRoundValue(x, precision)\n
 \n
 \n
 ##########################\n
@@ -107,7 +111,6 @@ for service in service_module.objectValues():\n
   # TODO: Use a validation workflow on Payroll Service and check the validity of a Service there\n
   base_categories = service.getVariationBaseCategoryList()\n
   if \'salary_range\' in base_categories and \'tax_category\' in base_categories:\n
-    # XXX add "and service.getId() != \'labour\':" ??\n
     paysheet_services.append(service)\n
 \n
 # Create the pre-calculation data structure\n
@@ -142,7 +145,71 @@ for service in paysheet_services:\n
 \n
 \n
 #########################################\n
-# This part of the script select the right localized version of PaySheetTransaction_preCalculation_l10n script.\n
+# Get new values that came from "Update" action on Pay Sheet fast input.\n
+# Currently we only keep fixed values which are part of the gross salary and addendum calculation.\n
+#\n
+# IDEA: Compare values from preCalculation script (rates and base) with the updated listbox\n
+#       to keep user-defined values. Then merge them with current one. This is a good idea but\n
+#       the decision algorithm must be smart.\n
+#########################################\n
+\n
+UPDATE_SCRIPT = \'PaySheetTransaction_updateCalculation\'\n
+# Get the updated listbox\n
+selection_params = context.portal_selections.getSelectionParams(UPDATE_SCRIPT)\n
+# Reset the selection (defensive strategy)\n
+context.portal_selections.setSelectionParamsFor(UPDATE_SCRIPT, {})\n
+updated_listbox = []\n
+if selection_params.has_key(\'updated_listbox\'):\n
+  updated_listbox = selection_params[\'updated_listbox\']\n
+\n
+\n
+# Scan the listbox and look for complementary lines to add to the gross salary\n
+new_gross_salary = 0.0\n
+for line in updated_listbox:\n
+  salary_range = line[\'salary_range\']\n
+  if salary_range not in [None, \'\'] and salary_range.startswith(\'salary_range/france/fixed/gross\'):\n
+    # Get the employee or employer share as part of the multiline gross salary\n
+    # The payroll service "setup convention" require that the fixed value should be defined on \'employer_share\'\n
+    share_value = line[\'employer_share\']\n
+    if share_value not in [None, \'\']:\n
+      new_gross_salary = r_(new_gross_salary + r_(share_value))\n
+\n
+if new_gross_salary > 0.0:\n
+  # Don\'t forget to add the base salary\n
+  new_gross_salary = r_(new_gross_salary + r_(context.getGrossSalary()))\n
+else:\n
+  new_gross_salary = None\n
+\n
+\n
+# Everything which is part of gross salary and addendum calculation should be kept untouched.\n
+# To keep thoses value, we should use the same dict format as in preCalculation script.\n
+update_kw = {}\n
+for line in updated_listbox:\n
+  salary_range   = line[\'salary_range\']\n
+  service_id     = line[\'service_id\']\n
+  employee_share = line[\'employee_share\']\n
+  employer_share = line[\'employer_share\']\n
+  base           = line[\'base\']\n
+  if salary_range not in [None, \'\']                                and \\\n
+     service_id   not in [None, \'\']                                and \\\n
+     base         not in [None, \'\']                                and \\\n
+     (salary_range.startswith(\'salary_range/france/fixed/gross\')    or \\\n
+      salary_range.startswith(\'salary_range/france/fixed/addendum\') ) and \\\n
+     (employee_share not in [None, \'\'] or employer_share not in [None, \'\']):\n
+    salary_range_id = salary_range.split(\'/\')[-1]\n
+    line_uid = "%s/%s" % (service_id, salary_range_id)\n
+    new_dict = { \'employer_share\': employer_share\n
+               , \'employee_share\': employee_share\n
+               , \'base\'          : base\n
+               }\n
+    update_kw[line_uid] = new_dict\n
+\n
+\n
+\n
+#########################################\n
+# This part of the script select the right localized version of\n
+#   PaySheetTransaction_preCalculation_l10n script.\n
+#\n
 # TODO: implement here a generic method to get the right precalculation script automaticcaly.\n
 #########################################\n
 \n
@@ -155,16 +222,19 @@ script_name = \'_\'.join([ "PaySheetTransaction_preCalculation_l10n"\n
                        , year\n
                        ])\n
 calculation_method = getattr(context, script_name)\n
+pre_calculation = calculation_method(gross_salary=new_gross_salary)\n
 \n
-pre_calculation = calculation_method()\n
-\n
-# Merge pre_calculation and preview line dict\n
+# Merge pre_calculation and preview line dict, or finaly override value from the update dict\n
 preview_line_keys = d.items()[0][1].keys()\n
 for k in pre_calculation.keys():\n
   if k in d.keys():\n
     for required_key in preview_line_keys:\n
+      # Merge\n
       if not pre_calculation[k].has_key(required_key):\n
         pre_calculation[k][required_key] = d[k][required_key]\n
+      # Override\n
+      if update_kw.has_key(k) and update_kw[k].has_key(required_key):\n
+        pre_calculation[k][required_key] = update_kw[k][required_key]\n
   else:\n
     context.log( "PaySheetTransaction_initializePreview"\n
                , "Preview line key \'%s\' not found in default services" % k\n
@@ -172,22 +242,6 @@ for k in pre_calculation.keys():\n
     # Remove line\n
     del pre_calculation[k]\n
 \n
-# Because all rates in the localized file are written in percents, we must convert them in pure floats.\n
-for preview_line_uid in pre_calculation.keys():\n
-  # Only \'Fixed\' (or \'Forfait\' in french) base are expressed in percents\n
-  # TODO: base this test on "/forfait" string is bad. A more generic way must be found.\n
-  preview_line = pre_calculation[preview_line_uid]\n
-  if not preview_line_uid.endswith(\'/forfait\'):\n
-    # Fix percents\n
-    for share_type in [\'employer_share\', \'employee_share\']:\n
-      share_value = preview_line[share_type]\n
-      if share_value not in (\'\', None):\n
-        preview_line[share_type] = share_value / 100.0\n
-  # Normalize the value of \'Fixed\' (or \'Forfait\' in french) base to 1.0\n
-  else:\n
-    preview_line[\'base\'] = 1.0\n
-  pre_calculation[preview_line_uid] = preview_line\n
-\n
 # Create a preview line for every salary_range value of the service\n
 portal_object = context.getPortalObject()\n
 preview_line_list = []\n
@@ -215,7 +269,9 @@ for (preview_line_id, preview_line_item) in pre_calculation.items():\n
 \n
 # return the list of preview lines\n
 return preview_line_list\n
-</string> </value>
+
+
+]]></string> </value>
         </item>
         <item>
             <key> <string>_code</string> </key>
@@ -272,14 +328,15 @@ return preview_line_list\n
                         <value>
                           <tuple>
                             <string>kw</string>
-                            <string>random</string>
                             <string>Products.ERP5Type.Document</string>
                             <string>newTempBase</string>
                             <string>string</string>
                             <string>zfill</string>
-                            <string>d</string>
                             <string>_getattr_</string>
                             <string>context</string>
+                            <string>precision</string>
+                            <string>r_</string>
+                            <string>d</string>
                             <string>erp5site</string>
                             <string>hasattr</string>
                             <string>service_module</string>
@@ -296,26 +353,36 @@ return preview_line_list\n
                             <string>None</string>
                             <string>preview_line_uid</string>
                             <string>_write_</string>
+                            <string>UPDATE_SCRIPT</string>
+                            <string>selection_params</string>
+                            <string>updated_listbox</string>
+                            <string>_getitem_</string>
+                            <string>new_gross_salary</string>
+                            <string>line</string>
+                            <string>share_value</string>
+                            <string>update_kw</string>
+                            <string>service_id</string>
+                            <string>employee_share</string>
+                            <string>employer_share</string>
+                            <string>base</string>
+                            <string>salary_range_id</string>
+                            <string>line_uid</string>
+                            <string>new_dict</string>
                             <string>country</string>
                             <string>year</string>
                             <string>script_name</string>
                             <string>getattr</string>
                             <string>calculation_method</string>
                             <string>pre_calculation</string>
-                            <string>_getitem_</string>
                             <string>preview_line_keys</string>
                             <string>k</string>
                             <string>required_key</string>
-                            <string>preview_line</string>
-                            <string>share_type</string>
-                            <string>share_value</string>
                             <string>portal_object</string>
                             <string>preview_line_list</string>
                             <string>num</string>
                             <string>INT_LEN</string>
                             <string>preview_line_id</string>
                             <string>preview_line_item</string>
-                            <string>service_id</string>
                             <string>o</string>
                           </tuple>
                         </value>
diff --git a/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_preCalculation_l10n_fr_2006.xml b/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_preCalculation_l10n_fr_2006.xml
index 9b7d463253..e2758a434f 100644
--- a/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_preCalculation_l10n_fr_2006.xml
+++ b/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_preCalculation_l10n_fr_2006.xml
@@ -73,6 +73,9 @@
 """\n
   This script define all rates to apply in 2006 to calculate an entire paysheet\n
     according french fiscal & social rules for a SME.\n
+\n
+  This script accept gross_salary parameter to override the default one.\n
+  This is helpfull in case of update made on PaySheetTransaction_viewPreview fast input.\n
 """\n
 \n
 kw = {}\n
@@ -89,8 +92,10 @@ paysheet_type = paysheet.getPortalType()\n
 employee = paysheet.getDestinationSectionValue()\n
 company  = paysheet.getSourceSectionValue()\n
 \n
-# Get Paysheet datas\n
-gross_salary = abs(paysheet.getGrossSalary())\n
+# Use the gross salary given as parameter or not\n
+if gross_salary == None:\n
+  gross_salary = abs(paysheet.getGrossSalary())\n
+\n
 start_date   = paysheet.getStartDate()\n
 stop_date    = paysheet.getStopDate()\n
 \n
@@ -410,7 +415,6 @@ if executive:\n
 comp_date = DateTime(start_date.year(), 3, 31)\n
 while comp_date < comp_date:\n
   comp_date = DateTime(comp_date.year() + 1, 3, 31)\n
-context.log("kev date", repr(comp_date))\n
 if executive and start_date <= comp_date <= stop_date:\n
   kw[\'apec/forfait\'] = \\\n
   { \'employer_share\': 3.72\n
@@ -513,6 +517,25 @@ kw[\'precarite/gross\'] = \\\n
 , \'base\'          : 1.0\n
 }\n
 \n
+\n
+# Normalize\n
+for line_key in kw.keys():\n
+  # Only \'variable\' contribution are expressed in percents of a base\n
+  line = kw[line_key]\n
+  # \'Fixed\' contributions\n
+  if line_key.endswith(\'/gross\')  or \\\n
+     line_key.endswith(\'/forfait\'):\n
+    # Defensive programming: be sure conventions are respected\n
+    kw[line_key][\'base\'] = 1.0\n
+  # \'Variable\' contributions\n
+  else:\n
+    # All rates in this script are written in percents, we must convert them in pure floats.\n
+    for share_type in [\'employer_share\', \'employee_share\']:\n
+      share_value = line[share_type]\n
+      if share_value not in [\'\', None]:\n
+        # Fix percents\n
+        kw[line_key][share_type] = share_value / 100.0\n
+\n
 return kw\n
 
 
@@ -544,7 +567,7 @@ return kw\n
         </item>
         <item>
             <key> <string>_params</string> </key>
-            <value> <string></string> </value>
+            <value> <string>gross_salary=None</string> </value>
         </item>
         <item>
             <key> <string>errors</string> </key>
@@ -564,12 +587,13 @@ return kw\n
                   <dictionary>
                     <item>
                         <key> <string>co_argcount</string> </key>
-                        <value> <int>0</int> </value>
+                        <value> <int>1</int> </value>
                     </item>
                     <item>
                         <key> <string>co_varnames</string> </key>
                         <value>
                           <tuple>
+                            <string>gross_salary</string>
                             <string>kw</string>
                             <string>_getattr_</string>
                             <string>context</string>
@@ -577,8 +601,8 @@ return kw\n
                             <string>paysheet_type</string>
                             <string>employee</string>
                             <string>company</string>
+                            <string>None</string>
                             <string>abs</string>
-                            <string>gross_salary</string>
                             <string>start_date</string>
                             <string>stop_date</string>
                             <string>ceiling_salary_list</string>
@@ -605,16 +629,19 @@ return kw\n
                             <string>DateTime</string>
                             <string>old_limit</string>
                             <string>comp_type</string>
-                            <string>None</string>
                             <string>employer_rate</string>
                             <string>employee_rate</string>
                             <string>fngs_employer_rate</string>
                             <string>employee_share_rate</string>
                             <string>employer_share_rate</string>
                             <string>comp_date</string>
-                            <string>repr</string>
                             <string>col_agr</string>
                             <string>syntec_rate</string>
+                            <string>_getiter_</string>
+                            <string>line_key</string>
+                            <string>line</string>
+                            <string>share_type</string>
+                            <string>share_value</string>
                           </tuple>
                         </value>
                     </item>
@@ -626,7 +653,9 @@ return kw\n
         <item>
             <key> <string>func_defaults</string> </key>
             <value>
-              <none/>
+              <tuple>
+                <none/>
+              </tuple>
             </value>
         </item>
         <item>
diff --git a/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_updateCalculation.xml b/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_updateCalculation.xml
index 03b9275d41..a1947cbc1f 100644
--- a/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_updateCalculation.xml
+++ b/bt5/erp5_payroll/SkinTemplateItem/portal_skins/erp5_payroll/PaySheetTransaction_updateCalculation.xml
@@ -68,22 +68,29 @@
         </item>
         <item>
             <key> <string>_body</string> </key>
-            <value> <string>return context.REQUEST.RESPONSE.redirect(\'%s/PaySheetTransaction_viewPreview?portal_status_message=Base+Salary+updated.\' % context.absolute_url())\n
+            <value> <string encoding="cdata"><![CDATA[
+
+"""\n
+  This script get the new listbox content and save it in a selection to let the\n
+  listbox script (PaySheetTransaction_initializePreview) recalculate necessary contributions.\n
+"""\n
 \n
-# Get all cells which are needed to calculate the base_salary\n
+portal = context.getPortalObject()\n
+N_     = portal.Base_translateString\n
 \n
-# Scan the listbox and look for complementary lines to add to the gross salary\n
-#for user_line in listbox:\n
- # if user_line[\'base\'] not in (None, \'\'):\n
-    # Get the base salary if given by the user\n
-  #  base = r_(user_line[\'base\'])\n
+# Save listbox dict in a selection for recalculation\n
+if len(listbox) > 0:\n
+  # XXX Don\'t know how the selection is supposed to behave in case of simultaneous\n
+  #     pay sheet edition by the same user.\n
+  context.portal_selections.setSelectionParamsFor(script.id, {\'updated_listbox\': listbox})\n
 \n
-# IDEA: compare normal _initializePreview returned rates and base with the current one and keep user-defined values. Then merge them with current one.\n
-# This is the only solution to mimic real user interaction without AJAX/DHTML-like UI.\n
-\n
-# Recalculate the preview with new base_salary\n
-#std_lines = context.PaySheetTransaction_initializePreview()\n
-</string> </value>
+from ZTUtils import make_query\n
+params = { \'portal_status_message\': N_(\'Gross Salary and Depending Contributions Updated.\')}\n
+redirect_url = \'%s/%s?%s\' % (context.absolute_url(), \'PaySheetTransaction_viewPreview\', make_query(params))\n
+return context.REQUEST.RESPONSE.redirect(redirect_url)\n
+
+
+]]></string> </value>
         </item>
         <item>
             <key> <string>_code</string> </key>
@@ -135,6 +142,14 @@
                             <string>kw</string>
                             <string>_getattr_</string>
                             <string>context</string>
+                            <string>portal</string>
+                            <string>N_</string>
+                            <string>len</string>
+                            <string>script</string>
+                            <string>ZTUtils</string>
+                            <string>make_query</string>
+                            <string>params</string>
+                            <string>redirect_url</string>
                           </tuple>
                         </value>
                     </item>
-- 
2.30.9