76fc1247de6d50971a961213cf33f2060f3cffd4
[odoo/odoo.git] / addons / l10n_in_hr_payroll / l10n_in_hr_payroll.py
1 #-*- coding:utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2011 OpenERP SA (<http://openerp.com>). All Rights Reserved
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as published by
9 #    the Free Software Foundation, either version 3 of the License, or
10 #    (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import time
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
25 from calendar import isleap
26
27 from openerp.tools.translate import _
28 from openerp.osv import fields, osv
29 from openerp import netsvc
30 import openerp.addons.decimal_precision as dp
31
32 DATETIME_FORMAT = "%Y-%m-%d"
33
34 class hr_contract(osv.osv):
35     """
36     Employee contract allows to add different values in fields.
37     Fields are used in salary rule computation.
38     """
39
40     _inherit = 'hr.contract'
41     _description = 'HR Contract'
42
43     _columns = {
44         'tds': fields.float('TDS', digits_compute=dp.get_precision('Payroll'), help="Amount for Tax Deduction at Source"),
45         'driver_salay': fields.boolean('Driver Salary', help="Check this box if you provide allowance for driver"),
46         'medical_insurance': fields.float('Medical Insurance', digits_compute=dp.get_precision('Payroll'), help="Deduction towards company provided medical insurance"),
47         'voluntary_provident_fund': fields.float('Voluntary Provident Fund (%)', digits_compute=dp.get_precision('Payroll'), help="VPF is a safe option wherein you can contribute more than the PF ceiling of 12% that has been mandated by the government and VPF computed as percentage(%)"),
48         'house_rent_allowance_metro_nonmetro': fields.float('House Rent Allowance (%)', digits_compute=dp.get_precision('Payroll'), help="HRA is an allowance given by the employer to the employee for taking care of his rental or accommodation expenses for metro city it is 50 % and for non metro 40%.HRA computed as percentage(%)"),
49         'supplementary_allowance': fields.float('Supplementary Allowance', digits_compute=dp.get_precision('Payroll')),
50     }
51
52 hr_contract()
53
54 class payroll_advice(osv.osv):
55     '''
56     Bank Advice
57     '''
58     _name = 'hr.payroll.advice'
59     _description = 'Bank Advice'
60     _columns = {
61         'name':fields.char('Name', size=32, readonly=True, required=True, states={'draft': [('readonly', False)]},),
62         'note': fields.text('Description'),
63         'date': fields.date('Date', readonly=True, required=True, states={'draft': [('readonly', False)]}, help="Advice Date is used to search Payslips"),
64         'state':fields.selection([
65             ('draft', 'Draft'),
66             ('confirm', 'Confirmed'),
67             ('cancel', 'Cancelled'),
68         ], 'Status', select=True, readonly=True),
69         'number':fields.char('Reference', size=16, readonly=True),
70         'line_ids':fields.one2many('hr.payroll.advice.line', 'advice_id', 'Employee Salary', states={'draft': [('readonly', False)]}, readonly=True),
71         'chaque_nos':fields.char('Cheque Numbers', size=256),
72         'neft': fields.boolean('NEFT Transaction', help="Check this box if your company use online transfer for salary"),
73         'company_id':fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft': [('readonly', False)]}),
74         'bank_id':fields.many2one('res.bank', 'Bank', readonly=True, states={'draft': [('readonly', False)]}, help="Select the Bank from which the salary is going to be paid"),
75         'batch_id': fields.many2one('hr.payslip.run', 'Batch', readonly=True)
76     }
77
78     _defaults = {
79         'date': lambda * a: time.strftime('%Y-%m-%d'),
80         'state': lambda * a: 'draft',
81         'company_id': lambda self, cr, uid, context: \
82                 self.pool.get('res.users').browse(cr, uid, uid,
83                     context=context).company_id.id,
84         'note': "Please make the payroll transfer from above account number to the below mentioned account numbers towards employee salaries:"
85     }
86
87     def compute_advice(self, cr, uid, ids, context=None):
88         """
89         Advice - Create Advice lines in Payment Advice and
90         compute Advice lines.
91         @param cr: the current row, from the database cursor,
92         @param uid: the current user’s ID for security checks,
93         @param ids: List of Advice’s IDs
94         @return: Advice lines
95         @param context: A standard dictionary for contextual values
96         """
97         payslip_pool = self.pool.get('hr.payslip')
98         advice_line_pool = self.pool.get('hr.payroll.advice.line')
99         payslip_line_pool = self.pool.get('hr.payslip.line')
100
101         for advice in self.browse(cr, uid, ids, context=context):
102             old_line_ids = advice_line_pool.search(cr, uid, [('advice_id', '=', advice.id)], context=context)
103             if old_line_ids:
104                 advice_line_pool.unlink(cr, uid, old_line_ids, context=context)
105             slip_ids = payslip_pool.search(cr, uid, [('date_from', '<=', advice.date), ('date_to', '>=', advice.date), ('state', '=', 'done')], context=context)
106             for slip in payslip_pool.browse(cr, uid, slip_ids, context=context):
107                 if not slip.employee_id.bank_account_id and not slip.employee_id.bank_account_id.acc_number:
108                     raise osv.except_osv(_('Error !'), _('Please define bank account for the %s employee') % (slip.employee_id.name))
109                 line_ids = payslip_line_pool.search(cr, uid, [ ('slip_id', '=', slip.id), ('code', '=', 'NET')], context=context)
110                 if line_ids:
111                     line = payslip_line_pool.browse(cr, uid, line_ids, context=context)[0]
112                     advice_line = {
113                             'advice_id': advice.id,
114                             'name': slip.employee_id.bank_account_id.acc_number,
115                             'employee_id': slip.employee_id.id,
116                             'bysal': line.total
117                             }
118                     advice_line_pool.create(cr, uid, advice_line, context=context)
119                 payslip_pool.write(cr, uid, slip_ids, {'advice_id': advice.id}, context=context)
120         return True
121
122     def confirm_sheet(self, cr, uid, ids, context=None):
123         """
124         confirm Advice - confirmed Advice after computing Advice Lines..
125         @param cr: the current row, from the database cursor,
126         @param uid: the current user’s ID for security checks,
127         @param ids: List of confirm Advice’s IDs
128         @return: confirmed Advice lines and set sequence of Advice.
129         @param context: A standard dictionary for contextual values
130         """
131         seq_obj = self.pool.get('ir.sequence')
132         for advice in self.browse(cr, uid, ids, context=context):
133             if not advice.line_ids:
134                 raise osv.except_osv(_('Error !'), _('You can not confirm Payment advice without advice lines.'))
135             advice_date = datetime.strptime(advice.date, DATETIME_FORMAT)
136             advice_year = advice_date.strftime('%m') + '-' + advice_date.strftime('%Y')
137             number = seq_obj.get(cr, uid, 'payment.advice')
138             sequence_num = 'PAY' + '/' + advice_year + '/' + number
139             self.write(cr, uid, [advice.id], {'number': sequence_num, 'state': 'confirm'}, context=context)
140         return True
141
142     def set_to_draft(self, cr, uid, ids, context=None):
143         """Resets Advice as draft.
144         """
145         return self.write(cr, uid, ids, {'state':'draft'}, context=context)
146
147     def cancel_sheet(self, cr, uid, ids, context=None):
148         """Marks Advice as cancelled.
149         """
150         return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
151
152     def onchange_company_id(self, cr, uid, ids, company_id=False, context=None):
153         res = {}
154         if company_id:
155             company = self.pool.get('res.company').browse(cr, uid, [company_id], context=context)[0]
156             if company.partner_id.bank_ids:
157                 res.update({'bank_id': company.partner_id.bank_ids[0].bank.id})
158         return {
159             'value':res
160         }
161 payroll_advice()
162
163 class hr_payslip_run(osv.osv):
164
165     _inherit = 'hr.payslip.run'
166     _description = 'Payslip Batches'
167     _columns = {
168         'available_advice': fields.boolean('Made Payment Advice?', help="If this box is checked which means that Payment Advice exists for current batch", readonly=False),
169     }
170     def copy(self, cr, uid, id, default={}, context=None):
171         if not default:
172             default = {}
173         default.update({'available_advice': False})
174         return super(hr_payslip_run, self).copy(cr, uid, id, default, context=context)    
175
176     def draft_payslip_run(self, cr, uid, ids, context=None):
177         res = super(hr_payslip_run, self).draft_payslip_run(cr, uid, ids, context=context)
178         self.write(cr, uid, ids, {'available_advice': False}, context=context)
179         return res
180     
181     def create_advice(self, cr, uid, ids, context=None):
182         wf_service = netsvc.LocalService("workflow")
183         payslip_pool = self.pool.get('hr.payslip')
184         payslip_line_pool = self.pool.get('hr.payslip.line')
185         advice_pool = self.pool.get('hr.payroll.advice')
186         advice_line_pool = self.pool.get('hr.payroll.advice.line')
187         users = self.pool.get('res.users').browse(cr, uid, [uid], context=context)
188         for run in self.browse(cr, uid, ids, context=context):
189             if run.available_advice:
190                 raise osv.except_osv(_('Error !'), _("Payment advice already exists for %s, 'Set to Draft' to create a new advice.") %(run.name))
191             advice_data = {
192                         'batch_id': run.id,
193                         'company_id': users[0].company_id.id,
194                         'name': run.name,
195                         'date': run.date_end,
196                         'bank_id': users[0].company_id.bank_ids and users[0].company_id.bank_ids[0].id or False
197                     }
198             advice_id = advice_pool.create(cr, uid, advice_data, context=context)
199             slip_ids = []
200             for slip_id in run.slip_ids:
201                 wf_service.trg_validate(uid, 'hr.payslip', slip_id.id, 'hr_verify_sheet', cr)
202                 wf_service.trg_validate(uid, 'hr.payslip', slip_id.id, 'process_sheet', cr)
203                 slip_ids.append(slip_id.id)
204
205             for slip in payslip_pool.browse(cr, uid, slip_ids, context=context):
206                 if not slip.employee_id.bank_account_id or not slip.employee_id.bank_account_id.acc_number:
207                     raise osv.except_osv(_('Error !'), _('Please define bank account for the %s employee') % (slip.employee_id.name))
208                 line_ids = payslip_line_pool.search(cr, uid, [('slip_id', '=', slip.id), ('code', '=', 'NET')], context=context)
209                 if line_ids:
210                     line = payslip_line_pool.browse(cr, uid, line_ids, context=context)[0]
211                     advice_line = {
212                             'advice_id': advice_id,
213                             'name': slip.employee_id.bank_account_id.acc_number,
214                             'employee_id': slip.employee_id.id,
215                             'bysal': line.total
216                     }
217                     advice_line_pool.create(cr, uid, advice_line, context=context)
218         return self.write(cr, uid, ids, {'available_advice' : True})
219
220 hr_payslip_run()
221
222 class payroll_advice_line(osv.osv):
223     '''
224     Bank Advice Lines
225     '''
226     def onchange_employee_id(self, cr, uid, ids, employee_id=False, context=None):
227         res = {}
228         hr_obj = self.pool.get('hr.employee')
229         if not employee_id:
230             return {'value': res}
231         employee = hr_obj.browse(cr, uid, [employee_id], context=context)[0]
232         res.update({'name': employee.bank_account_id.acc_number , 'ifsc_code': employee.bank_account_id.bank_bic or ''})
233         return {'value': res}
234
235     _name = 'hr.payroll.advice.line'
236     _description = 'Bank Advice Lines'
237     _columns = {
238         'advice_id': fields.many2one('hr.payroll.advice', 'Bank Advice'),
239         'name': fields.char('Bank Account No.', size=25, required=True),
240         'ifsc_code': fields.char('IFSC Code', size=16),
241         'employee_id': fields.many2one('hr.employee', 'Employee', required=True),
242         'bysal': fields.float('By Salary', digits_compute=dp.get_precision('Payroll')),
243         'debit_credit': fields.char('C/D', size=3, required=False),
244         'company_id': fields.related('advice_id', 'company_id', type='many2one', required=False, relation='res.company', string='Company', store=True),
245         'ifsc': fields.related('advice_id', 'neft', type='boolean', string='IFSC'),
246     }
247     _defaults = {
248         'debit_credit': 'C',
249     }
250
251 payroll_advice_line()
252
253 class hr_payslip(osv.osv):
254     '''
255     Employee Pay Slip
256     '''
257     _inherit = 'hr.payslip'
258     _description = 'Pay Slips'
259     _columns = {
260         'advice_id': fields.many2one('hr.payroll.advice', 'Bank Advice')
261     }
262
263     def copy(self, cr, uid, id, default={}, context=None):
264         if not default:
265             default = {}
266         default.update({'advice_id' : False})
267         return super(hr_payslip, self).copy(cr, uid, id, default, context=context)
268
269 hr_payslip()
270
271 class res_company(osv.osv):
272
273     _inherit = 'res.company'
274     _columns = {
275         'dearness_allowance': fields.boolean('Dearness Allowance', help="Check this box if your company provide Dearness Allowance to employee")
276     }
277     _defaults = {
278         'dearness_allowance': True,
279     }
280
281 res_company()
282
283 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: