[IMP] crm_parner_assign, crm: Corrected history[whole, laetst, case info] for lead...
[odoo/odoo.git] / addons / hr_payroll / hr_payroll.py
index b5b9f33..affd81c 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
@@ -164,30 +169,29 @@ class contrib_register(osv.osv):
 
 contrib_register()
 
-class hr_salary_head(osv.osv):
+class hr_salary_rule_category(osv.osv):
     """
-    HR Salary Head
+    HR Salary Rule Category
     """
 
-    _name = 'hr.salary.head'
-    _description = 'Salary Head'
+    _name = 'hr.salary.rule.category'
+    _description = 'Salary Rule Category'
     _columns = {
         '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.head', 'Parent', help="Linking a salary head to its parent is used only for the reporting purpose."),
+        '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),
-        'sequence': fields.integer('Sequence', required=True, help='Display sequence order'),
     }
 
     _defaults = {
         'company_id': lambda self, cr, uid, context: \
                 self.pool.get('res.users').browse(cr, uid, uid,
                     context=context).company_id.id,
-        'sequence': 5
     }
 
-hr_salary_head()
+hr_salary_rule_category()
 
 class one2many_mod2(fields.one2many):
 
@@ -204,6 +208,35 @@ class one2many_mod2(fields.one2many):
             res[r[self._fields_id]].append( r['id'] )
         return res
 
+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),
+        '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):
+        return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
+
+    def close_payslip_run(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'state': 'close'}, context=context)
+
+hr_payslip_run()
+
 class hr_payslip(osv.osv):
     '''
     Pay Slip
@@ -212,49 +245,48 @@ class hr_payslip(osv.osv):
     _name = 'hr.payslip'
     _description = 'Pay Slip'
 
-    def _get_lines_salary_head(self, cr, uid, ids, field_names, arg=None, context=None):
+    def _get_lines_salary_rule_category(self, cr, uid, ids, field_names, arg=None, context=None):
         result = {}
         if not ids: return result
+        for id in ids:
+            result.setdefault(id, [])
         cr.execute('''SELECT pl.slip_id, pl.id FROM hr_payslip_line AS pl \
-                    LEFT JOIN hr_salary_head AS sh on (pl.category_id = sh.id) \
+                    LEFT JOIN hr_salary_rule_category AS sh on (pl.category_id = sh.id) \
                     WHERE pl.slip_id in %s \
-                    GROUP BY pl.slip_id, sh.sequence, pl.sequence, pl.id ORDER BY sh.sequence, pl.sequence''',(tuple(ids),))
+                    GROUP BY pl.slip_id, pl.sequence, pl.id ORDER BY pl.sequence''',(tuple(ids),))
         res = cr.fetchall()
         for r in res:
-            result.setdefault(r[0], [])
             result[r[0]].append(r[1])
         return result
 
     _columns = {
-        'struct_id': fields.many2one('hr.payroll.structure', 'Structure', help='Defines the rules that have to be applied to this payslip, accordingly to the contract chosen. If you let empty the field contract, this field isn\'t mandatory anymore and thus the rules applied will be all the rules set on the structure of all contracts of the employee valid for the chosen period'),
+        'struct_id': fields.many2one('hr.payroll.structure', 'Structure', readonly=True, states={'draft': [('readonly', False)]}, help='Defines the rules that have to be applied to this payslip, accordingly to the contract chosen. If you let empty the field contract, this field isn\'t mandatory anymore and thus the rules applied will be all the rules set on the structure of all contracts of the employee valid for the chosen period'),
         'name': fields.char('Description', size=64, required=False, readonly=True, states={'draft': [('readonly', False)]}),
         'number': fields.char('Reference', size=64, required=False, readonly=True, states={'draft': [('readonly', False)]}),
         'employee_id': fields.many2one('hr.employee', 'Employee', required=True, readonly=True, states={'draft': [('readonly', False)]}),
         '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 Line',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)]}),
+        'worked_days_line_ids': fields.one2many('hr.payslip.worked_days', 'payslip_id', 'Payslip Worked Days', required=False, readonly=True, states={'draft': [('readonly', False)]}),
         'input_line_ids': fields.one2many('hr.payslip.input', 'payslip_id', 'Payslip Inputs', required=False, readonly=True, states={'draft': [('readonly', False)]}),
         'paid': fields.boolean('Made Payment Order ? ', required=False, readonly=True, states={'draft': [('readonly', False)]}),
