[FIX] working urls
[odoo/odoo.git] / addons / hr_payroll / hr_payroll.py
index e1ff7aa..aaf215c 100644 (file)
@@ -50,6 +50,7 @@ class hr_payroll_structure(osv.osv):
         'company_id':fields.many2one('res.company', 'Company', required=True),
         'note': fields.text('Description'),
         'parent_id':fields.many2one('hr.payroll.structure', 'Parent'),
+        'children_ids':fields.one2many('hr.payroll.structure', 'parent_id', 'Children'),
     }
 
     def _get_parent(self, cr, uid, context=None):
@@ -119,7 +120,7 @@ class hr_contract(osv.osv):
     _inherit = 'hr.contract'
     _description = 'Employee Contract'
     _columns = {
-        'struct_id': fields.many2one('hr.payroll.structure', 'Salary Structure', required=True),
+        'struct_id': fields.many2one('hr.payroll.structure', 'Salary Structure'),
         'schedule_pay': fields.selection([
             ('monthly', 'Monthly'),
             ('quarterly', 'Quarterly'),
@@ -131,6 +132,10 @@ class hr_contract(osv.osv):
             ], 'Scheduled Pay', select=True),
     }
 
+    _defaults = {
+        'schedule_pay': 'monthly',
+    }
+
     def get_all_structures(self, cr, uid, contract_ids, context=None):
         """
         @param contract_ids: list of contracts
@@ -175,6 +180,7 @@ class hr_salary_rule_category(osv.osv):
         'name':fields.char('Name', size=64, required=True, readonly=False),
         'code':fields.char('Code', size=64, required=True, readonly=False),
         'parent_id':fields.many2one('hr.salary.rule.category', 'Parent', help="Linking a salary category to its parent is used only for the reporting purpose."),
+        'children_ids': fields.one2many('hr.salary.rule.category', 'parent_id', 'Children'),
         'note': fields.text('Description'),
         'company_id':fields.many2one('res.company', 'Company', required=False),
     }
@@ -205,16 +211,22 @@ class one2many_mod2(fields.one2many):
 class hr_payslip_run(osv.osv):
 
     _name = 'hr.payslip.run'
+    _description = 'Payslip Batches'
     _columns = {
         'name': fields.char('Name', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
         'slip_ids': fields.one2many('hr.payslip', 'payslip_run_id', 'Payslips', required=False, readonly=True, states={'draft': [('readonly', False)]}),
         'state': fields.selection([
             ('draft', 'Draft'),
             ('close', 'Close'),
-        ], 'State', select=True, readonly=True)
+        ], 'State', select=True, readonly=True),
+        'date_start': fields.date('Date From', required=True, readonly=True, states={'draft': [('readonly', False)]}),
+        'date_end': fields.date('Date To', required=True, readonly=True, states={'draft': [('readonly', False)]}),
+        'credit_note': fields.boolean('Credit Note', readonly=True, states={'draft': [('readonly', False)]}, help="If its checked, indicates that all payslips generated from here are refund payslips."),
     }
     _defaults = {
         'state': 'draft',
+        'date_start': lambda *a: time.strftime('%Y-%m-01'),
+        'date_end': lambda *a: str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10],
     }
 
     def draft_payslip_run(self, cr, uid, ids, context=None):
@@ -255,19 +267,15 @@ class hr_payslip(osv.osv):
         'date_from': fields.date('Date From', readonly=True, states={'draft': [('readonly', False)]}, required=True),
         'date_to': fields.date('Date To', readonly=True, states={'draft': [('readonly', False)]}, required=True),
         'state': fields.selection([
-            ('draft', 'Waiting for Verification'),
-            ('hr_check', 'Waiting for HR Verification'),
-            ('accont_check', 'Waiting for Account Verification'),
-            ('confirm', 'Confirm Sheet'),
-            ('done', 'Paid Salary'),
-            ('cancel', 'Reject'),
+            ('draft', 'Draft'),
+            ('verify', 'Waiting'),
+            ('done', 'Done'),
+            ('cancel', 'Rejected'),
         ], 'State', select=True, readonly=True,
-            help=' * When the payslip is created the state is \'Waiting for verification\'.\
-            \n* It is varified by the user and payslip is sent for HR varification, the state is \'Waiting for HR Verification\'. \
-            \n* If HR varify the payslip, it is sent for account verification, the state is \'Waiting for Account Verification\'. \
-            \n* It is confirmed by the accountant and the state set to \'Confirm Sheet\'.\
-            \n* If the salary is paid then state is set to \'Paid Salary\'.\
-            \n* The \'Reject\' state is used when user cancel payslip.'),
+            help='* When the payslip is created the state is \'Draft\'.\
+            \n* If the payslip is under verification, the state is \'Waiting\'. \
+            \n* If the payslip is confirmed then state is set to \'Done\'.\
+            \n* When user cancel payslip the state is \'Rejected\'.'),
 #        'line_ids': fields.one2many('hr.payslip.line', 'slip_id', 'Payslip Line', required=False, readonly=True, states={'draft': [('readonly', False)]}),
         'line_ids': one2many_mod2('hr.payslip.line', 'slip_id', 'Payslip Lines', readonly=True, states={'draft':[('readonly',False)]}),
         'company_id': fields.many2one('res.company', 'Company', required=False, readonly=True, states={'draft': [('readonly', False)]}),
@@ -277,8 +285,8 @@ class hr_payslip(osv.osv):
         'note': fields.text('Description', readonly=True, states={'draft':[('readonly',False)]}),
         'contract_id': fields.many2one('hr.contract', 'Contract', required=False, readonly=True, states={'draft': [('readonly', False)]}),
         'details_by_salary_rule_category': fields.function(_get_lines_salary_rule_category, method=True, type='one2many', relation='hr.payslip.line', string='Details by Salary Rule Category'),
-        'credit_note': fields.boolean('Credit Note', help="Indicates this payslip has a refund of another"),
-        'payslip_run_id': fields.many2one('hr.payslip.run', 'Payslip Run', readonly=True, states={'draft': [('readonly', False)]}),
+        'credit_note': fields.boolean('Credit Note', help="Indicates this payslip has a refund of another", readonly=True, states={'draft': [('readonly', False)]}),
+        'payslip_run_id': fields.many2one('hr.payslip.run', 'Payslip Batches', readonly=True, states={'draft': [('readonly', False)]}),
     }
     _defaults = {
         'date_from': lambda *a: time.strftime('%Y-%m-01'),
@@ -290,6 +298,14 @@ class hr_payslip(osv.osv):
                     context=context).company_id.id,
     }
 
+    def _check_dates(self, cr, uid, ids, context=None):
+        for payslip in self.browse(cr, uid, ids, context=context):
+            if payslip.date_from > payslip.date_to:
+                return False
+        return True
+
+    _constraints = [(_check_dates, "Payslip 'Date From' must be before 'Date To'.", ['date_from', 'date_to'])]  
+
     def copy(self, cr, uid, id, default=None, context=None):
         if not default:
             default = {}
@@ -308,23 +324,19 @@ class hr_payslip(osv.osv):
     def cancel_sheet(self, cr, uid, ids, context=None):
         return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
 
-    def account_check_sheet(self, cr, uid, ids, context=None):
-        return self.write(cr, uid, ids, {'state': 'accont_check'}, context=context)
-
-    def hr_check_sheet(self, cr, uid, ids, context=None):
-        return self.write(cr, uid, ids, {'state': 'hr_check'}, context=context)
-
     def process_sheet(self, cr, uid, ids, context=None):
         return self.write(cr, uid, ids, {'paid': True, 'state': 'done'}, context=context)
 
+    def hr_verify_sheet(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'state': 'verify'}, context=context)
+
     def refund_sheet(self, cr, uid, ids, context=None):
         mod_obj = self.pool.get('ir.model.data')
         wf_service = netsvc.LocalService("workflow")
-        for id in ids:
-            id_copy = self.copy(cr, uid, id, {'credit_note': True}, context=context)
+        for payslip in self.browse(cr, uid, ids, context=context):
+            id_copy = self.copy(cr, uid, payslip.id, {'credit_note': True, 'name': _('Refund: ')+payslip.name}, context=context)
             self.compute_sheet(cr, uid, [id_copy], context=context)
-            wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'verify_sheet', cr)
-            wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'final_verify_sheet', cr)
+            wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'hr_verify_sheet', cr)
             wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'process_sheet', cr)
 
         form_id = mod_obj.get_object_reference(cr, uid, 'hr_payroll', 'view_hr_payslip_form')
@@ -345,8 +357,8 @@ class hr_payslip(osv.osv):
             'context': {}
         }
 
-    def verify_sheet(self, cr, uid, ids, context=None):
-        return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
+    def check_done(self, cr, uid, ids, context=None):
+        return True
 
     #TODO move this function into hr_contract module, on hr.employee object
     def get_contract(self, cr, uid, employee, date_from, date_to, context=None):
@@ -372,7 +384,7 @@ class hr_payslip(osv.osv):
         slip_line_pool = self.pool.get('hr.payslip.line')
         sequence_obj = self.pool.get('ir.sequence')
         for payslip in self.browse(cr, uid, ids, context=context):
-            number = sequence_obj.get(cr, uid, 'salary.slip')
+            number = payslip.number or sequence_obj.get(cr, uid, 'salary.slip')
             #delete old payslip lines
             old_slipline_ids = slip_line_pool.search(cr, uid, [('slip_id', '=', payslip.id)], context=context)
 #            old_slipline_ids
@@ -467,8 +479,13 @@ class hr_payslip(osv.osv):
         return res
 
     def get_payslip_lines(self, cr, uid, contract_ids, payslip_id, context):
-        
-        class Categories(object):
+        def _sum_salary_rule_category(localdict, category, amount):
+            if category.parent_id:
+                localdict = _sum_salary_rule_category(localdict, category.parent_id, amount)
+            localdict['categories'].dict[category.code] = category.code in localdict['categories'].dict and localdict['categories'].dict[category.code] + amount or amount
+            return localdict
+
+        class BrowsableObject(object):
             def __init__(self, pool, cr, uid, employee_id, dict):
                 self.pool = pool
                 self.cr = cr
@@ -476,29 +493,81 @@ class hr_payslip(osv.osv):
                 self.employee_id = employee_id
                 self.dict = dict
 
-            def __getattr__(self, key):
-                value = self.dict[key]
-                return value
-            
-        def _sum_salary_rule_category(categories_dict, category, amount):
-            if category.parent_id:
-                categories_dict = _sum_salary_rule_category(categories_dict, category.parent_id, amount)
-            categories_dict[category.code] = category.code in categories_dict and categories_dict[category.code] + amount or amount
-            return categories_dict
+            def __getattr__(self, attr):
+                return attr in self.dict and self.dict.__getitem__(attr) or 0.0
+
+        class InputLine(BrowsableObject):
+            """a class that will be used into the python code, mainly for usability purposes"""
+            def sum(self, code, from_date, to_date=None):
+                if to_date is None:
+                    to_date = datetime.now().strftime('%Y-%m-%d')
+                result = 0.0
+                self.cr.execute("SELECT sum(amount) as sum\
+                            FROM hr_payslip as hp, hr_payslip_input as pi \
+                            WHERE hp.employee_id = %s AND hp.state = 'done' \
+                            AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s",
+                           (self.employee_id, from_date, to_date, code))
+                res = self.cr.fetchone()[0]
+                return res or 0.0
+
+        class WorkedDays(BrowsableObject):
+            """a class that will be used into the python code, mainly for usability purposes"""
+            def _sum(self, code, from_date, to_date=None):
+                if to_date is None:
+                    to_date = datetime.now().strftime('%Y-%m-%d')
+                result = 0.0
+                self.cr.execute("SELECT sum(number_of_days) as number_of_days, sum(number_of_hours) as number_of_hours\
+                            FROM hr_payslip as hp, hr_payslip_worked_days as pi \
+                            WHERE hp.employee_id = %s AND hp.state = 'done'\
+                            AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s",
+                           (self.employee_id, from_date, to_date, code))
+                return self.cr.fetchone()
+
+            def sum(self, code, from_date, to_date=None):
+                res = self._sum(code, from_date, to_date)
+                return res and res[0] or 0.0
+
+            def sum_hours(self, code, from_date, to_date=None):
+                res = self._sum(code, from_date, to_date)
+                return res and res[1] or 0.0
+
+        class Payslips(BrowsableObject):
+            """a class that will be used into the python code, mainly for usability purposes"""
+
+            def sum(self, code, from_date, to_date=None):
+                if to_date is None:
+                    to_date = datetime.now().strftime('%Y-%m-%d')
+                self.cr.execute("SELECT sum(case when hp.credit_note = False then (pl.total) else (-pl.total) end)\
+                            FROM hr_payslip as hp, hr_payslip_line as pl \
+                            WHERE hp.employee_id = %s AND hp.state = 'done' \
+                            AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s",
+                            (self.employee_id, from_date, to_date, code))
+                res = self.cr.fetchone()
+                return res and res[0] or 0.0
 
         #we keep a dict with the result because a value can be overwritten by another rule with the same code
         result_dict = {}
-        blacklist = []
+        rules = {}
         categories_dict = {}
+        blacklist = []
+        payslip_obj = self.pool.get('hr.payslip')
+        inputs_obj = self.pool.get('hr.payslip.worked_days')
         obj_rule = self.pool.get('hr.salary.rule')
-        payslip = self.pool.get('hr.payslip').browse(cr, uid, payslip_id, context=context)
+        payslip = payslip_obj.browse(cr, uid, payslip_id, context=context)
         worked_days = {}
         for worked_days_line in payslip.worked_days_line_ids:
             worked_days[worked_days_line.code] = worked_days_line
         inputs = {}
         for input_line in payslip.input_line_ids:
             inputs[input_line.code] = input_line
-        localdict = {'payslip': payslip, 'worked_days': worked_days, 'inputs': inputs}
+
+        categories_obj = BrowsableObject(self.pool, cr, uid, payslip.employee_id.id, categories_dict)
+        input_obj = InputLine(self.pool, cr, uid, payslip.employee_id.id, inputs)
+        worked_days_obj = WorkedDays(self.pool, cr, uid, payslip.employee_id.id, worked_days)
+        payslip_obj = Payslips(self.pool, cr, uid, payslip.employee_id.id, payslip)
+        rules_obj = BrowsableObject(self.pool, cr, uid, payslip.employee_id.id, rules)
+
+        localdict = {'categories': categories_obj, 'rules': rules_obj, 'payslip': payslip_obj, 'worked_days': worked_days_obj, 'inputs': input_obj}
         #get the ids of the structures on the contracts and their parent id as well
         structure_ids = self.pool.get('hr.contract').get_all_structures(cr, uid, contract_ids, context=context)
         #get the rules of the structure and thier children
@@ -512,18 +581,18 @@ class hr_payslip(osv.osv):
             for rule in obj_rule.browse(cr, uid, sorted_rule_ids, context=context):
                 key = rule.code + '-' + str(contract.id)
                 localdict['result'] = None
+                localdict['result_qty'] = 1.0
                 #check if the rule can be applied
                 if obj_rule.satisfy_condition(cr, uid, rule.id, localdict, context=context) and rule.id not in blacklist:
                     #compute the amount of the rule
-                    amount = obj_rule.compute_rule(cr, uid, rule.id, localdict, context=context)
+                    amount, qty = obj_rule.compute_rule(cr, uid, rule.id, localdict, context=context)
                     #check if there is already a rule computed with that code
                     previous_amount = rule.code in localdict and localdict[rule.code] or 0.0
                     #set/overwrite the amount computed for this rule in the localdict
-                    localdict[rule.code] = amount
+                    localdict[rule.code] = amount * qty
+                    rules[rule.code] = rule
                     #sum the amount for its salary category
-                    categories_dict = _sum_salary_rule_category(categories_dict, rule.category_id, amount - previous_amount)
-                    categories_obj = Categories(self.pool, cr, uid, payslip.employee_id.id, categories_dict)
-                    localdict['categories'] = categories_obj
+                    localdict = _sum_salary_rule_category(localdict, rule.category_id, (amount * qty) - previous_amount)
                     #create/overwrite the rule in the temporary results
                     result_dict[key] = {
                         'salary_rule_id': rule.id,
@@ -544,9 +613,9 @@ class hr_payslip(osv.osv):
                         'amount_percentage': rule.amount_percentage,
                         'amount_percentage_base': rule.amount_percentage_base,
                         'register_id': rule.register_id.id,
-                        'total': amount,
+                        'amount': amount,
                         'employee_id': contract.employee_id.id,
-                        'quantity': rule.quantity,
+                        'quantity': qty,
                     }
                 else:
                     #blacklist this rule and its children
@@ -585,7 +654,7 @@ class hr_payslip(osv.osv):
                       'struct_id': False,
                       }
             }
-        if not employee_id:
+        if (not employee_id) or (not date_from) or (not date_to):
             return res
         ttyme = datetime.fromtimestamp(time.mktime(time.strptime(date_from, "%Y-%m-%d")))
         employee_id = empolyee_obj.browse(cr, uid, employee_id, context=context)
@@ -651,7 +720,7 @@ class hr_payslip_worked_days(osv.osv):
     _description = 'Payslip Worked Days'
     _columns = {
         'name': fields.char('Description', size=256, required=True),
-        'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True),
+        'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True, ondelete='cascade'),
         'sequence': fields.integer('Sequence', required=True,),
         'code': fields.char('Code', size=52, required=True, help="The code that can be used in the salary rules"),
         'number_of_days': fields.float('Number of Days'),
@@ -673,17 +742,18 @@ class hr_payslip_input(osv.osv):
     _description = 'Payslip Input'
     _columns = {
         'name': fields.char('Description', size=256, required=True),
-        'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True),
+        'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True, ondelete='cascade'),
         'sequence': fields.integer('Sequence', required=True,),
         'code': fields.char('Code', size=52, required=True, help="The code that can be used in the salary rules"),
-        'quantity': fields.float('Quantity', help="It is used in computation. For e.g. A rule for sales having 1% commission of basic salary for per product can defined in expression like result = inputs['S-ASUS']['qunatity']*contract.wage*0.01."),
+        'amount': fields.float('Amount', help="It is used in computation. For e.g. A rule for sales having 1% commission of basic salary for per product can defined in expression like result = inputs.SALEURO.amount * contract.wage*0.01."),
         'contract_id': fields.many2one('hr.contract', 'Contract', required=True, help="The contract for which applied this input"),
     }
     _order = 'payslip_id, sequence'
     _defaults = {
         'sequence': 10,
-        'quantity': 0.0,
+        'amount': 0.0,
     }
+
 hr_payslip_input()
 
 class hr_salary_rule(osv.osv):
@@ -693,7 +763,7 @@ class hr_salary_rule(osv.osv):
         'name':fields.char('Name', size=256, required=True, readonly=False),
         'code':fields.char('Code', size=64, required=True, help="The code of salary rules can be used as reference in computation of other rules. In that case, it is case sensitive."),
         'sequence': fields.integer('Sequence', required=True, help='Use to arrange calculation sequence'),
-        'quantity': fields.char('Quantity', size=256, help="It is used in computation for percentage and fixed amount.For e.g. A rule for Meal Voucher having fixed amount of 1€ per worked day can have its quantity defined in expression like worked_days['WORK100']['number_of_days']."),
+        'quantity': fields.char('Quantity', size=256, help="It is used in computation for percentage and fixed amount.For e.g. A rule for Meal Voucher having fixed amount of 1€ per worked day can have its quantity defined in expression like worked_days.WORK100.number_of_days."),
         'category_id':fields.many2one('hr.salary.rule.category', 'Category', required=True),
         'active':fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the salary rule without removing it."),
         'appears_on_payslip': fields.boolean('Appears on Payslip', help="Used for the display of rule on payslip"),
@@ -709,21 +779,12 @@ class hr_salary_rule(osv.osv):
             ('fix','Fixed Amount'),
             ('code','Python Code'),
         ],'Amount Type', select=True, required=True, help="The computation method for the rule amount."),
-        'amount_fix': fields.float('Fixed Amount', digits_compute=dp.get_precision('Account'),),
-        'amount_percentage': fields.float('Percentage (%)', digits_compute=dp.get_precision('Account'), help='For example, enter 50.0 to apply a percentage of 50%'),
+        'amount_fix': fields.float('Fixed Amount', digits_compute=dp.get_precision('Payroll'),),
+        'amount_percentage': fields.float('Percentage (%)', digits_compute=dp.get_precision('Payroll'), help='For example, enter 50.0 to apply a percentage of 50%'),
         'amount_python_compute':fields.text('Python Code'),
         'amount_percentage_base':fields.char('Percentage based on',size=1024, required=False, readonly=False, help='result will be affected to a variable'),
         'child_ids':fields.one2many('hr.salary.rule', 'parent_rule_id', 'Child Salary Rule'),
-        'register_id':fields.property(
-            'hr.contribution.register',
-            type='many2one',
-            relation='hr.contribution.register',
-            string="Contribution Register",
-            method=True,
-            view_load=True,
-            help="Contribution register based on company",
-            required=False
-        ),
+        'register_id':fields.many2one('hr.contribution.register', 'Contribution Register', help="Eventual third party involved in the salary payment of the employees."),
         'input_ids': fields.one2many('hr.rule.input', 'input_id', 'Inputs'),
         'note':fields.text('Description'),
      }
@@ -731,13 +792,13 @@ class hr_salary_rule(osv.osv):
         'amount_python_compute': '''
 # Available variables:
 #----------------------
-# payslip: hr.payslip object
+# payslip: object containing the payslips
 # employee: hr.employee object
 # contract: hr.contract object
-# rules: rules code (previously computed)
-# categories: dictionary containing the computed salary rule categories (sum of amount of all rules belonging to that category). Keys are the category codes.
-# worked_days: dictionary containing the computed worked days. Keys are the worked days codes.
-# inputs: dictionary containing the computed inputs. Keys are the inputs codes.
+# rules: object containing the rules code (previously computed)
+# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
+# worked_days: object containing the computed worked days.
+# inputs: object containing the computed inputs.
 
 # Note: returned value have to be set in the variable 'result'
 
@@ -746,17 +807,17 @@ result = contract.wage * 0.10''',
 '''
 # Available variables:
 #----------------------
-# payslip: hr.payslip object
+# payslip: object containing the payslips
 # employee: hr.employee object
 # contract: hr.contract object
-# rules: rules code (previously computed)
-# categories: dictionary containing the computed salary rule categories (sum of amount of all rules belonging to that category). Keys are the category codes.
-# worked_days: dictionary containing the computed worked days. Keys are the worked days codes.
-# inputs: dictionary containing the computed inputs. Keys are the inputs codes.
+# rules: object containing the rules code (previously computed)
+# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
+# worked_days: object containing the computed worked days
+# inputs: object containing the computed inputs
 
 # Note: returned value have to be set in the variable 'result'
 
-result = rules['NET'] > categories['NET'] * 0.10''',
+result = rules.NET > categories.NET * 0.10''',
         'condition_range': 'contract.wage',
         'sequence': 5,
         'appears_on_payslip': True,
@@ -768,7 +829,7 @@ result = rules['NET'] > categories['NET'] * 0.10''',
         'amount_select': 'fix',
         'amount_fix': 0.0,
         'amount_percentage': 0.0,
-        'quantity': '1',
+        'quantity': '1.0',
      }
 
     def _recursive_search_of_rules(self, cr, uid, rule_ids, context=None):
@@ -787,24 +848,24 @@ result = rules['NET'] > categories['NET'] * 0.10''',
         """
         @param rule_id: id of rule to compute
         @param localdict: dictionary containing the environement in which to compute the rule