-        'note': fields.text('Description'),
+        '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_head': fields.function(_get_lines_salary_head, method=True, type='one2many', relation='hr.payslip.line', string='Details by Salary Head'),
-        'credit_note': fields.boolean('Credit Note', help="Indicates this payslip has a refund of another"),
+        '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", 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'),
@@ -284,23 +316,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')
@@ -321,61 +349,8 @@ class hr_payslip(osv.osv):
             'context': {}
         }
 
-    def verify_sheet(self, cr, uid, ids, context=None):
-         #TODO clean me: this function should create the register lines accordingly to the rules computed (run the compute_sheet first)
-#        holiday_pool = self.pool.get('hr.holidays')
-#        salary_rule_pool = self.pool.get('hr.salary.rule')
-#        structure_pool = self.pool.get('hr.payroll.structure')
-#        register_line_pool = self.pool.get('hr.contibution.register.line')
-#        contracts = []
-#        structures = []
-#        rules = []
-#        lines = []
-#        sal_structures =[]
-#        for slip in self.browse(cr, uid, ids, context=context):
-#            if slip.contract_id:
-#                contracts.append(slip.contract_id)
-#            else:
-#                contracts = self.get_contract(cr, uid, slip.employee_id, slip.date, context=context)
-#            for contract in contracts:
-#                structures.append(contract.struct_id.id)
-#                leave_ids = self._get_leaves(cr, uid, slip.date, slip.employee_id, contract, context)
-#                for hday in holiday_pool.browse(cr, uid, leave_ids, context=context):
-#                    salary_rules = salary_rule_pool.search(cr, uid, [('code', '=', hday.holiday_status_id.code)], context=context)
-#                    rules +=  salary_rule_pool.browse(cr, uid, salary_rules, context=context)
-#            for structure in structures:
-#                sal_structures = self._get_parent_structure(cr, uid, [structure], context=context)
-#                for struct in sal_structures:
-#                    lines = structure_pool.browse(cr, uid, struct, context=context).rule_ids
-#                    for line in lines:
-#                        if line.child_ids:
-#                            for r in line.child_ids:
-#                                lines.append(r)
-#                        rules.append(line)
-#            base = {
-#                'basic': slip.basic_amount,
-#            }
-#            if rules:
-#                for rule in rules:
-#                    if rule.company_contribution:
-#                        base[rule.code.lower()] = rule.amount
-#                        if rule.register_id:
-#                            for slip in slip.line_ids:
-#                                if slip.category_id == rule.category_id:
-#                                    line_tot = slip.total
-#                            value = eval(rule.computational_expression, base)
-#                            company_contrib = self._compute(cr, uid, rule.id, value, employee, contract, context)
-#                            reg_line = {
-#                                'name': rule.name,
-#                                'register_id': rule.register_id.id,
-#                                'code': rule.code,
-#                                'employee_id': slip.employee_id.id,
-#                                'emp_deduction': line_tot,
-#                                'comp_deduction': company_contrib,
-#                                'total': rule.amount + line_tot
-#                            }
-#                            register_line_pool.create(cr, uid, reg_line, context=context)
-        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):
@@ -392,17 +367,19 @@ class hr_payslip(osv.osv):
         #OR if it starts between the given dates
         clause_2 = ['&',('date_start', '<=', date_to),('date_start','>=', date_from)]
         #OR if it starts before the date_from and finish after the date_end (or never finish)
-        clause_3 = [('date_start','<=', date_from),'|',('date_end', '=', False),('date_end','>=', date)]
+        clause_3 = [('date_start','<=', date_from),'|',('date_end', '=', False),('date_end','>=', date_to)]
         clause_final =  [('employee_id', '=', employee.id),'|','|'] + clause_1 + clause_2 + clause_3
-        contract_ids = contract_obj.search(cr, uid, [('employee_id', '=', employee.id),], context=context)
+        contract_ids = contract_obj.search(cr, uid, clause_final, context=context)
         return contract_ids
 
     def compute_sheet(self, cr, uid, ids, context=None):
         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 = 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
+#            old_slipline_ids
             if old_slipline_ids:
                 slip_line_pool.unlink(cr, uid, old_slipline_ids, context=context)
             if payslip.contract_id:
@@ -412,10 +389,10 @@ class hr_payslip(osv.osv):
                 #if we don't give the contract, then the rules to apply should be for all current contracts of the employee
                 contract_ids = self.get_contract(cr, uid, payslip.employee_id, payslip.date_from, payslip.date_to, context=context)
             lines = [(0,0,line) for line in self.pool.get('hr.payslip').get_payslip_lines(cr, uid, contract_ids, payslip.id, context=context)]
-            self.write(cr, uid, [payslip.id], {'line_ids': lines}, context=context)
+            self.write(cr, uid, [payslip.id], {'line_ids': lines, 'number': number,}, context=context)
         return True
 
-    def get_input_lines(self, cr, uid, contract_ids, date_from, date_to, context=None):
+    def get_worked_day_lines(self, cr, uid, contract_ids, date_from, date_to, context=None):
         """
         @param contract_ids: list of contract id
         @return: returns a list of dict containing the input that should be applied for the given contract between date_from and date_to
@@ -472,22 +449,117 @@ class hr_payslip(osv.osv):
             res += [attendances] + leaves
         return res
 
+    def get_inputs(self, cr, uid, contract_ids, date_from, date_to, context=None):
+        res = []
+        contract_obj = self.pool.get('hr.contract')
+        rule_obj = self.pool.get('hr.salary.rule')
+
+        structure_ids = contract_obj.get_all_structures(cr, uid, contract_ids, context=context)
+        rule_ids = self.pool.get('hr.payroll.structure').get_all_rules(cr, uid, structure_ids, context=context)
+        sorted_rule_ids = [id for id, sequence in sorted(rule_ids, key=lambda x:x[1])]
+
+        for contract in contract_obj.browse(cr, uid, contract_ids, context=context):
+            for rule in rule_obj.browse(cr, uid, sorted_rule_ids, context=context):
+                if rule.input_ids:
+                    for input in rule.input_ids:
+                        inputs = {
+                             'name': input.name,
+                             'code': input.code,
+                             'contract_id': contract.id,
+                        }
+                        res += [inputs]
+        return res
+
     def get_payslip_lines(self, cr, uid, contract_ids, payslip_id, context):
-        def _sum_salary_head(localdict, head, amount):
-            if head.parent_id:
-                localdict = _sum_salary_head(localdict, head.parent_id, amount)
-            localdict['heads'][head.code] = head.code in localdict['heads'] and localdict['heads'][head.code] + amount or amount
+        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
+                self.uid = uid
+                self.employee_id = employee_id
+                self.dict = 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 = {}
+        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:
-            worked_days[input_line.code] = input_line
-        localdict = {'heads': {}, 'payslip': payslip, 'worked_days': worked_days}
+            inputs[input_line.code] = input_line
+
+        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
@@ -501,16 +573,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
-                    #sum the amount for its salary head
-                    localdict = _sum_salary_head(localdict, rule.category_id, amount - previous_amount)
+                    localdict[rule.code] = amount * qty
+                    rules[rule.code] = rule
+                    #sum the amount for its salary category
+                    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,
@@ -531,9 +605,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
@@ -545,39 +619,48 @@ class hr_payslip(osv.osv):
     def onchange_employee_id(self, cr, uid, ids, date_from, date_to, employee_id=False, contract_id=False, context=None):
         empolyee_obj = self.pool.get('hr.employee')
         contract_obj = self.pool.get('hr.contract')
+        worked_days_obj = self.pool.get('hr.payslip.worked_days')
         input_obj = self.pool.get('hr.payslip.input')
 
         if context is None:
             context = {}
+        #delete old worked days lines
+        old_worked_days_ids = ids and worked_days_obj.search(cr, uid, [('payslip_id', '=', ids[0])], context=context) or False
+        if old_worked_days_ids:
+            worked_days_obj.unlink(cr, uid, old_worked_days_ids, context=context)
+
         #delete old input lines
         old_input_ids = ids and input_obj.search(cr, uid, [('payslip_id', '=', ids[0])], context=context) or False
         if old_input_ids:
             input_obj.unlink(cr, uid, old_input_ids, context=context)
 
+
         #defaults
         res = {'value':{
                       'line_ids':[],
+                      'input_line_ids': [],
+                      'worked_days_line_ids': [],
                       #'details_by_salary_head':[], TODO put me back
                       'name':'',
                       'contract_id': False,
                       '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)
         res['value'].update({
-                           'name': _('Salary Slip of %s for %s') % (employee_id.name, tools.ustr(ttyme.strftime('%B-%Y'))),
-                           'company_id': employee_id.company_id.id
-                           })
+                    'name': _('Salary Slip of %s for %s') % (employee_id.name, tools.ustr(ttyme.strftime('%B-%Y'))),
+                    'company_id': employee_id.company_id.id
+        })
 
         if not context.get('contract', False):
             #fill with the first contract of the employee
             contract_ids = self.get_contract(cr, uid, employee_id, date_from, date_to, context=context)
             res['value'].update({
-                    'struct_id': contract_ids and contract_obj.read(cr, uid, contract_ids[0], ['struct_id'], context=context)['struct_id'][0] or False,
-                    'contract_id': contract_ids and contract_ids[0] or False,
+                        'struct_id': contract_ids and contract_obj.read(cr, uid, contract_ids[0], ['struct_id'], context=context)['struct_id'][0] or False,
+                        'contract_id': contract_ids and contract_ids[0] or False,
             })
         else:
             if contract_id:
@@ -585,7 +668,10 @@ class hr_payslip(osv.osv):
                 contract_ids = [contract_id]
                 #fill the structure with the one on the selected contract
                 contract_record = contract_obj.browse(cr, uid, contract_id, context=context)
-                res['value'].update({'struct_id': contract_record.struct_id.id, 'contract_id': contract_id})
+                res['value'].update({
+                            'struct_id': contract_record.struct_id.id,
+                            'contract_id': contract_id
+                })
             else:
                 #if we don't give the contract, then the input to fill should be for all current contracts of the employee
                 contract_ids = self.get_contract(cr, uid, employee_id, date_from, date_to, context=context)
@@ -593,13 +679,16 @@ class hr_payslip(osv.osv):
                     return res
 
         #computation of the salary input
-        input_line_ids = self.get_input_lines(cr, uid, contract_ids, date_from, date_to, context=context)
+        worked_days_line_ids = self.get_worked_day_lines(cr, uid, contract_ids, date_from, date_to, context=context)
+        input_line_ids = self.get_inputs(cr, uid, contract_ids, date_from, date_to, context=context)
         res['value'].update({
+                    'worked_days_line_ids': worked_days_line_ids,
                     'input_line_ids': input_line_ids,
-            })
+        })
         return res
 
     def onchange_contract_id(self, cr, uid, ids, date_from, date_to, employee_id=False, contract_id=False, context=None):
+#TODO it seems to be the mess in the onchanges, we should have onchange_employee => onchange_contract => doing all the things
         if context is None:
             context = {}
         res = {'value':{
@@ -612,28 +701,30 @@ class hr_payslip(osv.osv):
             res['value'].update({'struct_id': False})
         return self.onchange_employee_id(cr, uid, ids, date_from=date_from, date_to=date_to, employee_id=employee_id, contract_id=contract_id, context=context)
 
-    def sum(self, cr, uid, code, from_date, to_date=None, employee=False, context=None):
-        if not employee:
-            return 0.0
-        if context is None:
-            context = {}
-        if to_date is None:
-            to_date = datetime.now().strftime('%Y-%m-%d')
-        sum = 0.0
-        cr.execute("SELECT pl.total, hp.credit_note\
-                    FROM hr_payslip as hp, hr_payslip_line as pl \
-                    WHERE hp.employee_id = %s AND hp.state in ('confirm','done') \
-                    AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s",
-                   (employee, from_date, to_date, code))
-        for r in cr.dictfetchall():
-            if r['credit_note'] == False:
-                sum += r['total']
-            else:
-                sum -= r['total']
-        return sum
-
 hr_payslip()
 
+class hr_payslip_worked_days(osv.osv):
+    '''
+    Payslip Worked Days
+    '''
+
+    _name = 'hr.payslip.worked_days'
+    _description = 'Payslip Worked Days'
+    _columns = {
+        'name': fields.char('Description', size=256, 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'),
+        'number_of_hours': fields.float('Number of Hours'),
+        'contract_id': fields.many2one('hr.contract', 'Contract', required=True, help="The contract for which applied this input"),
+    }
+    _order = 'payslip_id, sequence'
+    _defaults = {
+        'sequence': 10,
+    }
+hr_payslip_worked_days()
+
 class hr_payslip_input(osv.osv):
     '''
     Payslip Input
@@ -643,37 +734,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"),
-        'number_of_days': fields.float('Number of Days'),
-        'number_of_hours': fields.float('Number of Hours'),
+        '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'
+    _order = 'payslip_id, sequence'
     _defaults = {
         'sequence': 10,
+        'amount': 0.0,
     }
 
-    def sum(self, cr, uid, code, field, from_date, to_date=None, employee=False, context=None):
-        if not employee:
-            return 0.0
-        if context is None:
-            context = {}
-        if to_date is None:
-            to_date = datetime.now().strftime('%Y-%m-%d')
-        result = 0.0
-        cr.execute("SELECT pi.id \
-                    FROM hr_payslip as hp, hr_payslip_input as pi \
-                    WHERE hp.employee_id = %s AND hp.state in ('confirm','done') \
-                    AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s",
-                   (employee, from_date, to_date, code))
-        input_ids = [r[0] for r in cr.fetchall()]
-        input_data = self.read(cr, uid, input_ids, [field], context=context)
-        for input in input_data:
-           result += input.get(field)
-        return result
-
 hr_payslip_input()
 
 class hr_salary_rule(osv.osv):
@@ -681,16 +753,16 @@ class hr_salary_rule(osv.osv):
     _name = 'hr.salary.rule'
     _columns = {
         'name':fields.char('Name', size=256, required=True, readonly=False),
-        'code':fields.char('Code', size=64, required=True),
+        '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']."),
-        'category_id':fields.many2one('hr.salary.head', 'Salary Head', required=True),
+        '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"),
         'parent_rule_id':fields.many2one('hr.salary.rule', 'Parent Salary Rule', select=True),
         'company_id':fields.many2one('res.company', 'Company', required=False),
         'condition_select': fields.selection([('none', 'Always True'),('range', 'Range'), ('python', 'Python Expression')], "Condition Based on", required=True),
-        'condition_range':fields.char('Range Based on',size=1024, readonly=False, help='This will use to computer the % fields values, in general its on basic, but You can use all heads code field in small letter as a variable name i.e. hra, ma, lta, etc...., also you can use, static varible basic'),#old name = conputional expression
+        'condition_range':fields.char('Range Based on',size=1024, readonly=False, help='This will use to computer the % fields values, in general its on basic, but You can use all categories code field in small letter as a variable name i.e. hra, ma, lta, etc...., also you can use, static varible basic'),
         'condition_python':fields.text('Python Condition', required=True, readonly=False, help='Applied this rule for calculation if condition is true. You can specify condition like basic > 1000.'),#old name = conditions
         'condition_range_min': fields.float('Minimum Range', required=False, help="The minimum amount, applied for this rule."),
         'condition_range_max': fields.float('Maximum Range', required=False, help="The maximum amount, applied for this rule."),
@@ -699,33 +771,26 @@ 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'), #old name = expressiont
+        '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'),
      }
     _defaults = {
         '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)
-# heads: dictionary containing the computed heads (sum of amount of all rules belonging to that head). Keys are the head codes.
-# worked_days: dictionary containing the computed worked days. Keys are the worked days 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'
 
@@ -734,16 +799,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)
-# heads: dictionary containing the computed heads (sum of amount of all rules belonging to that head). Keys are the head codes.
-# worked_days: dictionary containing the computed worked days. Keys are the worked days 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'] > heads['NET'] * 0.10''',
+result = rules.NET > categories.NET * 0.10''',
         'condition_range': 'contract.wage',
         'sequence': 5,
         'appears_on_payslip': True,
@@ -755,7 +821,7 @@ result = rules['NET'] > heads['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):
@@ -774,24 +840,24 @@ result = rules['NET'] > heads['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))
 
@@ -820,6 +886,21 @@ result = rules['NET'] > heads['NET'] * 0.10''',
 
 hr_salary_rule()
 
+class hr_rule_input(osv.osv):
+    '''
+    Salary Rule Input
+    '''
+
+    _name = 'hr.rule.input'
+    _description = 'Salary Rule Input'
+    _columns = {
+        'name': fields.char('Description', size=256, required=True),
+        'code': fields.char('Code', size=52, required=True, help="The code that can be used in the salary rules"),
+        'input_id': fields.many2one('hr.salary.rule', 'Salary Rule Input', required=True)
+    }
+
+hr_rule_input()
+
 class hr_payslip_line(osv.osv):
     '''
     Payslip Line
@@ -830,13 +911,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()
@@ -878,7 +971,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()