-        @return: returns the result of computation as float
+        @return: returns the result of computation and the quantity as floats
         """
         rule = self.browse(cr, uid, rule_id, context=context)
         if rule.amount_select == 'fix':
             try:
-                return rule.amount_fix * eval(rule.quantity, localdict)
+                return rule.amount_fix, eval(rule.quantity, localdict)
             except:
                 raise osv.except_osv(_('Error'), _('Wrong quantity defined for salary rule %s (%s)')% (rule.name, rule.code))
         elif rule.amount_select == 'percentage':
             try:
                 amount = rule.amount_percentage * eval(rule.amount_percentage_base, localdict) / 100
-                return amount * eval(rule.quantity, localdict)
+                return amount, eval(rule.quantity, localdict)
             except:
                 raise osv.except_osv(_('Error'), _('Wrong percentage base or quantity defined for salary rule %s (%s)')% (rule.name, rule.code))
         else:
             try:
                 eval(rule.amount_python_compute, localdict, mode='exec', nocopy=True)
-                return localdict['result']
+                return localdict['result'], 'result_qty' in localdict and localdict['result_qty'] or 1.0
             except:
                 raise osv.except_osv(_('Error'), _('Wrong python code defined for salary rule %s (%s) ')% (rule.name, rule.code))
 
@@ -858,13 +919,25 @@ class hr_payslip_line(osv.osv):
     _description = 'Payslip Line'
     _order = 'contract_id, sequence'
 
+    def _calculate_total(self, cr, uid, ids, name, args, context):
+        if not ids: return {}
+        res = {}
+        for line in self.browse(cr, uid, ids, context=context):
+            res[line.id] = float(line.quantity) * line.amount
+        return res
+
     _columns = {
-        'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=True),
+        'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=True, ondelete='cascade'),
         'salary_rule_id':fields.many2one('hr.salary.rule', 'Rule', required=True),
         'employee_id':fields.many2one('hr.employee', 'Employee', required=True),
         'contract_id':fields.many2one('hr.contract', 'Contract', required=True),
-        'total': fields.float('Amount', digits_compute=dp.get_precision('Account')),
-        'company_contrib': fields.float('Company Contribution', readonly=True, digits_compute=dp.get_precision('Account')),
+        'amount': fields.float('Amount', digits_compute=dp.get_precision('Payroll')),
+        'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Payroll')),
+        'total': fields.function(_calculate_total, method=True, type='float', string='Total', digits_compute=dp.get_precision('Payroll'),store=True ),
+    }
+
+    _defaults = {
+        'quantity': 1.0,
     }
 
 hr_payslip_line()
@@ -906,7 +979,7 @@ class hr_employee(osv.osv):
 
     _columns = {
         'slip_ids':fields.one2many('hr.payslip', 'employee_id', 'Payslips', required=False, readonly=True),
-        'total_wage': fields.function(_calculate_total_wage, method=True, type='float', string='Total Basic Salary', digits_compute=dp.get_precision('Account'), help="Sum of all current contract's wage of employee."),
+        'total_wage': fields.function(_calculate_total_wage, method=True, type='float', string='Total Basic Salary', digits_compute=dp.get_precision('Payroll'), help="Sum of all current contract's wage of employee."),
     }
 
 hr_employee()