openerp-community-reviewer team mailing list archive
-
openerp-community-reviewer team
-
Mailing list archive
-
Message #00188
[Merge] lp:~openerp-community/openerp-hr/7.0-modules-from-addons-hr-ng into lp:openerp-hr
Maxime Chambreuil (http://www.savoirfairelinux.com) has proposed merging lp:~openerp-community/openerp-hr/7.0-modules-from-addons-hr-ng into lp:openerp-hr.
Requested reviews:
HR Core Editors (hr-core-editors)
For more details, see:
https://code.launchpad.net/~openerp-community/openerp-hr/7.0-modules-from-addons-hr-ng/+merge/188169
--
The attached diff has been truncated due to its size.
https://code.launchpad.net/~openerp-community/openerp-hr/7.0-modules-from-addons-hr-ng/+merge/188169
Your team HR Core Editors is requested to review the proposed merge of lp:~openerp-community/openerp-hr/7.0-modules-from-addons-hr-ng into lp:openerp-hr.
=== added directory 'hr_accrual'
=== added file 'hr_accrual/__init__.py'
--- hr_accrual/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_accrual/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_accrual
=== added file 'hr_accrual/__openerp__.py'
--- hr_accrual/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_accrual/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,54 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Accrual',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Accruals
+========
+
+An Accrual is any benefit (usually time) that accrues on behalf of an employee over an extended
+period of time. This can be vacation days, sick days, or a simple time bank. The actual policy
+and mechanics of accrual should be handled by other modules. This module only provides
+the basic framework for recording the data.
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ 'hr_holidays',
+ 'hr_holidays_extension',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/ir.model.access.csv',
+ 'hr_accrual_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_accrual/hr_accrual.py'
--- hr_accrual/hr_accrual.py 1970-01-01 00:00:00 +0000
+++ hr_accrual/hr_accrual.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,70 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import time
+
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as OE_DATEFORMAT
+
+
+class hr_accrual(osv.Model):
+
+ _name = 'hr.accrual'
+ _description = 'Accrual'
+
+ _columns = {
+ 'name': fields.char('Name', size=128, required=True),
+ 'holiday_status_id': fields.many2one('hr.holidays.status', 'Leave'),
+ 'line_ids': fields.one2many('hr.accrual.line', 'accrual_id', 'Accrual Lines', readonly=True),
+ }
+
+ def get_balance(self, cr, uid, ids, employee_id, date=None, context=None):
+
+ if date == None:
+ date = time.strftime(OE_DATEFORMAT)
+
+ res = 0.0
+ cr.execute('''SELECT SUM(amount) from hr_accrual_line \
+ WHERE accrual_id in %s AND employee_id=%s AND date <= %s''',
+ (tuple(ids), employee_id, date))
+ for row in cr.fetchall():
+ res = row[0]
+
+ return res
+
+
+class hr_accrual_line(osv.Model):
+
+ _name = 'hr.accrual.line'
+ _description = 'Accrual Line'
+
+ _columns = {
+ 'date': fields.date('Date', required=True),
+ 'accrual_id': fields.many2one('hr.accrual', 'Accrual', required=True),
+ 'employee_id': fields.many2one('hr.employee', 'Employee', required=True),
+ 'amount': fields.float('Amount', required=True),
+ }
+
+ _defaults = {
+ 'date': time.strftime(OE_DATEFORMAT),
+ }
+
+ _rec_name = 'date'
=== added file 'hr_accrual/hr_accrual_view.xml'
--- hr_accrual/hr_accrual_view.xml 1970-01-01 00:00:00 +0000
+++ hr_accrual/hr_accrual_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="hr_accrual_view_tree" model="ir.ui.view">
+ <field name="name">hr.accrual.tree</field>
+ <field name="model">hr.accrual</field>
+ <field name="arch" type="xml">
+ <tree string="Accruals">
+ <field name="name"/>
+ <field name="holiday_status_id"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="hr_accrual_view_form" model="ir.ui.view">
+ <field name="name">hr.accrual.form</field>
+ <field name="model">hr.accrual</field>
+ <field name="arch" type="xml">
+ <form string="Accrual" version="7.0">
+ <label for="name" string="Name" class="oe_edit_only"/>
+ <h1>
+ <field name="name"/>
+ </h1>
+ <field name="holiday_status_id"/>
+ <group string="Accrual Lines">
+ <field name="line_ids" nolabel="1"/>
+ </group>
+ </form>
+ </field>
+ </record>
+
+ <record id="open_accrual" model="ir.actions.act_window">
+ <field name="name">Accrual Time Banks</field>
+ <field name="res_model">hr.accrual</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem action="open_accrual"
+ id="menu_hr_accrual"
+ parent="hr.menu_hr_configuration"
+ sequence="45"/>
+
+ </data>
+</openerp>
=== added directory 'hr_accrual/security'
=== added file 'hr_accrual/security/ir.model.access.csv'
--- hr_accrual/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_accrual/security/ir.model.access.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_hr_accrual_manager,access_hr_accrual,model_hr_accrual,base.group_hr_manager,1,1,1,1
+access_hr_accrual_line_manager,access_hr_accrual_line,model_hr_accrual_line,base.group_hr_manager,1,1,1,1
+access_hr_accrual_leave_approval,access_hr_accrual,model_hr_accrual,hr_holidays_extension.group_hr_leave,1,1,1,1
+access_hr_accrual_line_leave_approval,access_hr_accrual_line,model_hr_accrual_line,hr_holidays_extension.group_hr_leave,1,1,1,1
=== added directory 'hr_contract_init'
=== added file 'hr_contract_init/__init__.py'
--- hr_contract_init/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_contract_init/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_contract
=== added file 'hr_contract_init/__openerp__.py'
--- hr_contract_init/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_contract_init/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,56 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Contracts - Initial Settings',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Define Initial Settings on New Contracts
+========================================
+ - Starting Wages
+ - Salary Structure
+ - Trial Period Length
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ 'hr_contract',
+ 'hr_job_categories',
+ 'hr_payroll',
+ 'hr_security',
+ 'hr_simplify',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/ir.model.access.csv',
+ 'hr_contract_init_workflow.xml',
+ 'hr_contract_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_contract_init/hr_contract.py'
--- hr_contract_init/hr_contract.py 1970-01-01 00:00:00 +0000
+++ hr_contract_init/hr_contract.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,252 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from datetime import datetime, timedelta
+from dateutil.relativedelta import relativedelta
+
+from openerp import netsvc
+from openerp.addons import decimal_precision as dp
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as OE_DFORMAT
+from openerp.tools.translate import _
+
+
+class contract_init(osv.Model):
+
+ _name = 'hr.contract.init'
+ _description = 'Initial Contract Settings'
+
+ _inherit = 'ir.needaction_mixin'
+
+ _columns = {
+ 'name': fields.char('Name', size=64, required=True, readonly=True,
+ states={'draft': [('readonly', False)]}),
+ 'date': fields.date('Effective Date', required=True, readonly=True,
+ states={'draft': [('readonly', False)]}),
+ 'wage_ids': fields.one2many('hr.contract.init.wage', 'contract_init_id',
+ 'Starting Wages', readonly=True,
+ states={'draft': [('readonly', False)]}),
+ 'struct_id': fields.many2one('hr.payroll.structure', 'Payroll Structure', readonly=True,
+ states={'draft': [('readonly', False)]}),
+ 'trial_period': fields.integer('Trial Period', readonly=True,
+ states={'draft': [('readonly', False)]},
+ help="Length of Trial Period, in days"),
+ 'active': fields.boolean('Active'),
+ 'state': fields.selection([('draft', 'Draft'),
+ ('approve', 'Approved'),
+ ('decline', 'Declined')], 'State', readonly=True),
+ }
+
+ _defaults = {
+ 'trial_period': 0,
+ 'active': True,
+ 'state': 'draft',
+ }
+
+ # Return records with latest date first
+ _order = 'date desc'
+
+ def _needaction_domain_get(self, cr, uid, context=None):
+
+ users_obj = self.pool.get('res.users')
+ domain = []
+
+ if users_obj.has_group(cr, uid, 'base.group_hr_director'):
+ domain = [('state', 'in', ['draft'])]
+ return domain
+
+ return False
+
+ def unlink(self, cr, uid, ids, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+ data = self.read(cr, uid, ids, ['state'], context=context)
+ for d in data:
+ if d['state'] in ['approve', 'decline']:
+ raise osv.except_osv(_('Error'),
+ _('You may not a delete a record that is not in a "Draft" state'))
+ return super(contract_init, self).unlink(cr, uid, ids, context=context)
+
+ def set_to_draft(self, cr, uid, ids, context=None):
+ self.write(cr, uid, ids, {
+ 'state': 'draft',
+ })
+ wf_service = netsvc.LocalService("workflow")
+ for i in ids:
+ wf_service.trg_delete(uid, 'hr.contract.init', i, cr)
+ wf_service.trg_create(uid, 'hr.contract.init', i, cr)
+ return True
+
+ def state_approve(self, cr, uid, ids, context=None):
+
+ self.write(cr, uid, ids, {'state': 'approve'}, context=context)
+ return True
+
+ def state_decline(self, cr, uid, ids, context=None):
+
+ self.write(cr, uid, ids, {'state': 'decline'}, context=context)
+ return True
+
+
+class init_wage(osv.Model):
+
+ _name = 'hr.contract.init.wage'
+ _description = 'Starting Wages'
+
+ _columns = {
+ 'job_id': fields.many2one('hr.job', 'Job'),
+ 'starting_wage': fields.float('Starting Wage', digits_compute=dp.get_precision('Payroll'),
+ required=True),
+ 'is_default': fields.boolean('Use as Default',
+ help="Use as default wage"),
+ 'contract_init_id': fields.many2one('hr.contract.init', 'Contract Settings'),
+ 'category_ids': fields.many2many('hr.employee.category', 'contract_init_category_rel', 'contract_init_id', 'category_id', 'Tags'),
+ }
+
+ _sql_constraints = [('unique_job_cinit', 'UNIQUE(job_id,contract_init_id)', _(
+ 'A Job Position cannot be referenced more than once in a Contract Settings record.'))]
+
+ def unlink(self, cr, uid, ids, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+ data = self.read(cr, uid, ids, ['contract_init_id'], context=context)
+ for d in data:
+ if not d.get('contract_init_id', False):
+ continue
+ d2 = self.pool.get(
+ 'hr.contract.init').read(cr, uid, d['contract_init_id'][0],
+ ['state'], context=context)
+ if d2['state'] in ['approve', 'decline']:
+ raise osv.except_osv(_('Error'),
+ _('You may not a delete a record that is not in a "Draft" state'))
+ return super(init_wage, self).unlink(cr, uid, ids, context=context)
+
+
+class hr_contract(osv.Model):
+
+ _inherit = 'hr.contract'
+
+ def _get_wage(self, cr, uid, context=None, job_id=None):
+
+ res = 0
+ default = 0
+ init = self.get_latest_initial_values(cr, uid, context=context)
+ if job_id:
+ catdata = self.pool.get('hr.job').read(
+ cr, uid, job_id, ['category_ids'], context=context)
+ else:
+ catdata = False
+ if init != None:
+ for line in init.wage_ids:
+ if job_id != None and line.job_id.id == job_id:
+ res = line.starting_wage
+ elif catdata:
+ cat_id = False
+ category_ids = [c.id for c in line.category_ids]
+ for ci in catdata['category_ids']:
+ if ci in category_ids:
+ cat_id = ci
+ break
+ if cat_id:
+ res = line.starting_wage
+ if line.is_default and default == 0:
+ default = line.starting_wage
+ if res != 0:
+ break
+ if res == 0:
+ res = default
+ return res
+
+ def _get_struct(self, cr, uid, context=None):
+
+ res = False
+ init = self.get_latest_initial_values(cr, uid, context=context)
+ if init != None and init.struct_id:
+ res = init.struct_id.id
+ return res
+
+ def _get_trial_date_start(self, cr, uid, context=None):
+
+ res = False
+ init = self.get_latest_initial_values(cr, uid, context=context)
+ if init != None and init.trial_period and init.trial_period > 0:
+ res = datetime.now().strftime(OE_DFORMAT)
+ return res
+
+ def _get_trial_date_end(self, cr, uid, context=None):
+
+ res = False
+ init = self.get_latest_initial_values(cr, uid, context=context)
+ if init != None and init.trial_period and init.trial_period > 0:
+ dEnd = datetime.now().date() + timedelta(days=init.trial_period)
+ res = dEnd.strftime(OE_DFORMAT)
+ return res
+
+ _defaults = {
+ 'wage': _get_wage,
+ 'struct_id': _get_struct,
+ 'trial_date_start': _get_trial_date_start,
+ 'trial_date_end': _get_trial_date_end,
+ }
+
+ def onchange_job(self, cr, uid, ids, job_id, context=None):
+
+ res = False
+ if job_id:
+ wage = self._get_wage(cr, uid, context=context, job_id=job_id)
+ res = {'value': {'wage': wage}}
+ return res
+
+ def onchange_trial(self, cr, uid, ids, trial_date_start, context=None):
+
+ res = {'value': {'trial_date_end': False}}
+
+ init = self.get_latest_initial_values(cr, uid, context=context)
+ if init != None and init.trial_period and init.trial_period > 0:
+ dStart = datetime.strptime(trial_date_start, OE_DFORMAT)
+ dEnd = dStart + timedelta(days=init.trial_period)
+ res['value']['trial_date_end'] = dEnd.strftime(OE_DFORMAT)
+
+ return res
+
+ def get_latest_initial_values(self, cr, uid, today_str=None, context=None):
+ '''Return a record with an effective date before today_str but greater than all others'''
+
+ init_obj = self.pool.get('hr.contract.init')
+ if today_str == None:
+ today_str = datetime.now().strftime(OE_DFORMAT)
+ dToday = datetime.strptime(today_str, OE_DFORMAT).date()
+
+ res = None
+ ids = init_obj.search(
+ cr, uid, [('date', '<=', today_str), ('state', '=', 'approve')],
+ context=context)
+ for init in init_obj.browse(cr, uid, ids, context=context):
+ d = datetime.strptime(init.date, OE_DFORMAT).date()
+ if d <= dToday:
+ if res == None:
+ res = init
+ elif d > datetime.strptime(res.date, OE_DFORMAT).date():
+ res = init
+
+ return res
=== added file 'hr_contract_init/hr_contract_init_workflow.xml'
--- hr_contract_init/hr_contract_init_workflow.xml 1970-01-01 00:00:00 +0000
+++ hr_contract_init/hr_contract_init_workflow.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Workflow Definition -->
+ <record id="wkf_contract_init" model="workflow">
+ <field name="name">hr.contract.init.basic</field>
+ <field name="osv">hr.contract.init</field>
+ <field name="on_create">True</field>
+ </record>
+
+ <!-- Workflow Activities (Stages) -->
+
+ <record id="act_draft" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract_init"/>
+ <field name="name">draft</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'draft'})</field>
+ <field name="flow_start">True</field>
+ </record>
+
+ <record id="act_approve" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract_init"/>
+ <field name="name">approve</field>
+ <field name="kind">function</field>
+ <field name="action">state_approve()</field>
+ </record>
+
+ <record id="act_decline" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract_init"/>
+ <field name="name">decline</field>
+ <field name="kind">function</field>
+ <field name="action">state_decline()</field>
+ <field name="flow_stop">True</field>
+ </record>
+
+ <!-- Workflow Transitions -->
+
+ <record id="draft2approve" model="workflow.transition">
+ <field name="act_from" ref="act_draft"/>
+ <field name="act_to" ref="act_approve"/>
+ <field name="signal">signal_approve</field>
+ <field name="group_id" ref="hr_security.group_hr_director"/>
+ </record>
+
+ <record id="draft2decline" model="workflow.transition">
+ <field name="act_from" ref="act_draft"/>
+ <field name="act_to" ref="act_decline"/>
+ <field name="signal">signal_decline</field>
+ <field name="group_id" ref="hr_security.group_hr_director"/>
+ </record>
+
+ <record id="approve2decline" model="workflow.transition">
+ <field name="act_from" ref="act_approve"/>
+ <field name="act_to" ref="act_decline"/>
+ <field name="signal">signal_decline</field>
+ <field name="group_id" ref="hr_security.group_hr_director"/>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_contract_init/hr_contract_view.xml'
--- hr_contract_init/hr_contract_view.xml 1970-01-01 00:00:00 +0000
+++ hr_contract_init/hr_contract_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="view_contract_init_tree" model="ir.ui.view">
+ <field name="name">hr.contract.init.tree</field>
+ <field name="model">hr.contract.init</field>
+ <field name="arch" type="xml">
+ <tree string="Contract Initial Values">
+ <field name="name"/>
+ <field name="date"/>
+ <field name="trial_period"/>
+ <field name="struct_id"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_contract_init_form" model="ir.ui.view">
+ <field name="name">hr.contract.init.form</field>
+ <field name="model">hr.contract.init</field>
+ <field name="arch" type="xml">
+ <form string="Contract Initial Values" version="7.0">
+ <header>
+ <button name="signal_approve" type="workflow" states="draft" string="Approve" class="oe_highlight" />
+ <button name="signal_decline" type="workflow" states="draft,approve" string="Decline" class="oe_highlight" />
+ <button string="Reset to New" name="set_to_draft" states="decline" type="object" groups="hr_security.group_hr_director"/>
+ <field name="state" widget="statusbar" statusbar_visible="draft,approve" />
+ </header>
+ <label for="name" class="oe_edit_only"/>
+ <h1>
+ <field name="name"/>
+ </h1>
+ <group>
+ <group>
+ <field name="date"/>
+ <field name="trial_period"/>
+ </group>
+ <group>
+ <field name="struct_id"/>
+ </group>
+ </group>
+ <group string="Initial Wages">
+ <field name="wage_ids" nolabel="1">
+ <tree string="Initial Wages" editable="bottom">
+ <field name="category_ids" widget="many2many_tags"/>
+ <field name="job_id"/>
+ <field name="starting_wage"/>
+ <field name="is_default"/>
+ </tree>
+ </field>
+ </group>
+ </form>
+ </field>
+ </record>
+
+ <record id="open_contract_init" model="ir.actions.act_window">
+ <field name="name">Contract Starting Values</field>
+ <field name="res_model">hr.contract.init</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem action="open_contract_init"
+ id="menu_hr_contract_init"
+ parent="hr_contract.next_id_56"
+ sequence="10"/>
+
+ <!-- Initial Wages -->
+
+ <record id="view_init_wage_tree" model="ir.ui.view">
+ <field name="name">hr.contract.init.wage.tree</field>
+ <field name="model">hr.contract.init.wage</field>
+ <field name="arch" type="xml">
+ <tree string="Initial Wages">
+ <field name="category_ids" widget="many2many_tags"/>
+ <field name="job_id"/>
+ <field name="starting_wage"/>
+ <field name="is_default"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_init_wage_form" model="ir.ui.view">
+ <field name="name">hr.contract.init.wage.form</field>
+ <field name="model">hr.contract.init.wage</field>
+ <field name="arch" type="xml">
+ <form string="Intial Wages" version="7.0">
+ <group>
+ <group>
+ <field name="job_id"/>
+ <field name="starting_wage"/>
+ <field name="is_default"/>
+ </group>
+ <group>
+ <field name="category_ids" widget="many2many_tags"/>
+ </group>
+ </group>
+ </form>
+ </field>
+ </record>
+
+ <!-- Contract -->
+
+ <record id="hr_contract_view_form" model="ir.ui.view">
+ <field name="name">hr.contract.view.form.contract_init</field>
+ <field name="model">hr.contract</field>
+ <field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='trial_date_start']" position="replace">
+ <field name="trial_date_start" class="oe_inline" on_change="onchange_trial(trial_date_start)"/> -
+ </xpath>
+ </field>
+ </record>
+
+ <record id="hr_contract_view_form" model="ir.ui.view">
+ <field name="name">hr.contract.view.form.contract_init</field>
+ <field name="model">hr.contract</field>
+ <field name="inherit_id" ref="hr_simplify.view_contract_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='job_id']" position="replace">
+ <field name="job_id" required="1" domain="[('department_id', '=', employee_dept_id)]" on_change="onchange_job(job_id)"/>
+ </xpath>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_contract_init/security'
=== added file 'hr_contract_init/security/ir.model.access.csv'
--- hr_contract_init/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_contract_init/security/ir.model.access.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,7 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_hr_contract_init_hro,access_hr_contract_initial,model_hr_contract_init,base.group_hr_user,1,0,0,0
+access_hr_contract_init_hrd,access_hr_contract_initial,model_hr_contract_init,hr_security.group_hr_director,1,1,1,1
+access_hr_contract_init_payroll,access_hr_contract_initial,model_hr_contract_init,hr_security.group_payroll_manager,1,0,0,0
+access_hr_contract_init_wage_hro,access_hr_contract_initial_wage,model_hr_contract_init_wage,base.group_hr_user,1,0,0,0
+access_hr_contract_init_wage_hrd,access_hr_contract_initial_wage,model_hr_contract_init_wage,hr_security.group_hr_director,1,1,1,1
+access_hr_contract_init_wage_payroll,access_hr_contract_initial_wage,model_hr_contract_init_wage,hr_security.group_payroll_manager,1,0,0,0
=== added directory 'hr_contract_reference'
=== added file 'hr_contract_reference/__init__.py'
--- hr_contract_reference/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_contract_reference/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_contract
=== added file 'hr_contract_reference/__openerp__.py'
--- hr_contract_reference/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_contract_reference/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,44 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ "name": "HR Contract Reference",
+ "version": "1.0",
+ "category": "Generic Modules/Human Resources",
+ "description": """
+HR Contract Reference
+=====================
+This module provides :
+ - Unique reference number for each employee contract
+ - Automatically generated
+ """,
+ "author": "Michael Telahun Makonnen <mmakonnen@xxxxxxxxx",
+ "website": "http://miketelahun.wordpress.com",
+ "depends": ["hr_contract"],
+ "init_xml": [],
+ 'update_xml': [
+ 'hr_contract_view.xml',
+ 'hr_contract_sequence.xml',
+ ],
+ 'demo_xml': [],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_contract_reference/hr_contract.py'
--- hr_contract_reference/hr_contract.py 1970-01-01 00:00:00 +0000
+++ hr_contract_reference/hr_contract.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,41 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from osv import fields, osv
+
+
+class hr_contract(osv.osv):
+
+ _inherit = 'hr.contract'
+
+ _columns = {
+ 'name': fields.char('Contract Reference', size=32, required=False, readonly=True),
+ }
+
+ def create(self, cr, uid, vals, context=None):
+
+ cid = super(hr_contract, self).create(cr, uid, vals, context)
+ if cid:
+ ref = self.pool.get('ir.sequence').next_by_code(
+ cr, uid, 'contract.ref', context=context)
+ self.pool.get('hr.contract').write(
+ cr, uid, cid, {'name': ref}, context=context)
+ return cid
=== added file 'hr_contract_reference/hr_contract_sequence.xml'
--- hr_contract_reference/hr_contract_sequence.xml 1970-01-01 00:00:00 +0000
+++ hr_contract_reference/hr_contract_sequence.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data noupdate="1">
+ <record id="seq_type_contract_ref" model="ir.sequence.type">
+ <field name="name">Contract Reference</field>
+ <field name="code">contract.ref</field>
+ </record>
+ <record id="seq_contract_ref" model="ir.sequence">
+ <field name="name">Contract Reference</field>
+ <field name="code">contract.ref</field>
+ <field name="prefix">EC/%(year)s/</field>
+ <field name="padding">5</field>
+ </record>
+ </data>
+</openerp>
\ No newline at end of file
=== added file 'hr_contract_reference/hr_contract_view.xml'
--- hr_contract_reference/hr_contract_view.xml 1970-01-01 00:00:00 +0000
+++ hr_contract_reference/hr_contract_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="hr_contract_view" model="ir.ui.view">
+ <field name="name">hr.contract.form.view.inherit.ref</field>
+ <field name="model">hr.contract</field>
+ <field name="type">form</field>
+ <field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
+ <field name="arch" type="xml">
+ <field name="name" position="replace">
+ <field name="name" readonly="1"/>
+ </field>
+ </field>
+ </record>
+ </data>
+</openerp>
\ No newline at end of file
=== added directory 'hr_contract_state'
=== added file 'hr_contract_state/__init__.py'
--- hr_contract_state/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_contract_state/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_contract
=== added file 'hr_contract_state/__openerp__.py'
--- hr_contract_state/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_contract_state/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,54 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Manage Employee Contracts',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Employee Contract Workflow and Notifications
+============================================
+
+Easily find and keep track of employees who are nearing the end of their contracts and
+trial periods.
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr_contract',
+ 'hr_contract_init',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/ir.model.access.csv',
+ 'hr_contract_cron.xml',
+ 'hr_contract_data.xml',
+ 'hr_contract_workflow.xml',
+ 'hr_contract_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_contract_state/hr_contract.py'
--- hr_contract_state/hr_contract.py 1970-01-01 00:00:00 +0000
+++ hr_contract_state/hr_contract.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,247 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import time
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+import netsvc
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
+from openerp.osv import fields, osv
+
+
+class hr_contract(osv.osv):
+
+ _name = 'hr.contract'
+ _inherit = ['hr.contract', 'mail.thread', 'ir.needaction_mixin']
+
+ def _get_ids_from_employee(self, cr, uid, ids, context=None):
+
+ res = []
+ for ee in self.pool.get('hr.employee').browse(cr, uid, ids, context=context):
+ for contract in ee.contract_ids:
+ if contract.state not in ['pending_done', 'done']:
+ res.append(contract.id)
+ return res
+
+ def _get_department(self, cr, uid, ids, field_name, arg, context=None):
+
+ res = dict.fromkeys(ids, False)
+ for contract in self.browse(cr, uid, ids, context=context):
+ if contract.department_id and contract.state in ['pending_done', 'done']:
+ res[contract.id] = contract.department_id.id
+ elif contract.employee_id.department_id:
+ res[contract.id] = contract.employee_id.department_id.id
+ return res
+
+ _columns = {
+ 'state': fields.selection([('draft', 'Draft'),
+ ('trial', 'Trial'),
+ ('trial_ending', 'Trial Period Ending'),
+ ('open', 'Open'),
+ ('contract_ending', 'Ending'),
+ ('pending_done', 'Pending Termination'),
+ ('done', 'Completed')],
+ 'State',
+ readonly=True),
+
+ # store this field in the database and trigger a change only if the contract is
+ # in the right state: we don't want future changes to an employee's department to
+ # impact past contracts that have now ended. Increased priority to
+ # override hr_simplify.
+ 'department_id': fields.function(_get_department, type='many2one', method=True,
+ obj='hr.department', string="Department", readonly=True,
+ store={
+ 'hr.employee': (_get_ids_from_employee, ['department_id'], 10)}),
+
+ # At contract end this field will hold the job_id, and the
+ # job_id field will be set to null so that modules that
+ # reference job_id don't include deactivated employees.
+ 'end_job_id': fields.many2one('hr.job', 'Job Title', readonly=True),
+
+ # The following are redefined again to make them editable only in
+ # certain states
+ 'employee_id': fields.many2one('hr.employee', "Employee", required=True, readonly=True,
+ states={
+ 'draft': [('readonly', False)]}),
+ 'type_id': fields.many2one('hr.contract.type', "Contract Type", required=True, readonly=True,
+ states={'draft': [('readonly', False)]}),
+ 'date_start': fields.date('Start Date', required=True, readonly=True,
+ states={'draft': [('readonly', False)]}),
+ 'wage': fields.float('Wage', digits=(16, 2), required=True, readonly=True,
+ states={'draft': [('readonly', False)]},
+ help="Basic Salary of the employee"),
+ }
+
+ _defaults = {
+ 'state': 'draft',
+ }
+
+ _track = {
+ 'state': {
+ 'hr_contract_state.mt_alert_trial_ending': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'trial_ending',
+ 'hr_contract_state.mt_alert_open': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open',
+ 'hr_contract_state.mt_alert_contract_ending': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'contract_ending',
+ },
+ }
+
+ def _needaction_domain_get(self, cr, uid, context=None):
+
+ users_obj = self.pool.get('res.users')
+ domain = []
+
+ if users_obj.has_group(cr, uid, 'base.group_hr_manager'):
+ domain = [
+ ('state', 'in', ['draft', 'contract_ending', 'trial_ending'])]
+ return domain
+
+ return False
+
+ def onchange_job(self, cr, uid, ids, job_id, context=None):
+
+ import logging
+ _l = logging.getLogger(__name__)
+ _l.warning('hr_contract_state: onchange_job()')
+ res = False
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+ if ids:
+ contract = self.browse(cr, uid, ids[0], context=None)
+ if contract.state != 'draft':
+ return res
+ return super(hr_contract, self).onchange_job(cr, uid, ids, job_id, context=context)
+
+ def condition_trial_period(self, cr, uid, ids, context=None):
+
+ for contract in self.browse(cr, uid, ids, context=context):
+ if not contract.trial_date_start:
+ return False
+ return True
+
+ def try_signal_ending_contract(self, cr, uid, context=None):
+
+ d = datetime.now().date() + relativedelta(days=+30)
+ ids = self.search(cr, uid, [
+ ('state', '=', 'open'),
+ ('date_end', '<=', d.strftime(
+ DEFAULT_SERVER_DATE_FORMAT))
+ ], context=context)
+ if len(ids) == 0:
+ return
+
+ wkf = netsvc.LocalService('workflow')
+ [wkf.trg_validate(uid, 'hr.contract', contract.id, 'signal_ending_contract', cr)
+ for contract in self.browse(cr, uid, ids, context=context)]
+
+ return
+
+ def try_signal_contract_completed(self, cr, uid, context=None):
+
+ d = datetime.now().date()
+ ids = self.search(cr, uid, [
+ ('state', '=', 'open'),
+ ('date_end', '<', d.strftime(
+ DEFAULT_SERVER_DATE_FORMAT))
+ ], context=context)
+ if len(ids) == 0:
+ return
+
+ wkf = netsvc.LocalService('workflow')
+ [wkf.trg_validate(uid, 'hr.contract', contract.id, 'signal_pending_done', cr)
+ for contract in self.browse(cr, uid, ids, context=context)]
+
+ return
+
+ def try_signal_ending_trial(self, cr, uid, context=None):
+
+ d = datetime.now().date() + relativedelta(days=+10)
+ ids = self.search(cr, uid, [
+ ('state', '=', 'trial'),
+ ('trial_date_end', '<=', d.strftime(
+ DEFAULT_SERVER_DATE_FORMAT))
+ ], context=context)
+ if len(ids) == 0:
+ return
+
+ wkf = netsvc.LocalService('workflow')
+ [wkf.trg_validate(uid, 'hr.contract', contract.id, 'signal_ending_trial', cr)
+ for contract in self.browse(cr, uid, ids, context=context)]
+
+ return
+
+ def try_signal_open(self, cr, uid, context=None):
+
+ d = datetime.now().date() + relativedelta(days=-5)
+ ids = self.search(cr, uid, [
+ ('state', '=', 'trial_ending'),
+ ('trial_date_end', '<=', d.strftime(
+ DEFAULT_SERVER_DATE_FORMAT))
+ ], context=context)
+ if len(ids) == 0:
+ return
+
+ wkf = netsvc.LocalService('workflow')
+ [wkf.trg_validate(uid, 'hr.contract', contract.id, 'signal_open', cr)
+ for contract in self.browse(cr, uid, ids, context=context)]
+
+ return
+
+ def onchange_start(self, cr, uid, ids, date_start, context=None):
+
+ res = {'value': {}}
+ res['value']['trial_date_start'] = date_start
+
+ return res
+
+ def state_trial(self, cr, uid, ids, context=None):
+
+ self.write(cr, uid, ids, {'state': 'trial'}, context=context)
+ return True
+
+ def state_open(self, cr, uid, ids, context=None):
+
+ self.write(cr, uid, ids, {'state': 'open'}, context=context)
+ return True
+
+ def state_pending_done(self, cr, uid, ids, context=None):
+
+ self.write(cr, uid, ids, {'state': 'pending_done'}, context=context)
+ return True
+
+ def state_done(self, cr, uid, ids, context=None):
+
+ for i in ids:
+ data = self.read(
+ cr, uid, i, ['date_end', 'job_id'], context=context)
+ vals = {'state': 'done',
+ 'date_end': False,
+ 'job_id': False,
+ 'end_job_id': data['job_id'][0]}
+
+ if data.get('date_end', False):
+ vals['date_end'] = data['date_end']
+ else:
+ vals['date_end'] = time.strftime(DEFAULT_SERVER_DATE_FORMAT)
+
+ self.write(cr, uid, ids, vals, context=context)
+
+ return True
=== added file 'hr_contract_state/hr_contract_cron.xml'
--- hr_contract_state/hr_contract_cron.xml 1970-01-01 00:00:00 +0000
+++ hr_contract_state/hr_contract_cron.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record model="ir.cron" id="hr_contract_ending_cron">
+ <field name="name">Contracts Nearing Completion</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">days</field>
+ <field name="numbercall">-1</field>
+ <field eval="(DateTime.now() + timedelta(days= +1)).strftime('%Y-%m-%d 2:30:00')" name="nextcall"/>
+ <field eval="True" name="doall"/>
+ <field eval="'hr.contract'" name="model"/>
+ <field eval="'try_signal_ending_contract'" name="function"/>
+ <field eval="'()'" name="args"/>
+ </record>
+
+ <record model="ir.cron" id="hr_contract_completed_cron">
+ <field name="name">Contracts That Have Ended</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">days</field>
+ <field name="numbercall">-1</field>
+ <field eval="(DateTime.now() + timedelta(days= +1)).strftime('%Y-%m-%d 2:45:00')" name="nextcall"/>
+ <field eval="True" name="doall"/>
+ <field eval="'hr.contract'" name="model"/>
+ <field eval="'try_signal_contract_completed'" name="function"/>
+ <field eval="'()'" name="args"/>
+ </record>
+
+ <record model="ir.cron" id="hr_trial_period_ending_cron">
+ <field name="name">Contracts Nearing the End of the Trial Period</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">days</field>
+ <field name="numbercall">-1</field>
+ <field eval="(DateTime.now() + timedelta(days= +1)).strftime('%Y-%m-%d 3:00:00')" name="nextcall"/>
+ <field eval="True" name="doall"/>
+ <field eval="'hr.contract'" name="model"/>
+ <field eval="'try_signal_ending_trial'" name="function"/>
+ <field eval="'()'" name="args"/>
+ </record>
+
+ <record model="ir.cron" id="hr_trial_period_ended_cron">
+ <field name="name">Contracts Past the End of the Trial Period</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">days</field>
+ <field name="numbercall">-1</field>
+ <field eval="(DateTime.now() + timedelta(days= +1)).strftime('%Y-%m-%d 3:30:00')" name="nextcall"/>
+ <field eval="True" name="doall"/>
+ <field eval="'hr.contract'" name="model"/>
+ <field eval="'try_signal_open'" name="function"/>
+ <field eval="'()'" name="args"/>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_contract_state/hr_contract_data.xml'
--- hr_contract_state/hr_contract_data.xml 1970-01-01 00:00:00 +0000
+++ hr_contract_state/hr_contract_data.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Alert-related subtypes for messaging / Chatter -->
+
+ <record id="mt_alert_trial_ending" model="mail.message.subtype">
+ <field name="name">Trial Period Ending</field>
+ <field name="res_model">hr.contract</field>
+ <field name="description">Trial Period is ending</field>
+ </record>
+
+ <record id="mt_alert_contract_ending" model="mail.message.subtype">
+ <field name="name">Contract Period Ending</field>
+ <field name="res_model">hr.contract</field>
+ <field name="description">Contract Period is ending</field>
+ </record>
+
+ <record id="mt_alert_open" model="mail.message.subtype">
+ <field name="name">Contract Open</field>
+ <field name="res_model">hr.contract</field>
+ <field name="description">Contract is Open</field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_contract_state/hr_contract_view.xml'
--- hr_contract_state/hr_contract_view.xml 1970-01-01 00:00:00 +0000
+++ hr_contract_state/hr_contract_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <!-- Menus -->
+
+ <menuitem name="Contracts"
+ id="submenu_hr_contracts"
+ parent="hr.menu_hr_main"
+ sequence="100" groups="base.group_hr_user"/>
+
+ <!-- Remove stock Contracts menuitem and put it in the submenu -->
+ <menuitem action="hr_contract.action_hr_contract" id="hr_contract.hr_menu_contract" parent="submenu_hr_contracts" name="Contracts" sequence="10" groups="base.group_hr_user"/>
+
+ <record id="hr_contract_view_tree" model="ir.ui.view">
+ <field name="name">hr.contract.tree.state</field>
+ <field name="model">hr.contract</field>
+ <field name="inherit_id" ref="hr_contract.hr_contract_view_tree"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//field[@name='employee_id']" position="after">
+ <field name="department_id"/>
+ </xpath>
+ <xpath expr="//field[@name='date_end']" position="after">
+ <field name="state"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ <record id="view_expiring_contracts_tree" model="ir.ui.view">
+ <field name="name">hr.contract.contract.expire.tree</field>
+ <field name="model">hr.contract</field>
+ <field name="arch" type="xml">
+ <tree string="Expiring Conracts">
+ <field name="employee_id"/>
+ <field name="department_id"/>
+ <field name="job_id"/>
+ <field name="trial_date_end"/>
+ <field name="date_end"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+ <record id="open_expiring_contracts" model="ir.actions.act_window">
+ <field name="name">Ending Trials & Contracts</field>
+ <field name="res_model">hr.contract</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" ref="view_expiring_contracts_tree"/>
+ <field name="domain">['|',('state','in',['contract_ending']),('state','in',['trial_ending'])]</field>
+ <field name="help" type="html">
+ <p>
+ There are currently no contracts or trial periods that are about to expire.
+ </p>
+ </field>
+ </record>
+ <menuitem action="open_expiring_contracts"
+ id="menu_expiring_contracts"
+ parent="submenu_hr_contracts"
+ groups="base.group_hr_user"
+ sequence="20"/>
+
+ <record id="view_draft_contracts_tree" model="ir.ui.view">
+ <field name="name">hr.contract.contract.draft.tree</field>
+ <field name="model">hr.contract</field>
+ <field name="arch" type="xml">
+ <tree string="Contracts to be Approved">
+ <field name="employee_id"/>
+ <field name="department_id"/>
+ <field name="job_id"/>
+ <field name="date_start"/>
+ <field name="trial_date_end"/>
+ <field name="date_end"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+ <record id="open_draft_contracts" model="ir.actions.act_window">
+ <field name="name">Contracts to be Approved</field>
+ <field name="res_model">hr.contract</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" ref="view_draft_contracts_tree"/>
+ <field name="domain">[('state','in',['draft'])]</field>
+ <field name="help" type="html">
+ <p>
+ There are currently no contracts that need to be approved.
+ </p>
+ </field>
+ </record>
+ <menuitem action="open_draft_contracts"
+ id="menu_draft_contracts"
+ parent="submenu_hr_contracts"
+ groups="base.group_hr_user"
+ sequence="30"/>
+
+ <!-- HR Contract: Form View -->
+ <record id="view_contract_form" model="ir.ui.view">
+ <field name="name">hr.contract.form.inherit.state</field>
+ <field name="model">hr.contract</field>
+ <field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//div[@class='oe_title']" position="before">
+ <header>
+ <button name="signal_confirm" type="workflow" string="Confirm"
+ groups="base.group_hr_manager" states="draft" class="oe_highlight"/>
+ <button name="signal_open" type="workflow" string="Trial Successfull"
+ groups="base.group_hr_manager" states="trial_ending" class="oe_highlight"/>
+ <button name="signal_done" type="workflow" string="End Contract"
+ groups="base.group_hr_manager" states="trial,trial_ending,open,contract_ending,pending_done" class="oe_highlight"/>
+ <field name="state" widget="statusbar"/>
+ </header>
+ </xpath>
+ <xpath expr="//field[@name='date_start']" position="replace">
+ <field name="date_start" class="oe_inline" on_change="onchange_start(date_start)"/> -
+ </xpath>
+ <xpath expr="//field[@name='trial_date_end']" position="replace">
+ <field name="trial_date_end" class="oe_inline" attrs="{'required':[('trial_date_start','!=',False)]}"/>
+ </xpath>
+ <xpath expr="//sheet" position="after">
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ <record id="contract_view_init_form" model="ir.ui.view">
+ <field name="name">hr.contract.view.form.contract_init</field>
+ <field name="model">hr.contract</field>
+ <field name="inherit_id" ref="hr_contract_init.hr_contract_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='job_id']" position="replace">
+ <field name="job_id" required="1" domain="[('department_id', '=', employee_dept_id)]" on_change="onchange_job(job_id)" attrs="{'invisible': [('state','=','done')]}"/>
+ <field name="end_job_id" attrs="{'invisible': [('state','!=','done')]}"/>
+ </xpath>
+ <xpath expr="//field[@name='trial_date_start']" position="replace">
+ <field name="trial_date_start" class="oe_inline" on_change="onchange_trial(trial_date_start)" attrs="{'required':[('trial_date_end','!=',False)]}"/> -
+ </xpath>
+ </field>
+ </record>
+
+ <record id="hr_hr_employee_view_form2" model="ir.ui.view">
+ <field name="name">hr.employee.view.form.contract_state</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr_contract.hr_hr_employee_view_form2"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//button[@string='Contracts']" position="replace">
+ <button name="%(hr_contract.act_hr_employee_2_hr_contract)d" string="Contracts" type="action" groups="base.group_hr_user"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_contract_state/hr_contract_workflow.xml'
--- hr_contract_state/hr_contract_workflow.xml 1970-01-01 00:00:00 +0000
+++ hr_contract_state/hr_contract_workflow.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Contract Workflow Definition -->
+ <record id="wkf_contract" model="workflow">
+ <field name="name">hr.contract.basic</field>
+ <field name="osv">hr.contract</field>
+ <field name="on_create">True</field>
+ </record>
+
+ <!-- Workflow Activities (Stages) -->
+
+ <record id="act_draft" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract"/>
+ <field name="name">draft</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'draft'})</field>
+ <field name="flow_start">True</field>
+ </record>
+
+ <record id="act_trial" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract"/>
+ <field name="name">trial</field>
+ <field name="kind">function</field>
+ <field name="action">state_trial()</field>
+ </record>
+
+ <record id="act_trial_ending" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract"/>
+ <field name="name">trial_ending</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'trial_ending'})</field>
+ </record>
+
+ <record id="act_open" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract"/>
+ <field name="name">open</field>
+ <field name="kind">function</field>
+ <field name="action">state_open()</field>
+ </record>
+
+ <record id="act_contract_ending" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract"/>
+ <field name="name">contract_ending</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'contract_ending'})</field>
+ </record>
+
+ <record id="act_pending_done" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract"/>
+ <field name="name">pending_done</field>
+ <field name="kind">function</field>
+ <field name="action">state_pending_done()</field>
+ </record>
+
+ <record id="act_done" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_contract"/>
+ <field name="name">done</field>
+ <field name="kind">function</field>
+ <field name="action">state_done()</field>
+ <field name="flow_stop">True</field>
+ </record>
+
+ <!-- Workflow Transitions -->
+
+ <record id="draft2trial" model="workflow.transition">
+ <field name="act_from" ref="act_draft"/>
+ <field name="act_to" ref="act_trial"/>
+ <field name="condition">condition_trial_period() == True</field>
+ <field name="signal">signal_confirm</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="draft2open" model="workflow.transition">
+ <field name="act_from" ref="act_draft"/>
+ <field name="act_to" ref="act_open"/>
+ <field name="condition">condition_trial_period() == False</field>
+ <field name="signal">signal_confirm</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="trial2end_trial" model="workflow.transition">
+ <field name="act_from" ref="act_trial"/>
+ <field name="act_to" ref="act_trial_ending"/>
+ <field name="signal">signal_ending_trial</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="trial2pending_done" model="workflow.transition">
+ <field name="act_from" ref="act_trial"/>
+ <field name="act_to" ref="act_pending_done"/>
+ <field name="signal">signal_pending_done</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="trial2done" model="workflow.transition">
+ <field name="act_from" ref="act_trial"/>
+ <field name="act_to" ref="act_done"/>
+ <field name="signal">signal_done</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="end_trial2open" model="workflow.transition">
+ <field name="act_from" ref="act_trial_ending"/>
+ <field name="act_to" ref="act_open"/>
+ <field name="signal">signal_open</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="end_trial2pending_done" model="workflow.transition">
+ <field name="act_from" ref="act_trial_ending"/>
+ <field name="act_to" ref="act_pending_done"/>
+ <field name="signal">signal_pending_done</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="end_trial2done" model="workflow.transition">
+ <field name="act_from" ref="act_trial_ending"/>
+ <field name="act_to" ref="act_done"/>
+ <field name="signal">signal_done</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="open2end_contract" model="workflow.transition">
+ <field name="act_from" ref="act_open"/>
+ <field name="act_to" ref="act_contract_ending"/>
+ <field name="signal">signal_ending_contract</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="open2pending_done" model="workflow.transition">
+ <field name="act_from" ref="act_open"/>
+ <field name="act_to" ref="act_pending_done"/>
+ <field name="signal">signal_pending_done</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="open2done" model="workflow.transition">
+ <field name="act_from" ref="act_open"/>
+ <field name="act_to" ref="act_done"/>
+ <field name="signal">signal_done</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="end_contract2pending_done" model="workflow.transition">
+ <field name="act_from" ref="act_contract_ending"/>
+ <field name="act_to" ref="act_pending_done"/>
+ <field name="signal">signal_pending_done</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="end_contract2done" model="workflow.transition">
+ <field name="act_from" ref="act_contract_ending"/>
+ <field name="act_to" ref="act_done"/>
+ <field name="signal">signal_done</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="pending_done2open" model="workflow.transition">
+ <field name="act_from" ref="act_pending_done"/>
+ <field name="act_to" ref="act_open"/>
+ <field name="signal">signal_open</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="pending_done2done" model="workflow.transition">
+ <field name="act_from" ref="act_pending_done"/>
+ <field name="act_to" ref="act_done"/>
+ <field name="signal">signal_done</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_contract_state/security'
=== added file 'hr_contract_state/security/ir.model.access.csv'
--- hr_contract_state/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_contract_state/security/ir.model.access.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_hr_contract_user,access_hr_contract,model_hr_contract,base.group_hr_user,1,1,1,1
+access_hr_contract_type_user,access_hr_contract_type,hr_contract.model_hr_contract_type,base.group_hr_user,1,0,0,0
=== added directory 'hr_department_sequence'
=== added file 'hr_department_sequence/__init__.py'
--- hr_department_sequence/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_department_sequence/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,21 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyrigth (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_department
=== added file 'hr_department_sequence/__openerp__.py'
--- hr_department_sequence/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_department_sequence/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,45 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyrigth (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Department Sequence',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Order by Parent-Child Relationship and by Sequence Number
+=========================================================
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://www.openerp.com',
+ 'depends': [
+ 'hr',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'hr_department_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_department_sequence/hr_department.py'
--- hr_department_sequence/hr_department.py 1970-01-01 00:00:00 +0000
+++ hr_department_sequence/hr_department.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,39 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyrigth (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from openerp.osv import fields, osv
+
+
+class hr_department(osv.Model):
+
+ _name = 'hr.department'
+ _inherit = 'hr.department'
+
+ _columns = {
+ 'code': fields.char('Code', size=64),
+ 'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of departments."),
+ 'parent_left': fields.integer('Left Parent', select=1),
+ 'parent_right': fields.integer('Right Parent', select=1),
+ }
+
+ _parent_name = "parent_id"
+ _parent_store = True
+ _parent_order = 'sequence, name'
+ _order = 'parent_left'
=== added file 'hr_department_sequence/hr_department_view.xml'
--- hr_department_sequence/hr_department_view.xml 1970-01-01 00:00:00 +0000
+++ hr_department_sequence/hr_department_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="hr_department_view" model="ir.ui.view">
+ <field name="name">hr.department.form.sequence</field>
+ <field name="model">hr.department</field>
+ <field name="inherit_id" ref="hr.view_department_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='name']" position="after">
+ <field name="code"/>
+ </xpath>
+ <xpath expr="//field[@name='company_id']" position="after">
+ <field name="sequence"/>
+ </xpath>
+ </field>
+ </record>
+
+ <record id="hr_department_tree_view" model="ir.ui.view">
+ <field name="name">hr.department.tree.sequence</field>
+ <field name="model">hr.department</field>
+ <field name="inherit_id" ref="hr.view_department_tree"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='complete_name']" position="after">
+ <field name="code"/>
+ <field name="sequence"/>
+ </xpath>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_emergency_contact'
=== added file 'hr_emergency_contact/__init__.py'
--- hr_emergency_contact/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_emergency_contact/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr
=== added file 'hr_emergency_contact/__openerp__.py'
--- hr_emergency_contact/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_emergency_contact/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,46 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'HR Emergency Contact',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Emergency Contact information for Employee
+==========================================
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://www.openerp.com',
+ 'depends': [
+ 'hr',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'hr_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_emergency_contact/emergency_contact.py'
--- hr_emergency_contact/emergency_contact.py 1970-01-01 00:00:00 +0000
+++ hr_emergency_contact/emergency_contact.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,41 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from osv import fields, osv
+
+
+class hr_employee(osv.osv):
+
+ _name = 'hr.employee'
+ _inherit = 'hr.employee'
+
+ _columns = {
+ 'ec_name': fields.char('Name', size=256, required=True),
+ 'ec_relationship': fields.char('Relationship', size=128, required=True),
+ 'ec_tel1': fields.char('Primary Phone No.', size=32),
+ 'ec_tel2': fields.char('Secondary Phone No.', size=32),
+ 'ec_woreda': fields.char('Subcity/Woreda', size=32),
+ 'ec_kebele': fields.char('Kebele', size=8),
+ 'ec_houseno': fields.char('House No.', size=8),
+ 'ec_address': fields.char('Address 2', size=256),
+ 'ec_country_id': fields.many2one('res.country', 'Country'),
+ 'ec_state_id': fields.many2one('res.country.state', 'State', domain="[('country_id','=',country_id)]"),
+ }
=== added file 'hr_emergency_contact/emergency_contact_view.xml'
--- hr_emergency_contact/emergency_contact_view.xml 1970-01-01 00:00:00 +0000
+++ hr_emergency_contact/emergency_contact_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+##############################################################################
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+-->
+
+<openerp>
+ <data>
+
+ <record id="emergency_contact_view_form" model="ir.ui.view">
+ <field name="name">hr.emergency_contact.form</field>
+ <field name="model">hr.emergency_contact</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Emergency Contact">
+ </form>
+ </field>
+ </record>
+
+ <record id="hr_employee_view_form" model="ir.ui.view">
+ <field name="name">hr.employee.view.form.inherit</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <xpath expr='//form/notebook/page[@string="Personal Information"]' position="after">
+ <page string="Emergency Contact">
+ <field name="ec_name"/>
+ <field name="ec_relationship"/>
+ <field name="ec_tel1"/>
+ <field name="ec_tel2"/>
+ <group colspan="4" col="6">
+ <field name="ec_woreda"/>
+ <field name="ec_kebele"/>
+ <field name="ec_houseno"/>
+ </group>
+ <field name="ec_address" colspan="4"/>
+ <field name="ec_country_id"/>
+ <field name="ec_state_id"/>
+ </page>
+ </xpath>
+ </field>
+ </record>
+
+ </data>
+</openerp>
\ No newline at end of file
=== added file 'hr_emergency_contact/hr.py'
--- hr_emergency_contact/hr.py 1970-01-01 00:00:00 +0000
+++ hr_emergency_contact/hr.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,50 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from osv import fields, osv
+
+
+class hr_employee(osv.osv):
+
+ _name = 'hr.employee'
+ _inherit = 'hr.employee'
+
+ _columns = {
+ 'ec_name': fields.char('Name', size=256),
+ 'ec_relationship': fields.char('Relationship', size=64),
+ 'ec_tel1': fields.char('Primary Phone No.', size=32),
+ 'ec_tel2': fields.char('Secondary Phone No.', size=32),
+ 'ec_woreda': fields.char('Subcity/Woreda', size=32),
+ 'ec_kebele': fields.char('Kebele', size=8),
+ 'ec_houseno': fields.char('House No.', size=8),
+ 'ec_address': fields.char('Address 2', size=256),
+ 'ec_country_id': fields.many2one('res.country', 'Country'),
+ 'ec_state_id': fields.many2one('res.country.state', 'State', domain="[('country_id','=',country_id)]"),
+ }
+
+ def _get_country(self, cr, uid, context=None):
+ cid = self.pool.get('res.country').search(
+ cr, uid, [('code', '=', 'ET')], context=context)
+ return cid[0]
+
+ _defaults = {
+ 'ec_country_id': _get_country,
+ }
=== added file 'hr_emergency_contact/hr_view.xml'
--- hr_emergency_contact/hr_view.xml 1970-01-01 00:00:00 +0000
+++ hr_emergency_contact/hr_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+##############################################################################
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+-->
+
+<openerp>
+ <data>
+
+ <record id="hr_employee_view_form" model="ir.ui.view">
+ <field name="name">hr.employee.view.form.inherit</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <xpath expr='//page[@string="Personal Information"]' position="after">
+ <page string="Emergency Contact">
+ <group>
+ <group string="Details">
+ <field name="ec_name"/>
+ <field name="ec_relationship"/>
+ <field name="ec_tel1"/>
+ <field name="ec_tel2"/>
+ </group>
+ <group string="Address">
+ <field name="ec_woreda"/>
+ <field name="ec_kebele"/>
+ <field name="ec_houseno"/>
+ <field name="ec_address"/>
+ <field name="ec_country_id"/>
+ <field name="ec_state_id"/>
+ </group>
+ </group>
+ </page>
+ </xpath>
+ </field>
+ </record>
+
+ </data>
+</openerp>
\ No newline at end of file
=== added directory 'hr_employee_education'
=== added file 'hr_employee_education/__init__.py'
--- hr_employee_education/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_education/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,23 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr
+from . import wizard
=== added file 'hr_employee_education/__openerp__.py'
--- hr_employee_education/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_education/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,49 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Employee Education Records',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Details About and Employee's Education
+======================================
+
+ Add an extra field about an employee's education.
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://www.openerp.com',
+ 'depends': [
+ 'hr',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'wizard/hr_employee_by_department_view.xml',
+ 'hr_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_employee_education/hr.py'
--- hr_employee_education/hr.py 1970-01-01 00:00:00 +0000
+++ hr_employee_education/hr.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,55 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from datetime import datetime
+
+from osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as OE_DFORMAT
+
+EDUCATION_SELECTION = [
+ ('none', 'No Education'),
+ ('primary', 'Primary School'),
+ ('secondary', 'Secondary School'),
+ ('diploma', 'Diploma'),
+ ('degree1', 'First Degree'),
+ ('masters', 'Masters Degree'),
+ ('phd', 'PhD'),
+]
+
+
+class hr_employee(osv.osv):
+
+ _inherit = 'hr.employee'
+
+ def _calculate_age(self, cr, uid, ids, field_name, arg, context=None):
+
+ res = dict.fromkeys(ids, False)
+ for ee in self.browse(cr, uid, ids, context=context):
+ if ee.birthday:
+ dBday = datetime.strptime(ee.birthday, OE_DFORMAT).date()
+ dToday = datetime.now().date()
+ res[ee.id] = (dToday - dBday).days / 365
+ return res
+
+ _columns = {
+ 'education': fields.selection(EDUCATION_SELECTION, 'Education'),
+ 'age': fields.function(_calculate_age, type='integer', method=True, string='Age'),
+ }
=== added file 'hr_employee_education/hr_view.xml'
--- hr_employee_education/hr_view.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_education/hr_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+##############################################################################
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+-->
+
+<openerp>
+ <data>
+
+ <record id="hr_employee_view_form" model="ir.ui.view">
+ <field name="name">hr.employee.view.form.inherit.education</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//group[@string='Birth']" position="after">
+ <group string="Education">
+ <field name="education"/>
+ </group>
+ </xpath>
+ <xpath expr="//field[@name='birthday']" position="after">
+ <field name="age"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ <!-- Employee Personal/Education View -->
+
+ <record id="view_employee_social_tree" model="ir.ui.view">
+ <field name="name">hr.employee.tree</field>
+ <field name="model">hr.employee</field>
+ <field name="arch" type="xml">
+ <tree string="Employees">
+ <field name="name"/>
+ <field name="age"/>
+ <field name="gender"/>
+ <field name="education"/>
+ <field name="marital"/>
+ <field name="company_id" groups="base.group_multi_company"/>
+ <field name="department_id"/>
+ <field name="job_id"/>
+ <field name="parent_id" invisible="1"/>
+ <field name="coach_id" invisible="1"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="open_hr_employee_social_welfare">
+ <field name="name">Employee Social Welfare Report</field>
+ <field name="res_model">hr.employee</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" eval="view_employee_social_tree"/>
+ <field name="search_view_id" ref="hr.view_employee_filter"/>
+ </record>
+ <menuitem name="Employee Social Welfare Report"
+ parent="hr.menu_hr_reporting"
+ id="menu_open_hr_employee_social_welfare"
+ action="open_hr_employee_social_welfare"
+ sequence="40"/>
+
+ </data>
+</openerp>
\ No newline at end of file
=== added directory 'hr_employee_education/wizard'
=== added file 'hr_employee_education/wizard/__init__.py'
--- hr_employee_education/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_education/wizard/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_employee_by_department
=== added file 'hr_employee_education/wizard/hr_employee_by_department.py'
--- hr_employee_education/wizard/hr_employee_by_department.py 1970-01-01 00:00:00 +0000
+++ hr_employee_education/wizard/hr_employee_by_department.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,95 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from openerp.osv import fields, osv
+
+from hr_employee_education.hr import EDUCATION_SELECTION
+
+import logging
+_l = logging.getLogger(__name__)
+
+
+class hr_al(osv.TransientModel):
+
+ _name = 'hr.employee.edu'
+
+ _columns = {
+ 'department_id': fields.many2one('hr.department', 'Department'),
+ 'line_ids': fields.one2many('hr.employee.edu.line', 'edu_id', 'Employee Education Lines'),
+ }
+
+ def _get_lines(self, cr, uid, context=None):
+
+ if context == None:
+ context = {}
+
+ res = []
+ ee_obj = self.pool.get('hr.employee')
+ department_id = context.get('active_id', False)
+ ee_ids = ee_obj.search(cr, uid, [('department_id', '=', department_id),
+ ('active', '=', True)], context=context)
+ data = ee_obj.read(cr, uid, ee_ids, ['education'], context=context)
+ for record in data:
+ vals = {
+ 'employee_id': record['id'],
+ 'days': record['education'],
+ }
+
+ res.append(vals)
+ return res
+
+ def _get_department(self, cr, uid, context=None):
+
+ if context == None:
+ context = {}
+ department_id = context.get('active_id', False)
+ return department_id
+
+ _defaults = {
+ 'department_id': _get_department,
+ 'line_ids': _get_lines,
+ }
+
+ def add_records(self, cr, uid, ids, context=None):
+
+ e_obj = self.pool.get('hr.employee')
+ data = self.read(cr, uid, ids[0], ['line_ids'], context=context)
+ for line in self.pool.get(
+ 'hr.employee.edu.line').browse(cr, uid, data['line_ids'],
+ context=context):
+ if line.education:
+ e_obj.write(
+ cr, uid, line.employee_id.id, {
+ 'education': line.education},
+ context=context)
+
+ return {'type': 'ir.actions.act_window_close'}
+
+
+class hr_edu_line(osv.TransientModel):
+
+ _name = 'hr.employee.edu.line'
+
+ _columns = {
+ 'edu_id': fields.many2one('hr.employee.edu', 'Education by Dept.'),
+ 'employee_id': fields.many2one('hr.employee', 'Employee', required=True),
+ 'education': fields.selection(EDUCATION_SELECTION, 'Education'),
+ }
=== added file 'hr_employee_education/wizard/hr_employee_by_department_view.xml'
--- hr_employee_education/wizard/hr_employee_by_department_view.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_education/wizard/hr_employee_by_department_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="view_edu_form" model="ir.ui.view">
+ <field name="name">hr.employee.edu.form</field>
+ <field name="model">hr.employee.edu</field>
+ <field name="arch" type="xml">
+ <form string="Employee Education by Department" version="7.0">
+ <header>
+ <button name="add_records" type="object" string="Save" class="oe_highlight"/>
+ </header>
+ <div>
+ <h2>
+ <field name="department_id" readonly="1"/>
+ </h2>
+ </div>
+ <group>
+ <group string="Employee Lines">
+ <field name="line_ids" nolabel="1"/>
+ </group>
+ <group></group>
+ </group>
+ </form>
+ </field>
+ </record>
+
+ <record id="view_edu_line_tree" model="ir.ui.view">
+ <field name="name">hr.employee.edu.line.tree</field>
+ <field name="model">hr.employee.edu.line</field>
+ <field name="arch" type="xml">
+ <tree string="Employee Lines" editable="top">
+ <field name="employee_id"/>
+ <field name="education"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_edu_line_form" model="ir.ui.view">
+ <field name="name">hr.employee.edu.line.form</field>
+ <field name="model">hr.employee.edu.line</field>
+ <field name="arch" type="xml">
+ <form string="Employee Education Line">
+ <field name="employee_id"/>
+ <field name="education"/>
+ </form>
+ </field>
+ </record>
+
+ <act_window id="action_hr_employee_education_by_department"
+ name="Employee Education Level"
+ res_model="hr.employee.edu"
+ src_model="hr.department"
+ view_mode="form"
+ key2="client_action_multi"
+ target="new"
+ />
+
+ </data>
+</openerp>
=== added directory 'hr_employee_id'
=== added file 'hr_employee_id/__init__.py'
--- hr_employee_id/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_id/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_employee_id
=== added file 'hr_employee_id/__openerp__.py'
--- hr_employee_id/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_id/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,47 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Employee ID',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Employee Identification Numbers
+===============================
+Company wide unique employee ID
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'hr_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_employee_id/hr_contract.py'
--- hr_employee_id/hr_contract.py 1970-01-01 00:00:00 +0000
+++ hr_employee_id/hr_contract.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,40 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from osv import fields, osv
+
+
+class hr_contract(osv.osv):
+
+ _inherit = 'hr.contract'
+
+ _columns = {
+ 'name': fields.char('Contract Reference', size=32, required=False, readonly=True),
+ }
+
+ def create(self, cr, uid, vals, context=None):
+
+ cid = super(hr_contract, self).create(cr, uid, vals, context)
+ if cid:
+ ref = self.pool.get('ir.sequence').get(cr, uid, 'contract.ref')
+ self.pool.get('hr.contract').write(
+ cr, uid, cid, {'name': ref}, context=context)
+ return cid
=== added file 'hr_employee_id/hr_contract_sequence.xml'
--- hr_employee_id/hr_contract_sequence.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_id/hr_contract_sequence.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data noupdate="1">
+ <record id="seq_type_contract_ref" model="ir.sequence.type">
+ <field name="name">Contract Reference</field>
+ <field name="code">contract.ref</field>
+ </record>
+ <record id="seq_contract_ref" model="ir.sequence">
+ <field name="name">Contract Reference</field>
+ <field name="code">contract.ref</field>
+ <field name="prefix">HR/EC/%(year)s/</field>
+ <field name="padding">5</field>
+ </record>
+ </data>
+</openerp>
\ No newline at end of file
=== added file 'hr_employee_id/hr_contract_view.xml'
--- hr_employee_id/hr_contract_view.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_id/hr_contract_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="hr_contract_view" model="ir.ui.view">
+ <field name="name">hr.contract.form.view.inherit.ref</field>
+ <field name="model">hr.contract</field>
+ <field name="type">form</field>
+ <field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
+ <field name="arch" type="xml">
+ <field name="name" position="replace">
+ <field name="name" readonly="1"/>
+ </field>
+ </field>
+ </record>
+ </data>
+</openerp>
\ No newline at end of file
=== added file 'hr_employee_id/hr_employee_id.py'
--- hr_employee_id/hr_employee_id.py 1970-01-01 00:00:00 +0000
+++ hr_employee_id/hr_employee_id.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,93 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import random
+import string
+from osv import fields, osv
+from tools.translate import _
+
+
+class hr_employee(osv.osv):
+
+ """Implement company wide unique identification number."""
+
+ IDLEN = 8
+
+ _name = 'hr.employee'
+ _inherit = 'hr.employee'
+
+ _columns = {
+ 'employee_no': fields.char('Employee ID',
+ size=IDLEN,
+ readonly=True),
+ # Formatted version of employee ID
+ 'f_employee_no': fields.char('Employee ID',
+ size=IDLEN + 2,
+ readonly=True),
+ 'tin_no': fields.char('TIN No', size=10),
+ }
+
+ _sql_constraints = [
+ ('employeeno_uniq', 'unique(employee_no)',
+ 'The Employee Number must be unique accross the company(s).'),
+ ('tinno_uniq', 'unique(tin_no)',
+ 'There is already another employee with this TIN number.'),
+ ]
+
+ def _check_identification(self, cr, uid, ids, context=None):
+ obj = self.browse(cr, uid, ids[0], context=context)
+ if obj.identification_id or obj.tin_no:
+ return True
+ return False
+
+# _constraints = [
+# (_check_identification, 'At least one of the identification fields must be filled in.', ['identification_id', 'tin_no']),
+# ]
+
+ def _generate_employeeno(self, cr, uid, arg):
+ """Generate a random employee identifacation number"""
+
+ tries = 0
+ max_tries = 50
+ while tries < max_tries:
+ rnd = random.SystemRandom()
+ eid = ''.join(rnd.choice(string.digits)
+ for _ in xrange(self.IDLEN))
+ cr.execute(
+ '''SELECT employee_no FROM hr_employee WHERE employee_no=%s''', tuple((eid,)))
+ res = cr.fetchall()
+ if len(res) == 0:
+ break
+
+ tries += 1
+
+ if tries == max_tries:
+ raise osv.except_osv(
+ _('Error'), _('Unable to generate an Employee ID number that is unique.'))
+
+ return eid
+
+ def create(self, cr, uid, vals, context={}):
+
+ eid = self._generate_employeeno(cr, uid, context)
+ vals['employee_no'] = eid
+ vals['f_employee_no'] = '%s-%s-%s' % (eid[:2], eid[2:4], eid[4:])
+ return super(hr_employee, self).create(cr, uid, vals, context)
=== added file 'hr_employee_id/hr_payroll_view.xml'
--- hr_employee_id/hr_payroll_view.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_id/hr_payroll_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+##############################################################################
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+ Document : hr_payroll_view.xml
+ Created on : April 15, 2011, 11:49 AM
+ Author : mtm
+ Description:
+ Update employee record view to show Employee ID.
+-->
+
+<openerp>
+ <data>
+ <record id="view_employee_form" model="ir.ui.view">
+ <field name="name">hr.employee.form.inherit</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <field name="department_id" position="before">
+ <field name="employee_no"/>
+ </field>
+ </field>
+ </record>
+ </data>
+</openerp>
=== added file 'hr_employee_id/hr_view.xml'
--- hr_employee_id/hr_view.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_id/hr_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+ <data>
+
+ <record id="view_employee_form" model="ir.ui.view">
+ <field name="name">hr.employee.form.inherit</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <div name="button_box" position="after">
+ <div class="oe_title">
+ <label for="f_employee_no" class="oe_edit_only"/>
+ <h2>
+ <field name="f_employee_no"/>
+ </h2>
+ </div>
+ </div>
+ <field name="identification_id" position="after">
+ <field name="tin_no"/>
+ </field>
+ </data>
+ </field>
+ </record>
+
+ <record id="view_employee_tree" model="ir.ui.view">
+ <field name="name">hr.employee.tree.inherit</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_tree"/>
+ <field name="arch" type="xml">
+ <data>
+ <field name="name" position="after">
+ <field name="employee_no" invisible="1"/>
+ <field name="f_employee_no"/>
+ <field name="tin_no"/>
+ </field>
+ </data>
+ </field>
+ </record>
+
+ <record id="view_employee_filter" model="ir.ui.view">
+ <field name="name">Employees</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_filter"/>
+ <field name="arch" type="xml">
+ <field name="name" position="after">
+ <field name="employee_no"/>
+ <field name="tin_no"/>
+ </field>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="hr_kanban_view_employees">
+ <field name="name">HR - Employess Kanban</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.hr_kanban_view_employees"/>
+ <field name="arch" type="xml">
+ <field name="job_id" position="before">
+ <li><field name="f_employee_no"/></li>
+ </field>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_employee_legacy_id'
=== added file 'hr_employee_legacy_id/__init__.py'
--- hr_employee_legacy_id/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_legacy_id/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr
=== added file 'hr_employee_legacy_id/__openerp__.py'
--- hr_employee_legacy_id/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_legacy_id/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,49 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Legacy Employee ID',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Employee Identification Numbers from Previous System
+====================================================
+
+When switching from a legacy HR system to a new one. It may be necessary
+to have a record of the ID No. used in the previous system.
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'hr_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_employee_legacy_id/hr.py'
--- hr_employee_legacy_id/hr.py 1970-01-01 00:00:00 +0000
+++ hr_employee_legacy_id/hr.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,32 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from openerp.osv import fields, osv
+
+
+class hr_employee(osv.Model):
+
+ _name = 'hr.employee'
+ _inherit = 'hr.employee'
+
+ _columns = {
+ 'legacy_no': fields.char('Legacy ID', size=16),
+ }
=== added file 'hr_employee_legacy_id/hr_view.xml'
--- hr_employee_legacy_id/hr_view.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_legacy_id/hr_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+ <data>
+
+ <record id="view_employee_form" model="ir.ui.view">
+ <field name="name">hr.employee.form.inherit</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <field name="active" position="after">
+ <field name="legacy_no"/>
+ </field>
+ </data>
+ </field>
+ </record>
+
+ <record id="view_employee_tree" model="ir.ui.view">
+ <field name="name">hr.employee.tree.inherit</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_tree"/>
+ <field name="arch" type="xml">
+ <data>
+ <field name="name" position="after">
+ <field name="legacy_no" invisible="1"/>
+ </field>
+ </data>
+ </field>
+ </record>
+
+ <record id="view_employee_filter" model="ir.ui.view">
+ <field name="name">Employees</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_filter"/>
+ <field name="arch" type="xml">
+ <field name="name" position="after">
+ <field name="legacy_no"/>
+ </field>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_employee_seniority'
=== added file 'hr_employee_seniority/__init__.py'
--- hr_employee_seniority/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_seniority/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr
=== added file 'hr_employee_seniority/__openerp__.py'
--- hr_employee_seniority/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_seniority/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,47 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Employee Seniority',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Keep Track of Length of Employment
+==================================
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ 'hr_security',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'hr_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_employee_seniority/hr.py'
--- hr_employee_seniority/hr.py 1970-01-01 00:00:00 +0000
+++ hr_employee_seniority/hr.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,153 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from datetime import datetime, date, timedelta
+from dateutil.relativedelta import relativedelta
+
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as OE_DATEFORMAT
+
+
+class hr_employee(osv.Model):
+
+ _inherit = 'hr.employee'
+
+ def _get_contracts_list(self, employee):
+ '''Return list of contracts in chronological order'''
+
+ contracts = []
+ for c in employee.contract_ids:
+ l = len(contracts)
+ if l == 0:
+ contracts.append(c)
+ else:
+ dCStart = datetime.strptime(c.date_start, OE_DATEFORMAT).date()
+ i = l - 1
+ while i >= 0:
+ dContractStart = datetime.strptime(
+ contracts[i].date_start, OE_DATEFORMAT).date()
+ if dContractStart < dCStart:
+ contracts = contracts[:i + 1] + [c] + contracts[i + 1:]
+ break
+ elif i == 0:
+ contracts = [c] + contracts
+ i -= 1
+
+ return contracts
+
+ def _get_days_in_month(self, d):
+
+ last_date = d - timedelta(days=(d.day - 1)) + relativedelta(
+ months= +1) + relativedelta(days= -1)
+ return last_date.day
+
+ def get_months_service_to_date(self, cr, uid, ids, dToday=None, context=None):
+ '''Returns a dictionary of floats. The key is the employee id, and the value is
+ number of months of employment.'''
+
+ res = dict.fromkeys(ids, 0)
+ if dToday == None:
+ dToday = date.today()
+
+ for ee in self.pool.get('hr.employee').browse(cr, uid, ids, context=context):
+
+ delta = relativedelta(dToday, dToday)
+ contracts = self._get_contracts_list(ee)
+ if len(contracts) == 0:
+ res[ee.id] = (0.0, False)
+ continue
+
+ dInitial = datetime.strptime(
+ contracts[0].date_start, OE_DATEFORMAT).date()
+
+ if ee.initial_employment_date:
+ dFirstContract = dInitial
+ dInitial = datetime.strptime(
+ ee.initial_employment_date, '%Y-%m-%d').date()
+ if dFirstContract < dInitial:
+ raise osv.except_osv(_('Employment Date mismatch!'),
+ _('The initial employment date cannot be after the first contract in the system.\nEmployee: %s', ee.name))
+
+ delta = relativedelta(dFirstContract, dInitial)
+
+ for c in contracts:
+ dStart = datetime.strptime(c.date_start, '%Y-%m-%d').date()
+ if dStart >= dToday:
+ continue
+
+ # If the contract doesn't have an end date, use today's date
+ # If the contract has finished consider the entire duration of
+ # the contract, otherwise consider only the months in the
+ # contract until today.
+ #
+ if c.date_end:
+ dEnd = datetime.strptime(c.date_end, '%Y-%m-%d').date()
+ else:
+ dEnd = dToday
+ if dEnd > dToday:
+ dEnd = dToday
+
+ delta += relativedelta(dEnd, dStart)
+
+ # Set the number of months the employee has worked
+ date_part = float(delta.days) / float(
+ self._get_days_in_month(dInitial))
+ res[ee.id] = (
+ float((delta.years * 12) + delta.months) + date_part, dInitial)
+
+ return res
+
+ def _get_employed_months(self, cr, uid, ids, field_name, arg, context=None):
+
+ res = dict.fromkeys(ids, 0.0)
+ _res = self.get_months_service_to_date(cr, uid, ids, context=context)
+ for k, v in _res.iteritems():
+ res[k] = v[0]
+ return res
+
+ def _search_amount(self, cr, uid, obj, name, args, context):
+ ids = set()
+ for cond in args:
+ amount = cond[2]
+ if isinstance(cond[2], (list, tuple)):
+ if cond[1] in ['in', 'not in']:
+ amount = tuple(cond[2])
+ else:
+ continue
+ else:
+ if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
+ continue
+
+ cr.execute("select id from hr_employee having %s %%s" %
+ (cond[1]), (amount,))
+ res_ids = set(id[0] for id in cr.fetchall())
+ ids = ids and (ids & res_ids) or res_ids
+ if ids:
+ return [('id', 'in', tuple(ids))]
+ return [('id', '=', '0')]
+
+ _columns = {
+ 'initial_employment_date': fields.date('Initial Date of Employment', groups=False,
+ help='Date of first employment if it was before the start of the first contract in the system.'),
+ 'length_of_service': fields.function(_get_employed_months, type='float', method=True,
+ groups=False,
+ string='Lenght of Service'),
+ }
=== added file 'hr_employee_seniority/hr_view.xml'
--- hr_employee_seniority/hr_view.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_seniority/hr_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+
+ <!-- Employee Records -->
+
+ <record id="hr_employee_view_tree" model="ir.ui.view">
+ <field name="name">hr.employee.tree.seniority</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_tree"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//field[@name='work_phone']" position="before">
+ <field name="length_of_service"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ <record id="hr_employee_view_form_inherit" model="ir.ui.view">
+ <field name="name">hr.employee.form.seniority</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//group[@name='active_group']" position="after">
+ <group string="Duration of Service">
+ <field name="initial_employment_date"/>
+ <field name="length_of_service"/>
+ </group>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_employee_state'
=== added file 'hr_employee_state/__init__.py'
--- hr_employee_state/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_state/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,23 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr
+from . import wizard
=== added file 'hr_employee_state/__openerp__.py'
--- hr_employee_state/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_state/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,54 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Employment Status',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Employee's Employment Status
+============================
+
+Track the HR status of employees.
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ 'hr_contract_state',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/ir.model.access.csv',
+ 'wizard/end_contract_view.xml',
+ 'hr_employee_data.xml',
+ 'hr_employee_termination_workflow.xml',
+ 'hr_employee_workflow.xml',
+ 'hr_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_employee_state/hr.py'
--- hr_employee_state/hr.py 1970-01-01 00:00:00 +0000
+++ hr_employee_state/hr.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,436 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import time
+
+from datetime import datetime
+
+from openerp import netsvc
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
+from openerp.tools.translate import _
+
+
+class hr_employee(osv.Model):
+
+ _name = 'hr.employee'
+ _inherit = 'hr.employee'
+
+ _columns = {
+ # 'state' is already being used by hr_attendance
+ 'status': fields.selection([
+ ('new', 'New-Hire'),
+ ('onboarding', 'On-Boarding'),
+ ('active', 'Active'),
+ ('pending_inactive', 'Pending Deactivation'),
+ ('inactive', 'Inactive'),
+ ('reactivated', 'Re-Activated'),
+ ],
+ 'Status', readonly=True),
+ 'inactive_ids': fields.one2many('hr.employee.termination', 'employee_id', 'Deactivation Records'),
+ 'saved_department_id': fields.many2one('hr.department', 'Saved Department'),
+ }
+
+ _defaults = {
+ 'status': 'new',
+ }
+
+ def condition_finished_onboarding(self, cr, uid, ids, context=None):
+
+ employee = self.browse(cr, uid, ids[0], context=context)
+ if employee.status == 'onboarding':
+ return True
+
+ return False
+
+ def state_active(self, cr, uid, ids, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ data = self.read(
+ cr, uid, ids, ['status', 'saved_department_id'], context=context)
+ for d in data:
+ if d['status'] and d['status'] == 'pending_inactive':
+ self.write(cr, uid, d['id'],
+ {'status': 'active',
+ 'department_id': d['saved_department_id'] and d['saved_department_id'][0] or False,
+ 'saved_department_id': False},
+ context=context)
+ else:
+ self.write(cr, uid, ids, {'status': 'active'}, context=context)
+
+ return True
+
+ def state_pending_inactive(self, cr, uid, ids, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ data = self.read(cr, uid, ids, ['department_id'], context=context)
+ for d in data:
+ self.write(cr, uid, d['id'],
+ {'status': 'pending_inactive',
+ 'saved_department_id': d['department_id'] and d['department_id'][0] or False,
+ 'department_id': False},
+ context=context)
+
+ return True
+
+ def state_inactive(self, cr, uid, ids, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ data = self.read(
+ cr, uid, ids, ['status', 'saved_department_id'], context=context)
+ for d in data:
+ vals = {
+ 'active': False,
+ 'status': 'inactive',
+ 'job_id': False,
+ }
+ if d['status'] and d['status'] == 'pending_inactive':
+ vals.update(
+ {'department_id': d['saved_department_id'] and d['saved_department_id'][0] or False,
+ 'saved_department_id': False})
+
+ self.pool.get('hr.employee').write(
+ cr, uid, d['id'], vals, context=context)
+
+ return True
+
+ def signal_reactivate(self, cr, uid, ids, context=None):
+
+ for employee in self.browse(cr, uid, ids, context=context):
+ self.write(cr, uid, employee.id, {'active': True}, context=context)
+ netsvc.LocalService('workflow').trg_validate(
+ uid, 'hr.employee', employee.id, 'signal_reactivate', cr)
+
+ return True
+
+
+class hr_employee_termination_reason(osv.Model):
+
+ _name = 'hr.employee.termination.reason'
+ _description = 'Reason for Employment Termination'
+
+ _columns = {
+ 'name': fields.char('Name', size=256, required=True),
+ }
+
+
+class hr_employee_termination(osv.osv):
+
+ _name = 'hr.employee.termination'
+ _description = 'Data Related to Deactivation of Employee'
+
+ _inherit = ['mail.thread', 'ir.needaction_mixin']
+
+ _columns = {
+ 'name': fields.date('Effective Date', required=True, readonly=True,
+ states={'draft': [('readonly', False)]}),
+ 'reason_id': fields.many2one('hr.employee.termination.reason', 'Reason', required=True,
+ readonly=True, states={
+ 'draft': [('readonly', False)]}),
+ 'notes': fields.text('Notes', readonly=True, states={'draft': [('readonly', False)]}),
+ 'employee_id': fields.many2one('hr.employee', 'Employee', required=True, readonly=True),
+ 'department_id': fields.related('employee_id', 'department_id', type='many2one',
+ relation='hr.department', store=True, string="Department"),
+ 'saved_department_id': fields.related('employee_id', 'saved_department_id', type='many2one',
+ relation='hr.department', store=True, string="Department"),
+ 'state': fields.selection([
+ ('draft', 'Draft'),
+ ('confirm', 'Confirmed'),
+ ('cancel', 'Cancelled'),
+ ('done', 'Done'),
+ ],
+ 'State', readonly=True),
+ }
+
+ _defaults = {
+ 'state': 'draft',
+ }
+
+ _track = {
+ 'state': {
+ 'hr_employee_state.mt_alert_state_confirm': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
+ 'hr_employee_state.mt_alert_state_done': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
+ 'hr_employee_state.mt_alert_state_cancel': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancel',
+ },
+ }
+
+ def _needaction_domain_get(self, cr, uid, context=None):
+
+ users_obj = self.pool.get('res.users')
+ domain = []
+
+ if users_obj.has_group(cr, uid, 'base.group_hr_user'):
+ domain = [('state', 'in', ['draft'])]
+
+ if users_obj.has_group(cr, uid, 'base.group_hr_manager'):
+ if len(domain) > 0:
+ domain = ['|'] + domain + [('state', '=', 'confirm')]
+ else:
+ domain = [('state', '=', 'confirm')]
+
+ if len(domain) > 0:
+ return domain
+
+ return False
+
+ def unlink(self, cr, uid, ids, context=None):
+
+ for term in self.browse(cr, uid, ids, context=context):
+ if term.state not in ['draft']:
+ raise osv.except_osv(_('Unable to delete record!'),
+ _('Employment termination already in progress. Use the "Cancel" button instead.'))
+
+ # Trigger employee status change back to Active and contract back
+ # to Open
+ wkf = netsvc.LocalService('workflow')
+ wkf.trg_validate(
+ uid, 'hr.employee', term.employee_id.id, 'signal_active', cr)
+ for contract in term.employee_id.contract_ids:
+ if contract.state == 'pending_done':
+ wkf.trg_validate(
+ uid, 'hr.contract', contract.id, 'signal_open', cr)
+
+ return super(hr_employee_termination, self).unlink(cr, uid, ids, context=context)
+
+ def effective_date_in_future(self, cr, uid, ids, context=None):
+
+ today = datetime.now().date()
+ for term in self.browse(cr, uid, ids, context=context):
+ effective_date = datetime.strptime(
+ term.name, DEFAULT_SERVER_DATE_FORMAT).date()
+ if effective_date <= today:
+ return False
+
+ return True
+
+ def state_cancel(self, cr, uid, ids, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ for term in self.browse(cr, uid, ids, context=context):
+
+ # Trigger a status change of the employee and his contract(s)
+ wkf = netsvc.LocalService('workflow')
+ wkf.trg_validate(
+ uid, 'hr.employee', term.employee_id.id, 'signal_active', cr)
+ for contract in term.employee_id.contract_ids:
+ if contract.state == 'pending_done':
+ wkf.trg_validate(
+ uid, 'hr.contract', contract.id, 'signal_open', cr)
+
+ self.write(cr, uid, term.id, {'state': 'cancel'}, context=context)
+
+ return True
+
+ def state_done(self, cr, uid, ids, context=None):
+
+ for term in self.browse(cr, uid, ids, context=context):
+ if self.effective_date_in_future(cr, uid, [term.id], context=context):
+ raise osv.except_osv(_('Unable to deactivate employee!'),
+ _('Effective date is still in the future.'))
+
+ # Trigger a status change of the employee and any contracts pending
+ # termination.
+ wkf = netsvc.LocalService('workflow')
+ for contract in term.employee_id.contract_ids:
+ if contract.state == 'pending_done':
+ wkf.trg_validate(
+ uid, 'hr.contract', contract.id, 'signal_done', cr)
+ wkf.trg_validate(
+ uid, 'hr.employee', term.employee_id.id, 'signal_inactive', cr)
+
+ self.write(cr, uid, term.id, {'state': 'done'}, context=context)
+
+ return True
+
+
+class hr_contract(osv.Model):
+
+ _name = 'hr.contract'
+ _inherit = 'hr.contract'
+
+ def end_contract(self, cr, uid, ids, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ if len(ids) == 0:
+ return False
+
+ context.update({'end_contract_id': ids[0]})
+ return {
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'hr.contract.end',
+ 'type': 'ir.actions.act_window',
+ 'target': 'new',
+ 'context': context
+ }
+
+ def _state_common(self, cr, uid, ids, context=None):
+
+ wkf = netsvc.LocalService('workflow')
+ for contract in self.browse(cr, uid, ids, context=context):
+ if contract.employee_id.status == 'new':
+ wkf.trg_validate(
+ uid, 'hr.employee', contract.employee_id.id, 'signal_confirm', cr)
+
+ def state_trial(self, cr, uid, ids, context=None):
+ """Override 'trial' contract state to also change employee state: new -> onboarding"""
+
+ res = super(hr_contract, self).state_trial(
+ cr, uid, ids, context=context)
+ self._state_common(cr, uid, ids, context=context)
+ return res
+
+ def state_open(self, cr, uid, ids, context=None):
+ """Override 'open' contract state to also change employee state: new -> onboarding"""
+
+ res = super(hr_contract, self).state_open(
+ cr, uid, ids, context=context)
+ self._state_common(cr, uid, ids, context=context)
+ return res
+
+ def try_signal_contract_completed(self, cr, uid, context=None):
+
+ d = datetime.now().date()
+ ids = self.search(cr, uid, [
+ ('state', '=', 'open'),
+ ('date_end', '<', d.strftime(
+ DEFAULT_SERVER_DATE_FORMAT))
+ ], context=context)
+ if len(ids) == 0:
+ return
+
+ for c in self.browse(cr, uid, ids, context=context):
+ vals = {
+ 'name': c.date_end and c.date_end or time.strftime(DEFAULT_SERVER_DATE_FORMAT),
+ 'employee_id': c.employee_id.id,
+ 'reason': 'contract_end',
+ }
+ self.setup_pending_done(cr, uid, c, vals, context=context)
+
+ return
+
+ def setup_pending_done(self, cr, uid, contract, term_vals, context=None):
+ """Start employee deactivation process."""
+
+ term_obj = self.pool.get('hr.employee.termination')
+ dToday = datetime.now().date()
+
+ # If employee is already inactive simply end the contract
+ wkf = netsvc.LocalService('workflow')
+ if not contract.employee_id.active:
+ wkf.trg_validate(
+ uid, 'hr.contract', contract.id, 'signal_done', cr)
+ return
+
+ # Ensure there are not other open contracts
+ #
+ open_contract = False
+ ee = self.pool.get('hr.employee').browse(
+ cr, uid, contract.employee_id.id, context=context)
+ for c2 in ee.contract_ids:
+ if c2.id == contract.id or c2.state == 'draft':
+ continue
+
+ if (not c2.date_end or datetime.strptime(c2.date_end, DEFAULT_SERVER_DATE_FORMAT).date() >= dToday) and c2.state != 'done':
+ open_contract = True
+
+ # Don't create an employment termination if the employee has an open contract or
+ # if this contract is already in the 'done' state.
+ if open_contract or contract.state == 'done':
+ return
+
+ # Also skip creating an employment termination if there is already one in
+ # progress for this employee.
+ #
+ term_ids = term_obj.search(
+ cr, uid, [('employee_id', '=', contract.employee_id.id),
+ ('state', 'in', ['draft', 'confirm'])],
+ context=context)
+ if len(term_ids) > 0:
+ return
+
+ term_obj.create(cr, uid, term_vals, context=context)
+
+ # Set the contract state to pending completion
+ wkf = netsvc.LocalService('workflow')
+ wkf.trg_validate(
+ uid, 'hr.contract', contract.id, 'signal_pending_done', cr)
+
+ # Set employee state to pending deactivation
+ wkf.trg_validate(
+ uid, 'hr.employee', contract.employee_id.id, 'signal_pending_inactive', cr)
+
+
+class hr_job(osv.Model):
+
+ _name = 'hr.job'
+ _inherit = 'hr.job'
+
+ # Override calculation of number of employees in job. Remove employees for
+ # which the termination process has already started.
+ #
+ def _no_of_employee(self, cr, uid, ids, name, args, context=None):
+ res = {}
+ count = 0
+ for job in self.browse(cr, uid, ids, context=context):
+ for ee in job.employee_ids:
+ if ee.active and ee.status not in ['pending_inactive']:
+ count += 1
+
+ res[job.id] = {
+ 'no_of_employee': count,
+ 'expected_employees': count + job.no_of_recruitment,
+ }
+ return res
+
+ def _get_job_position(self, cr, uid, ids, context=None):
+ res = []
+ data = self.pool.get('hr.employee').read(
+ cr, uid, ids, ['job_id'], context=context)
+ [res.append(d['job_id'][0]) for d in data if d['job_id']]
+ return res
+
+ _columns = {
+ # Override from base class. Also, watch 'status' field of hr.employee
+ 'no_of_employee': fields.function(_no_of_employee, string="Current Number of Employees",
+ help='Number of employees currently occupying this job position.',
+ store={
+ 'hr.employee': (_get_job_position, ['job_id', 'status'], 10),
+ },
+ multi='no_of_employee'),
+ 'expected_employees': fields.function(_no_of_employee, string='Total Forecasted Employees',
+ help='Expected number of employees for this job position after new recruitment.',
+ store={
+ 'hr.job': (lambda self, cr, uid, ids, c=None: ids, ['no_of_recruitment'], 10),
+ 'hr.employee': (_get_job_position, ['job_id', 'status'], 10),
+ },
+ multi='no_of_employee'),
+ }
=== added file 'hr_employee_state/hr_employee_data.xml'
--- hr_employee_state/hr_employee_data.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_state/hr_employee_data.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Alert-related subtypes for messaging / Chatter -->
+
+ <record id="mt_alert_state_confirm" model="mail.message.subtype">
+ <field name="name">Employment Termination - Confirmed</field>
+ <field name="res_model">hr.infraction</field>
+ <field name="description">Employment Termination initiated</field>
+ </record>
+
+ <record id="mt_alert_state_done" model="mail.message.subtype">
+ <field name="name">Employment Termination - Completed</field>
+ <field name="res_model">hr.infraction</field>
+ <field name="description">Completed</field>
+ </record>
+
+ <record id="mt_alert_state_cancel" model="mail.message.subtype">
+ <field name="name">Employment Termination - Cancelled</field>
+ <field name="res_model">hr.infraction</field>
+ <field name="description">Cancelled</field>
+ </record>
+
+ <!-- Employment Termination Reason -->
+ <record id="term_contract_end" model="hr.employee.termination.reason">
+ <field name="name">Contract Ended</field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_employee_state/hr_employee_termination_workflow.xml'
--- hr_employee_state/hr_employee_termination_workflow.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_state/hr_employee_termination_workflow.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <!-- Workflow Definition -->
+
+ <record id="wkf_employee_termination" model="workflow">
+ <field name="name">hr.employee.termination.basic</field>
+ <field name="osv">hr.employee.termination</field>
+ <field name="on_create">True</field>
+ </record>
+
+ <!-- Workflow Activities (States) -->
+
+ <record id="act_draft" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee_termination"/>
+ <field name="name">draft</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'draft'})</field>
+ <field name="flow_start">True</field>
+ </record>
+
+ <record id="act_confirm" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee_termination"/>
+ <field name="name">confirm</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'confirm'})</field>
+ </record>
+
+ <record id="act_done" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee_termination"/>
+ <field name="name">done</field>
+ <field name="kind">function</field>
+ <field name="action">state_done()</field>
+ <field name="flow_stop">True</field>
+ </record>
+
+ <record id="act_cancel" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee_termination"/>
+ <field name="name">cancel</field>
+ <field name="kind">function</field>
+ <field name="action">state_cancel()</field>
+ <field name="flow_stop">True</field>
+ </record>
+
+ <!-- Workflow Transitions -->
+
+ <record id="draft2confirm" model="workflow.transition">
+ <field name="act_from" ref="act_draft"/>
+ <field name="act_to" ref="act_confirm"/>
+ <field name="signal">signal_confirmed</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="confirm2cancel" model="workflow.transition">
+ <field name="act_from" ref="act_confirm"/>
+ <field name="act_to" ref="act_cancel"/>
+ <field name="signal">signal_cancel</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="confirm2done" model="workflow.transition">
+ <field name="act_from" ref="act_confirm"/>
+ <field name="act_to" ref="act_done"/>
+ <field name="signal">signal_done</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_employee_state/hr_employee_workflow.xml'
--- hr_employee_state/hr_employee_workflow.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_state/hr_employee_workflow.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <!-- Employee Workflow Definition -->
+ <record id="wkf_employee" model="workflow">
+ <field name="name">hr.employee.basic</field>
+ <field name="osv">hr.employee</field>
+ <field name="on_create">True</field>
+ </record>
+
+ <!-- Workflow Activities (Stages) -->
+
+ <record id="act_new" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee"/>
+ <field name="name">new</field>
+ <field name="kind">function</field>
+ <field name="action">write({'status': 'new'})</field>
+ <field name="flow_start">True</field>
+ </record>
+
+ <record id="act_onboarding" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee"/>
+ <field name="name">onboarding</field>
+ <field name="kind">function</field>
+ <field name="action">write({'status': 'onboarding'})</field>
+ </record>
+
+ <record id="act_active" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee"/>
+ <field name="name">active</field>
+ <field name="kind">function</field>
+ <field name="action">state_active()</field>
+ </record>
+
+ <record id="act_pending_inactive" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee"/>
+ <field name="name">pending_inactive</field>
+ <field name="kind">function</field>
+ <field name="action">state_pending_inactive()</field>
+ </record>
+
+ <record id="act_inactive" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee"/>
+ <field name="name">inactive</field>
+ <field name="kind">function</field>
+ <field name="action">state_inactive()</field>
+ </record>
+
+ <record id="act_reactivated" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee"/>
+ <field name="name">reactivated</field>
+ <field name="kind">function</field>
+ <field name="action">write({'status': 'reactivated'})</field>
+ </record>
+
+ <!-- Workflow Transitions -->
+
+ <record id="new2onboarding" model="workflow.transition">
+ <field name="act_from" ref="act_new"/>
+ <field name="act_to" ref="act_onboarding"/>
+ <field name="signal">signal_confirm</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="onboarding2active" model="workflow.transition">
+ <field name="act_from" ref="act_onboarding"/>
+ <field name="act_to" ref="act_active"/>
+ <field name="condition">condition_finished_onboarding() == True</field>
+ <field name="signal">signal_active</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="new2pendinginactive" model="workflow.transition">
+ <field name="act_from" ref="act_new"/>
+ <field name="act_to" ref="act_pending_inactive"/>
+ <field name="signal">signal_pending_inactive</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="onboarding2pendinginactive" model="workflow.transition">
+ <field name="act_from" ref="act_onboarding"/>
+ <field name="act_to" ref="act_pending_inactive"/>
+ <field name="signal">signal_pending_inactive</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="active2pendinginactive" model="workflow.transition">
+ <field name="act_from" ref="act_active"/>
+ <field name="act_to" ref="act_pending_inactive"/>
+ <field name="signal">signal_pending_inactive</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="pendinginactive2active" model="workflow.transition">
+ <field name="act_from" ref="act_pending_inactive"/>
+ <field name="act_to" ref="act_active"/>
+ <field name="signal">signal_active</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="pendinginactive2inactive" model="workflow.transition">
+ <field name="act_from" ref="act_pending_inactive"/>
+ <field name="act_to" ref="act_inactive"/>
+ <field name="signal">signal_inactive</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="inactive2reactivated" model="workflow.transition">
+ <field name="act_from" ref="act_inactive"/>
+ <field name="act_to" ref="act_reactivated"/>
+ <field name="signal">signal_reactivate</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="reactivated2onboarding" model="workflow.transition">
+ <field name="act_from" ref="act_reactivated"/>
+ <field name="act_to" ref="act_onboarding"/>
+ <field name="signal">signal_confirm_reactivate</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_employee_state/hr_view.xml'
--- hr_employee_state/hr_view.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_state/hr_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <record id="view_employee_form" model="ir.ui.view">
+ <field name="name">hr.employee.form.inherit.1</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//form/sheet/field[@name='image_medium']" position="before">
+ <header>
+ <button name="signal_confirm" type="workflow" string="Confirm"
+ attrs="{'invisible': [('status','!=','new')]}" groups="base.group_hr_manager" class="oe_highlight"/>
+ <button name="signal_active" type="workflow" string="Finished Onboarding"
+ attrs="{'invisible': [('status','!=','onboarding')]}" groups="base.group_hr_manager" class="oe_highlight"/>
+ <!--<button name="%(action_set_inactive)d" type="action" string="Set Inactive"
+ attrs="{'invisible': [('status','not in',['new', 'onboarding', 'active'])]}" groups="base.group_hr_manager" class="oe_highlight"/>-->
+ <button name="signal_reactivate" type="object" string="Re-Activate"
+ attrs="{'invisible': [('status','!=','inactive')]}" groups="base.group_hr_manager" class="oe_highlight"/>
+ <button name="signal_confirm_reactivate" type="workflow" string="Confirm"
+ attrs="{'invisible': [('status','!=','reactivated')]}" groups="base.group_hr_manager" class="oe_highlight"/>
+ <field name="status" widget="statusbar"/>
+ </header>
+ </xpath>
+ <xpath expr="//page[@string='HR Settings']" position="after">
+ <page string="Deactivation">
+ <group>
+ <group string="Deactivation Records" colspan="4" col="4">
+ <field name="inactive_ids" nolabel="1">
+ <tree string="Inactive Records">
+ <field name="name"/>
+ <field name="reason_id"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </group>
+ </group>
+ </page>
+ </xpath>
+ <xpath expr="//field[@name='department_id']" position="replace">
+ <field name="department_id" on_change="onchange_department_id(department_id)" attrs="{'invisible': [('status','=','pending_inactive')]}"/>
+ <field name="saved_department_id" attrs="{'invisible': [('status','!=','pending_inactive')]}"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+
+ <record id="view_termination_filter" model="ir.ui.view">
+ <field name="name">Employees</field>
+ <field name="model">hr.employee.termination</field>
+ <field name="arch" type="xml">
+ <search string="Employees">
+ <field name="name" string="Employees"/>
+ <field name="department_id" />
+ <filter string="Draft, Confirmed" icon="terp-personal" name="draft_state" domain="[('state','in', ['draft','confirm'])]" help="To be processed"/>
+ <group expand="0" string="Group By...">
+ <filter string="Department" icon="terp-personal+" domain="[]" context="{'group_by':'department_id'}"/>
+ </group>
+ </search>
+ </field>
+ </record>
+
+ <record id="hr_employee_termination_tree_view" model="ir.ui.view">
+ <field name="name">hr.employee.termination.tree</field>
+ <field name="model">hr.employee.termination</field>
+ <field name="arch" type="xml">
+ <tree string="Employment Terminations">
+ <field name="employee_id"/>
+ <field name="department_id"/>
+ <field name="name"/>
+ <field name="reason_id"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="hr_employee_termination_form_view" model="ir.ui.view">
+ <field name="name">hr.employee.termination.form</field>
+ <field name="model">hr.employee.termination</field>
+ <field name="arch" type="xml">
+ <form string="Employment Termination" version="7.0">
+ <sheet>
+ <header>
+ <button name="signal_confirmed" type="workflow" states="draft" string="Confirm" class="oe_highlight"/>
+ <button name="signal_done" type="workflow" states="confirm" string="Deactivate" class="oe_highlight"/>
+ <button name="signal_cancel" type="workflow" states="confirm" string="Cancel" class="oe_highlight"/>
+ <field name="state" widget="statusbar"/>
+ </header>
+ <group>
+ <group>
+ <field name="employee_id"/>
+ <field name="reason_id"/>
+ <field name="name"/>
+ </group>
+ <group>
+ <field name="department_id" attrs="{'invisible': [('state','!=','done')]}"/>
+ <field name="saved_department_id" attrs="{'invisible': [('state','=','done')]}"/>
+ </group>
+ </group>
+ <separator string="Notes"/>
+ <field name="notes" nolabel="1"/>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </field>
+ </record>
+
+ <record id="open_hr_employee_termination" model="ir.actions.act_window">
+ <field name="name">Employment Termination</field>
+ <field name="res_model">hr.employee.termination</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="context">{'search_default_draft_state': 1}</field>
+ <field name="search_view_id" ref="view_termination_filter"/>
+ </record>
+ <menuitem id="menu_hr_employee_termination"
+ action="open_hr_employee_termination"
+ parent="hr.menu_hr_main"
+ sequence="150"/>
+
+ <!-- Termination Reason -->
+
+ <record id="view_termination_reason_tree" model="ir.ui.view">
+ <field name="name">hr.employee.termination.reason.tree</field>
+ <field name="model">hr.employee.termination.reason</field>
+ <field name="arch" type="xml">
+ <tree string="Employment Termination Reasons">
+ <field name="name"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_termination_reason_form" model="ir.ui.view">
+ <field name="name">hr.employee.termination.reason.form</field>
+ <field name="model">hr.employee.termination.reason</field>
+ <field name="arch" type="xml">
+ <form string="Employment Termination Reason">
+ <field name="name"/>
+ </form>
+ </field>
+ </record>
+
+ <record id="open_termination_reason" model="ir.actions.act_window">
+ <field name="name">Employment Termination Reasons</field>
+ <field name="res_model">hr.employee.termination.reason</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem id="menu_hr_employee_termination_reason"
+ action="open_termination_reason"
+ parent="hr.menu_hr_configuration"
+ sequence="5"/>
+
+ <!-- HR Contract -->
+ <record id="view_contract_form" model="ir.ui.view">
+ <field name="name">hr.contract.form.inherit.1</field>
+ <field name="model">hr.contract</field>
+ <field name="inherit_id" ref="hr_contract_state.view_contract_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//button[@name='signal_done']" position="replace">
+ <button name="end_contract" type="object" string="End Contract"
+ groups="base.group_hr_user" states="trial,trial_ending,open,contract_ending" class="oe_highlight"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_employee_state/security'
=== added file 'hr_employee_state/security/ir.model.access.csv'
--- hr_employee_state/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_employee_state/security/ir.model.access.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,6 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_hr_employee_termination_user,access_hr_employee_termination,model_hr_employee_termination,base.group_hr_user,1,1,1,1
+access_hr_employee_termination_pm,access_hr_employee_termination,model_hr_employee_termination,hr_security.group_payroll_manager,1,0,0,0
+access_hr_employee_termination_reason_user,access_hr_employee_termination_reason,model_hr_employee_termination_reason,base.group_hr_user,1,0,0,0
+access_hr_employee_termination_reason_manager,access_hr_employee_termination_reason,model_hr_employee_termination_reason,base.group_hr_manager,1,1,1,1
+access_hr_employee_termination_reason_pm,access_hr_employee_termination_reason,model_hr_employee_termination_reason,hr_security.group_payroll_manager,1,0,0,0
=== added directory 'hr_employee_state/wizard'
=== added file 'hr_employee_state/wizard/__init__.py'
--- hr_employee_state/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_employee_state/wizard/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import end_contract
=== added file 'hr_employee_state/wizard/end_contract.py'
--- hr_employee_state/wizard/end_contract.py 1970-01-01 00:00:00 +0000
+++ hr_employee_state/wizard/end_contract.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,88 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from datetime import datetime
+
+import netsvc
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
+
+
+class employee_set_inactive(osv.TransientModel):
+
+ _name = 'hr.contract.end'
+ _description = 'Employee De-Activation Wizard'
+
+ _columns = {
+ 'contract_id': fields.many2one('hr.contract', 'Contract', readonly=True),
+ 'employee_id': fields.many2one('hr.employee', 'Employee', required=True, readonly=True),
+ 'date': fields.date('Date', required=True),
+ 'reason_id': fields.many2one('hr.employee.termination.reason', 'Reason', required=True),
+ 'notes': fields.text('Notes'),
+ }
+
+ def _get_contract(self, cr, uid, context=None):
+
+ if context == None:
+ context = {}
+
+ return context.get('end_contract_id', False)
+
+ def _get_employee(self, cr, uid, context=None):
+
+ if context == None:
+ context = {}
+
+ contract_id = context.get('end_contract_id', False)
+ if not contract_id:
+ return False
+
+ data = self.pool.get(
+ 'hr.contract').read(cr, uid, contract_id, ['employee_id'],
+ context=context)
+ return data['employee_id'][0]
+
+ _defaults = {
+ 'date': datetime.now().strftime(DEFAULT_SERVER_DATE_FORMAT),
+ 'employee_id': _get_employee,
+ 'contract_id': _get_contract,
+ }
+
+ def set_employee_inactive(self, cr, uid, ids, context=None):
+
+ data = self.read(
+ cr, uid, ids[0], [
+ 'employee_id', 'contract_id', 'date', 'reason_id', 'notes'],
+ context=context)
+ vals = {
+ 'name': data['date'],
+ 'employee_id': data['employee_id'][0],
+ 'reason_id': data['reason_id'][0],
+ 'notes': data['notes'],
+ }
+
+ contract_obj = self.pool.get('hr.contract')
+ contract = contract_obj.browse(
+ cr, uid, data['contract_id'][0], context=context)
+ contract_obj.setup_pending_done(
+ cr, uid, contract, vals, context=context)
+
+ return {'type': 'ir.actions.act_window_close'}
=== added file 'hr_employee_state/wizard/end_contract_view.xml'
--- hr_employee_state/wizard/end_contract_view.xml 1970-01-01 00:00:00 +0000
+++ hr_employee_state/wizard/end_contract_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="action_set_inactive" model="ir.actions.act_window">
+ <field name="name">Employee De-Activation Wizard</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">hr.contract.end</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+
+ <record id="view_inactive_wizard" model="ir.ui.view">
+ <field name="name">hr.contract.end.form</field>
+ <field name="model">hr.contract.end</field>
+ <field name="arch" type="xml">
+ <form string="Employee De-Activation Wizard" version="7.0">
+ <header>
+ <button name="set_employee_inactive" type="object" string="End Employment" class="oe_highlight"/>
+ </header>
+ <newline/>
+ <div>
+ <b>
+ This employee no longer has any valid contracts. In such a case it is
+ customary for the employee record to be archived as an 'inactive' record.
+ However, the record and all the employee's associated data such as contracts,
+ leaves, attendances, etc will still be available. This is not a
+ permanent removal of the employee record. Should you wish to you can
+ easily re-activate it again at any time in the future.
+ </b>
+ </div>
+ <group>
+ <group>
+ <field name="contract_id" invisible="1"/>
+ <field name="employee_id"/>
+ <field name="date"/>
+ <field name="reason_id"/>
+ </group>
+ <group></group>
+ </group>
+ <group string="Notes" colspan="4">
+ <field name="notes" nolabel="1"/>
+ </group>
+ </form>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_family'
=== added file 'hr_family/__init__.py'
--- hr_family/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_family/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr
=== added file 'hr_family/__openerp__.py'
--- hr_family/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_family/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,49 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Family Information',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Employee Family Information
+===========================
+
+ Enter extra information about employee's family
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://www.openerp.com',
+ 'depends': [
+ 'hr',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/ir.model.access.csv',
+ 'hr_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_family/hr.py'
--- hr_family/hr.py 1970-01-01 00:00:00 +0000
+++ hr_family/hr.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,51 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from osv import fields, osv
+
+
+class hr_children(osv.osv):
+
+ _name = 'hr.employee.children'
+ _description = 'HR Employee Children'
+
+ _columns = {
+ 'name': fields.char('Name', size=256, required=True),
+ 'dob': fields.date('Date of Birth'),
+ 'employee_id': fields.many2one('hr.employee', 'Employee'),
+ }
+
+
+class hr_employee(osv.osv):
+
+ _name = 'hr.employee'
+ _inherit = 'hr.employee'
+
+ _columns = {
+ 'fam_spouse': fields.char("Name", size=256),
+ 'fam_spouse_employer': fields.char("Employer", size=256),
+ 'fam_spouse_tel': fields.char("Telephone.", size=32),
+ 'fam_children_ids': fields.one2many('hr.employee.children', 'employee_id', 'Children'),
+ 'fam_father': fields.char("Father's Name", size=128),
+ 'fam_father_dob': fields.date('Date of Birth'),
+ 'fam_mother': fields.char("Mother's Name", size=128),
+ 'fam_mother_dob': fields.date('Date of Birth'),
+ }
=== added file 'hr_family/hr_view.xml'
--- hr_family/hr_view.xml 1970-01-01 00:00:00 +0000
+++ hr_family/hr_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+##############################################################################
+#
+# Copyright (C) 2011 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+-->
+
+<openerp>
+ <data>
+
+ <record id="hr_employee_view_form" model="ir.ui.view">
+ <field name="name">hr.employee.view.form.inherit.familyinfo</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <xpath expr='//page[@string="Personal Information"]' position="after">
+ <page string="Family">
+ <group>
+ <group string="Spouse">
+ <field name="fam_spouse"/>
+ <field name="fam_spouse_employer"/>
+ <field name="fam_spouse_tel"/>
+ </group>
+ <group string="Parents">
+ <field name="fam_father"/>
+ <field name="fam_father_dob"/>
+ <field name="fam_mother"/>
+ <field name="fam_mother_dob"/>
+ </group>
+ </group>
+ <group string="Children">
+ <field name="fam_children_ids" nolabel="1"/>
+ </group>
+ </page>
+ </xpath>
+ </field>
+ </record>
+
+ <record id="hr_children_view_tree" model="ir.ui.view">
+ <field name="name">hr.children.view.tree</field>
+ <field name="model">hr.employee.children</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Employee Children">
+ <field name="name"/>
+ <field name="dob"/>
+ </tree>
+ </field>
+ </record>
+ <record id="hr_children_view_form" model="ir.ui.view">
+ <field name="name">hr.children.view.form</field>
+ <field name="model">hr.employee.children</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Employee Children">
+ <field name="name"/>
+ <field name="dob"/>
+ </form>
+ </field>
+ </record>
+
+ </data>
+</openerp>
\ No newline at end of file
=== added directory 'hr_family/security'
=== added file 'hr_family/security/ir.model.access.csv'
--- hr_family/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_family/security/ir.model.access.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,2 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_hr_employee_children_user","hr.employee.children_user","model_hr_employee_children","base.group_hr_user",1,1,1,1
=== added directory 'hr_holidays_extension'
=== added file 'hr_holidays_extension/__init__.py'
--- hr_holidays_extension/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_holidays_extension/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_holidays
=== added file 'hr_holidays_extension/__openerp__.py'
--- hr_holidays_extension/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_holidays_extension/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,64 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'HR Holidays Extension',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Extended Capabilities for HR Holidays (Leaves)
+==============================================
+
+ * When calculating the number of leave days take into account the employee's schedule and
+ public holidays
+ * The 'Need Action' mechanism assumes the HR Manager approves leave requests
+ * Rename 'Leave Requests' menu item to 'My Leaves' (which is closer to its intent)
+ * Add a new menu item: All Leave Requests
+ * New way of entering leaves based on the number of days requested, rather
+ than by specifying a start and end date. You tell it how many days to
+ grant and it calculates the start and end dates based on the employee's schedule.
+ * Allow a manager to approve the leave requests of subordinates (manager must be
+ immediate superior of employee or manager of employee's department and have
+ leave approval rights)
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr_holidays',
+ 'hr_public_holidays',
+ 'hr_schedule',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/user_groups.xml',
+ 'security/ir.model.access.csv',
+ 'security/ir_rule.xml',
+ 'hr_holidays_workflow.xml',
+ 'hr_holidays_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_holidays_extension/hr_holidays.py'
--- hr_holidays_extension/hr_holidays.py 1970-01-01 00:00:00 +0000
+++ hr_holidays_extension/hr_holidays.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,313 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com)
+# and 2004-2010 Tiny SPRL (<http://tiny.be>).
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from datetime import datetime, timedelta
+from pytz import timezone, utc
+
+from openerp import tools
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as OE_DTFORMAT
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as OE_DFORMAT
+from openerp.tools.translate import _
+
+
+class hr_holidays_status(osv.Model):
+
+ _inherit = 'hr.holidays.status'
+
+ _columns = {
+ 'ex_rest_days': fields.boolean('Exclude Rest Days',
+ help="If enabled, the employee's day off is skipped in leave days calculation."),
+ 'ex_public_holidays': fields.boolean('Exclude Public Holidays',
+ help="If enabled, public holidays are skipped in leave days calculation."),
+ }
+
+
+class hr_holidays(osv.osv):
+
+ _name = 'hr.holidays'
+ _inherit = ['hr.holidays', 'ir.needaction_mixin']
+
+ _columns = {
+ 'real_days': fields.float('Total Days', digits=(16, 1)),
+ 'rest_days': fields.float('Rest Days', digits=(16, 1)),
+ 'public_holiday_days': fields.float('Public Holidays', digits=(16, 1)),
+ 'return_date': fields.char('Return Date', size=32),
+ }
+
+ def _employee_get(self, cr, uid, context=None):
+
+ if context == None:
+ context = {}
+
+ # If the user didn't enter from "My Leaves" don't pre-populate Employee
+ # field
+ import logging
+ _l = logging.getLogger(__name__)
+ _l.warning('context: %s', context)
+ if not context.get('search_default_my_leaves', False):
+ return False
+
+ ids = self.pool.get('hr.employee').search(
+ cr, uid, [('user_id', '=', uid)], context=context)
+ if ids:
+ return ids[0]
+ return False
+
+ def _days_get(self, cr, uid, context=None):
+
+ if context == None:
+ context = {}
+
+ date_from = context.get('default_date_from')
+ date_to = context.get('default_date_to')
+ if date_from and date_to:
+ delta = datetime.strptime(
+ date_to, OE_DTFORMAT) - datetime.strptime(date_from, OE_DTFORMAT)
+ return (delta.days and delta.days or 1)
+ return False
+
+ _defaults = {
+ 'employee_id': _employee_get,
+ 'number_of_days_temp': _days_get,
+ }
+
+ _order = 'date_from asc, type desc'
+
+ def _needaction_domain_get(self, cr, uid, context=None):
+
+ users_obj = self.pool.get('res.users')
+ domain = []
+
+ if users_obj.has_group(cr, uid, 'base.group_hr_manager'):
+ domain = [('state', 'in', ['draft', 'confirm'])]
+ return domain
+
+ elif users_obj.has_group(cr, uid, 'hr_holidays_extension.group_hr_leave'):
+ domain = [('state', 'in', ['confirm']), (
+ 'employee_id.user_id', '!=', uid)]
+ return domain
+
+ return False
+
+ def onchange_bynumber(self, cr, uid, ids, no_days, date_from, employee_id, holiday_status_id, context=None):
+ """
+ Update the dates based on the number of days requested.
+ """
+
+ ee_obj = self.pool.get('hr.employee')
+ holiday_obj = self.pool.get('hr.holidays.public')
+ sched_tpl_obj = self.pool.get('hr.schedule.template')
+ sched_detail_obj = self.pool.get('hr.schedule.detail')
+ result = {'value': {}}
+
+ if not no_days or not date_from or not employee_id:
+ return result
+
+ user = self.pool.get('res.users').browse(cr, uid, uid)
+ if user and user.tz:
+ local_tz = timezone(user.tz)
+ else:
+ local_tz = timezone('Africa/Addis_Ababa')
+
+ dt = datetime.strptime(date_from, OE_DTFORMAT)
+ employee = ee_obj.browse(cr, uid, employee_id, context=context)
+ if holiday_status_id:
+ hs_data = self.pool.get(
+ 'hr.holidays.status').read(cr, uid, holiday_status_id,
+ ['ex_rest_days', 'ex_public_holidays'],
+ context=context)
+ else:
+ hs_data = {}
+ ex_rd = hs_data.get('ex_rest_days', False)
+ ex_ph = hs_data.get('ex_public_holidays', False)
+
+ # Get rest day and the schedule start time on the date the leave begins
+ #
+ rest_days = []
+ times = tuple()
+ if ex_rd:
+ if employee.contract_id and employee.contract_id.schedule_template_id:
+ rest_days = sched_tpl_obj.get_rest_days(cr, uid,
+ employee.contract_id.schedule_template_id.id,
+ context=context)
+ times = sched_detail_obj.scheduled_begin_end_times(
+ cr, uid, employee.id,
+ employee.contract_id.id, dt,
+ context=context)
+ if len(times) > 0:
+ utcdtStart = times[0][0]
+ else:
+ dtStart = local_tz.localize(
+ datetime.strptime(dt.strftime(OE_DFORMAT) + ' 00:00:00', OE_DTFORMAT), is_dst=False)
+ utcdtStart = dtStart.astimezone(utc)
+
+ count_days = no_days
+ real_days = 1
+ ph_days = 0
+ r_days = 0
+ next_dt = dt
+ while count_days > 1:
+ public_holiday = holiday_obj.is_public_holiday(
+ cr, uid, next_dt.date(), context=context)
+ public_holiday = (public_holiday and ex_ph)
+ rest_day = (next_dt.weekday() in rest_days and ex_rd)
+ next_dt += timedelta(days=+1)
+ if public_holiday or rest_day:
+ if public_holiday:
+ ph_days += 1
+ elif rest_day:
+ r_days += 1
+ real_days += 1
+ continue
+ else:
+ count_days -= 1
+ real_days += 1
+ while (next_dt.weekday() in rest_days and ex_rd) or (holiday_obj.is_public_holiday(cr, uid, next_dt.date(), context=context) and ex_ph):
+ if holiday_obj.is_public_holiday(cr, uid, next_dt.date(), context=context):
+ ph_days += 1
+ elif next_dt.weekday() in rest_days:
+ r_days += 1
+ next_dt += timedelta(days=1)
+ real_days += 1
+
+ # Set end time based on schedule
+ #
+ times = sched_detail_obj.scheduled_begin_end_times(
+ cr, uid, employee.id,
+ employee.contract_id.id, next_dt,
+ context=context)
+ if len(times) > 0:
+ utcdtEnd = times[-1][1]
+ else:
+ dtEnd = local_tz.localize(
+ datetime.strptime(next_dt.strftime(OE_DFORMAT) + ' 23:59:59', OE_DTFORMAT), is_dst=False)
+ utcdtEnd = dtEnd.astimezone(utc)
+
+ result['value'].update({'department_id': employee.department_id.id,
+ 'date_from': utcdtStart.strftime(OE_DTFORMAT),
+ 'date_to': utcdtEnd.strftime(OE_DTFORMAT),
+ 'rest_days': r_days,
+ 'public_holiday_days': ph_days,
+ 'real_days': real_days})
+ return result
+
+ def onchange_enddate(self, cr, uid, ids, employee_id, date_to, holiday_status_id, context=None):
+
+ ee_obj = self.pool.get('hr.employee')
+ holiday_obj = self.pool.get('hr.holidays.public')
+ sched_tpl_obj = self.pool.get('hr.schedule.template')
+ res = {'value': {'return_date': False}}
+
+ if not employee_id or not date_to:
+ return res
+
+ if holiday_status_id:
+ hs_data = self.pool.get(
+ 'hr.holidays.status').read(cr, uid, holiday_status_id,
+ ['ex_rest_days', 'ex_public_holidays'],
+ context=context)
+ else:
+ hs_data = {}
+ ex_rd = hs_data.get('ex_rest_days', False)
+ ex_ph = hs_data.get('ex_public_holidays', False)
+
+ rest_days = []
+ if ex_rd:
+ ee = ee_obj.browse(cr, uid, employee_id, context=context)
+ if ee.contract_id and ee.contract_id.schedule_template_id:
+ rest_days = sched_tpl_obj.get_rest_days(cr, uid,
+ ee.contract_id.schedule_template_id.id,
+ context=context)
+
+ dt = datetime.strptime(date_to, OE_DTFORMAT)
+ return_date = dt + timedelta(days=+1)
+ while (return_date.weekday() in rest_days and ex_rd) or (holiday_obj.is_public_holiday(cr, uid, return_date.date(), context=context) and ex_ph):
+ return_date += timedelta(days=1)
+ res['value']['return_date'] = return_date.strftime('%B %d, %Y')
+ return res
+
+ def create(self, cr, uid, vals, context=None):
+
+ att_obj = self.pool.get('hr.attendance')
+ if vals.get('date_from', False) and vals.get('date_to', False) and (not vals.get('type', False) or vals.get('type', 'x') == 'remove') and vals.get('holiday_type', 'x') == 'employee':
+ att_ids = att_obj.search(
+ cr, uid, [('employee_id', '=', vals['employee_id']),
+ ('name', '>=', vals[
+ 'date_from']),
+ ('name', '<=', vals['date_to'])],
+ context=context)
+ if len(att_ids) > 0:
+ raise osv.except_osv(_('Warning'),
+ _('There is already one or more attendance records for the date you have chosen.'))
+
+ return super(hr_holidays, self).create(cr, uid, vals, context=context)
+
+ def holidays_first_validate(self, cr, uid, ids, context=None):
+
+ self._check_validate(cr, uid, ids, context=context)
+ return super(hr_holidays, self).holidays_first_validate(cr, uid, ids, context=context)
+
+ def holidays_validate(self, cr, uid, ids, context=None):
+
+ self._check_validate(cr, uid, ids, context=context)
+ return super(hr_holidays, self).holidays_validate(cr, uid, ids, context=context)
+
+ def _check_validate(self, cr, uid, ids, context=None):
+
+ users_obj = self.pool.get('res.users')
+
+ if not users_obj.has_group(cr, uid, 'base.group_hr_manager'):
+ for leave in self.browse(cr, uid, ids, context=context):
+ if leave.employee_id.user_id.id == uid:
+ raise osv.except_osv(
+ _('Warning!'), _('You cannot approve your own leave:\nHoliday Type: %s\nEmployee: %s') %
+ (leave.holiday_status_id.name, leave.employee_id.name))
+ return
+
+
+class hr_attendance(osv.Model):
+
+ _name = 'hr.attendance'
+ _inherit = 'hr.attendance'
+
+ def create(self, cr, uid, vals, context=None):
+
+ if vals.get('name', False):
+ lv_ids = self.pool.get(
+ 'hr.holidays').search(cr, uid, [('employee_id', '=', vals['employee_id']),
+ ('type', '=', 'remove'),
+ ('date_from', '<=', vals[
+ 'name']),
+ ('date_to', '>=', vals[
+ 'name']),
+ ('state', 'not in', ['cancel', 'refuse'])],
+ context=context)
+ if len(lv_ids) > 0:
+ ee_data = self.pool.get(
+ 'hr.employee').read(cr, uid, vals['employee_id'], ['name'],
+ context=context)
+ raise osv.except_osv(_('Warning'),
+ _('There is already one or more leaves recorded for the date you have chosen:\nEmployee: %s\nDate: %s' % (ee_data['name'], vals['name'])))
+
+ return super(hr_attendance, self).create(cr, uid, vals, context=context)
=== added file 'hr_holidays_extension/hr_holidays_view.xml'
--- hr_holidays_extension/hr_holidays_view.xml 1970-01-01 00:00:00 +0000
+++ hr_holidays_extension/hr_holidays_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <!-- Make Leave Requests to Approve viewable to managers with Leave Approval rights -->
+ <delete model="ir.ui.menu" id="hr_holidays.menu_request_approve_holidays"/>
+ <menuitem name="Leave Requests to Approve" parent="hr_holidays.menu_open_ask_holidays" id="hr_holidays.menu_request_approve_holidays" action="hr_holidays.request_approve_holidays" groups="base.group_hr_user,hr_holidays_extension.group_hr_leave"/>
+
+ <record id="view_leave_request_form" model="ir.ui.view">
+ <field name="name">Leave Request</field>
+ <field name="model">hr.holidays</field>
+ <field name="priority">1</field>
+ <field name="inherit_id" ref="hr_holidays.edit_holiday_new"/>
+ <field name="arch" type="xml">
+ <xpath expr="//form" position="replace">
+ <form string="Leave Request" version="7.0">
+ <header>
+ <button string="Approve" name="validate" states="confirm" type="workflow" groups="hr_holidays_extension.group_hr_leave" class="oe_highlight"/>
+ <button string="Validate" name="second_validate" states="validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
+ <button string="Refuse" name="refuse" states="confirm,validate" type="workflow" groups="hr_holidays_extension.group_hr_leave"/>
+ <button string="Refuse" name="refuse" states="validate1" type="workflow" groups="base.group_hr_user"/>
+ <button string="Reset to New" name="set_to_draft" states="refuse" type="object" groups="hr_holidays_extension.group_hr_leave"/>
+ <field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate" statusbar_colors='{"confirm":"blue","validate1":"blue","refuse":"red"}'/>
+ </header>
+ <sheet string="Leave Request">
+ <group>
+ <group>
+ <field name="holiday_type" invisible="1"/>
+ <field name="employee_id" attrs="{'required':[('holiday_type','=','employee')],'invisible':[('holiday_type','=','category')]}" on_change="onchange_bynumber(number_of_days_temp, date_from, employee_id, holiday_status_id)" groups="base.group_hr_user"/>
+ <field name="holiday_status_id" context="{'employee_id':employee_id}"/>
+ <field name="number_of_days_temp" on_change="onchange_bynumber(number_of_days_temp, date_from, employee_id, holiday_status_id)" string="Days Requested" required="1" readonly="0"/>
+ <label for="date_from" string="Duration" help="The default duration interval between the start date and the end date is 8 hours. Feel free to adapt it to your needs."/>
+ <div>
+ <group col="3">
+ <field name="date_from" nolabel="1" on_change="onchange_bynumber(number_of_days_temp, date_from, employee_id, holiday_status_id)" required="1" class="oe_inline"/><label string="-" class="oe_inline"/>
+ <field name="date_to" nolabel="1" on_change="onchange_enddate(employee_id, date_to, holiday_status_id)" required="1" class="oe_inline"/>
+ </group>
+ </div>
+ </group>
+ <group>
+ <field name="name" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
+ <field name="department_id" readonly="1" groups="base.group_hr_user"/>
+ </group>
+ </group>
+ <group>
+ <group>
+ <field name="return_date"/>
+ </group>
+ <group>
+ <field name="rest_days"/>
+ <field name="public_holiday_days"/>
+ <field name="real_days"/>
+ <field name="holiday_type" invisible="1"/>
+ </group>
+ </group>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </xpath>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="open_leave_request">
+ <field name="name">All Leave Requests</field>
+ <field name="res_model">hr.holidays</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form,calendar</field>
+ <field name="context">{'default_type': 'remove', 'search_default_group_date_from':1, 'search_default_group_type':1}</field>
+ <field name="domain">[('type','=','remove')]</field>
+ <field name="search_view_id" ref="hr_holidays.view_hr_holidays_filter"/>
+ <field name="help" type="html">
+ <p class="oe_view_nocontent_create">
+ Click to create a new leave request.
+ </p><p>
+ Once you have recorded your leave request, it will be sent
+ to a manager for validation. Be sure to set the right leave
+ type (recuperation, legal holidays, sickness) and the exact
+ number of open days related to your leave.
+ </p>
+ </field>
+ </record>
+ <menuitem parent="hr_holidays.menu_open_ask_holidays"
+ id="menu_leave_request"
+ action="open_leave_request"
+ groups="base.group_hr_user"
+ sequence="1"/>
+
+ <record model="ir.actions.act_window.view" id="action_open_leave_request_tree">
+ <field name="sequence" eval="2"/>
+ <field name="view_mode">tree</field>
+ <field name="view_id" ref="hr_holidays.view_holiday"/>
+ <field name="act_window_id" ref="open_leave_request"/>
+ </record>
+
+ <!-- Change name of leave request action (from a stock install) -->
+ <record model="ir.actions.act_window" id="hr_holidays.open_ask_holidays">
+ <field name="name">My Leaves</field>
+ </record>
+ <menuitem id="hr_holidays.menu_open_ask_holidays_new"
+ parent="hr_holidays.menu_open_ask_holidays"
+ action="hr_holidays.open_ask_holidays"
+ string="My Leaves"/>
+
+ <record id="view_holiday_status_form" model="ir.ui.view">
+ <field name="name">hr.holidays.status.form.extension</field>
+ <field name="model">hr.holidays.status</field>
+ <field name="inherit_id" ref="hr_holidays.edit_holiday_status_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//field[@name='active']" position="after">
+ <field name="ex_rest_days"/>
+ <field name="ex_public_holidays"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_holidays_extension/hr_holidays_workflow.xml'
--- hr_holidays_extension/hr_holidays_workflow.xml 1970-01-01 00:00:00 +0000
+++ hr_holidays_extension/hr_holidays_workflow.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,119 @@
+<?xml version="1.0" ?>
+<openerp>
+<data>
+
+ <!-- Workflow definition
+ 1. draft->submitted (no signal)
+ 2. submitted->accepted (validate signal) if not double_validation
+ 2. submitted -> first_accepted (validate signal) if double_validation
+ 2. submitted->refused (refuse signal)
+ 3. accepted->refused (refuse signal)
+ 4. first_accepted -> accepted (second_validate signal)
+ 4. first_accepted -> refused (refuse signal)
+
+ -->
+
+ <record model="workflow" id="wkf_holidays">
+ <field name="name">hr.wkf.holidays.extension</field>
+ <field name="osv">hr.holidays</field>
+ <field name="on_create">True</field>
+ </record>
+
+ <record model="workflow.activity" id="act_draft"> <!-- draft -->
+ <field name="wkf_id" ref="wkf_holidays" />
+ <field name="flow_start">True</field>
+ <field name="name">draft</field>
+ </record>
+
+ <record model="workflow.activity" id="act_confirm"> <!-- submitted -->
+ <field name="wkf_id" ref="wkf_holidays" />
+ <field name="name">confirm</field>
+ <field name="kind">function</field>
+ <field name="action">holidays_confirm()</field>
+ <field name="split_mode">OR</field>
+ </record>
+
+ <record model="workflow.activity" id="act_validate"> <!-- accepted -->
+ <field name="wkf_id" ref="wkf_holidays" />
+ <field name="name">validate</field>
+ <field name="kind">function</field>
+ <field name="action">holidays_validate()</field>
+ </record>
+
+ <record model="workflow.activity" id="act_validate1"> <!-- first_accepted -->
+ <field name="wkf_id" ref="wkf_holidays" />
+ <field name="name">first_validate</field>
+ <field name="kind">function</field>
+ <field name="action">holidays_first_validate()</field>
+ <field name="split_mode">OR</field>
+ </record>
+
+
+ <record model="workflow.activity" id="act_refuse"> <!-- refused -->
+ <field name="wkf_id" ref="wkf_holidays" />
+ <field name="name">refuse</field>
+ <field name="flow_stop">True</field>
+ <field name="kind">function</field>
+ <field name="action">holidays_refuse()</field>
+ </record>
+
+ <!--
+ workflow transition
+ -->
+
+ <record model="workflow.transition" id="holiday_draft2confirm"> <!-- 1. draft->submitted (no signal) -->
+ <field name="act_from" ref="act_draft" />
+ <field name="act_to" ref="act_confirm" />
+ </record>
+
+ <record model="workflow.transition" id="holiday_confirm2validate"> <!-- 2. submitted->accepted (validate signal) if not double_validation-->
+ <field name="act_from" ref="act_confirm" />
+ <field name="act_to" ref="act_validate" />
+ <field name="signal">validate</field>
+ <field name="condition">not double_validation</field>
+ <field name="group_id" ref="hr_holidays_extension.group_hr_leave"/>
+ </record>
+
+ <record model="workflow.transition" id="holiday_confirm2validate1"> <!-- 2. submitted -> first_accepted (validate signal) if double_validation-->
+ <field name="act_from" ref="act_confirm" />
+ <field name="act_to" ref="act_validate1" />
+ <field name="signal">validate</field>
+ <field name="condition">double_validation</field>
+ <field name="group_id" ref="hr_holidays_extension.group_hr_leave"/>
+ </record>
+
+
+ <record model="workflow.transition" id="holiday_confirm2refuse"> <!-- 2. submitted->refused (refuse signal) -->
+ <field name="act_from" ref="act_confirm" />
+ <field name="act_to" ref="act_refuse" />
+ <field name="signal">refuse</field>
+ <field name="condition">True</field>
+ <field name="group_id" ref="hr_holidays_extension.group_hr_leave"/>
+ </record>
+
+ <record model="workflow.transition" id="holiday_validate2refuse"> <!-- 3. accepted->refused (refuse signal) -->
+ <field name="act_from" ref="act_validate" />
+ <field name="act_to" ref="act_refuse" />
+ <field name="signal">refuse</field>
+ <field name="condition">True</field>
+ <field name="group_id" ref="hr_holidays_extension.group_hr_leave"/>
+ </record>
+
+ <record model="workflow.transition" id="holiday_validate1_validate"> <!-- 4. first_accepted -> accepted (second_validate signal) -->
+ <field name="act_from" ref="act_validate1" />
+ <field name="act_to" ref="act_validate" />
+ <field name="condition">True</field>
+ <field name="signal">second_validate</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record model="workflow.transition" id="holiday_validate1_refuse"> <!-- 4. first_accepted->refused (refuse signal) -->
+ <field name="act_from" ref="act_validate1" />
+ <field name="act_to" ref="act_refuse" />
+ <field name="signal">refuse</field>
+ <field name="condition">True</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+</data>
+</openerp>
=== added directory 'hr_holidays_extension/security'
=== added file 'hr_holidays_extension/security/ir.model.access.csv'
--- hr_holidays_extension/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_holidays_extension/security/ir.model.access.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_hr_holidays_approval,hr.holidays leave approval,model_hr_holidays,hr_holidays_extension.group_hr_leave,1,1,0,0
+access_hr_holydays_status_approval,hr.holidays.status leave approval,model_hr_holidays_status,hr_holidays_extension.group_hr_leave,1,1,0,0
+access_resource_calendar_leaves_approval_user,resource_calendar_leaves_approval_user,resource.model_resource_calendar_leaves,hr_holidays_extension.group_hr_leave,1,1,1,1
+access_crm_meeting_type_approval_user,crm.meeting.type.approval.user,base_calendar.model_crm_meeting_type,hr_holidays_extension.group_hr_leave,1,1,1,1
=== added file 'hr_holidays_extension/security/ir_rule.xml'
--- hr_holidays_extension/security/ir_rule.xml 1970-01-01 00:00:00 +0000
+++ hr_holidays_extension/security/ir_rule.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<openerp>
+ <data>
+
+ <record id="property_rule_holidays_supervisor" model="ir.rule">
+ <field name="name">Supervisor Holidays</field>
+ <field model="ir.model" name="model_id" ref="model_hr_holidays"/>
+ <field name="domain_force">['|', ('employee_id', 'child_of', [user.employee_ids[0].id]), ('employee_id.department_id.manager_id.user_id.id', '=', user.id)]</field>
+ <field name="groups" eval="[(4,ref('hr_holidays_extension.group_hr_leave'))]"/>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_holidays_extension/security/user_groups.xml'
--- hr_holidays_extension/security/user_groups.xml 1970-01-01 00:00:00 +0000
+++ hr_holidays_extension/security/user_groups.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data noupdate="0">
+
+ <!-- Leave Approval User Group -->
+
+ <record id="group_hr_leave" model="res.groups">
+ <field name="name">Leave Approval</field>
+ <field name="category_id" ref="base.module_category_human_resources"/>
+ <field name="users" eval="[(4, ref('base.user_root'))]"/>
+ <field name="comment">the user can approve leaves</field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_infraction'
=== added file 'hr_infraction/__init__.py'
--- hr_infraction/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_infraction/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,23 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_infraction
+from . import wizard
=== added file 'hr_infraction/__openerp__.py'
--- hr_infraction/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_infraction/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,53 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Employee Infraction Management',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Warning/Disciplinary Action Management
+========================================
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ 'hr_employee_state',
+ 'hr_security',
+ 'hr_transfer',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/ir.model.access.csv',
+ 'wizard/action.xml',
+ 'hr_infraction_data.xml',
+ 'hr_infraction_view.xml',
+ 'hr_infraction_workflow.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_infraction/hr_infraction.py'
--- hr_infraction/hr_infraction.py 1970-01-01 00:00:00 +0000
+++ hr_infraction/hr_infraction.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,194 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import time
+
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
+from openerp.tools.translate import _
+
+
+class hr_infraction_category(osv.Model):
+
+ _name = 'hr.infraction.category'
+ _description = 'Infraction Type'
+
+ _columns = {
+ 'name': fields.char('Name', required=True),
+ 'code': fields.char('Code', required=True),
+ }
+
+
+class hr_infraction(osv.Model):
+
+ _name = 'hr.infraction'
+ _description = 'Infraction'
+
+ _inherit = ['mail.thread', 'ir.needaction_mixin']
+
+ _columns = {
+ 'name': fields.char('Subject', size=256, required=True, readonly=True,
+ states={'draft': [('readonly', False)]}),
+ 'date': fields.date('Date', required=True, readonly=True,
+ states={'draft': [('readonly', False)]}),
+ 'employee_id': fields.many2one('hr.employee', 'Employee', required=True, readonly=True,
+ states={
+ 'draft': [('readonly', False)]}),
+ 'category_id': fields.many2one('hr.infraction.category', 'Category', required=True,
+ readonly=True, states={
+ 'draft': [('readonly', False)]}),
+ 'action_ids': fields.one2many('hr.infraction.action', 'infraction_id', 'Actions',
+ readonly=True),
+ 'memo': fields.text('Description', readonly=True, states={'draft': [('readonly', False)]}),
+ 'state': fields.selection([('draft', 'Draft'),
+ ('confirm', 'Confirmed'),
+ ('action', 'Actioned'),
+ ('noaction', 'No Action'),
+ ],
+ 'State', readonly=True),
+ }
+
+ _defaults = {
+ 'date': time.strftime(DEFAULT_SERVER_DATE_FORMAT),
+ 'state': 'draft',
+ }
+
+ _track = {
+ 'state': {
+ 'hr_infraction.mt_alert_infraction_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
+ 'hr_infraction.mt_alert_infraction_action': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'action',
+ 'hr_infraction.mt_alert_infraction_noaction': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'noaction',
+ },
+ }
+
+ def _needaction_domain_get(self, cr, uid, context=None):
+
+ users_obj = self.pool.get('res.users')
+
+ domain = []
+ if users_obj.has_group(cr, uid, 'base.group_hr_manager'):
+ domain = [('state', '=', 'confirm')]
+
+ if len(domain) == 0:
+ return False
+
+ return domain
+
+ def unlink(self, cr, uid, ids, context=None):
+
+ for infraction in self.browse(cr, uid, ids, context=context):
+ if infraction.state not in ['draft']:
+ raise osv.except_osv(_('Error'),
+ _('Infractions that have progressed beyond "Draft" state may not be removed.'))
+
+ return super(hr_infraction, self).unlink(cr, uid, ids, context=context)
+
+ def onchange_category(self, cr, uid, ids, category_id, context=None):
+
+ res = {'value': {'name': False}}
+ if category_id:
+ category = self.pool.get(
+ 'hr.infraction.category').browse(cr, uid, category_id,
+ context=context)
+ res['value']['name'] = category.name
+ return res
+
+ACTION_TYPE_SELECTION = [
+ ('warning_verbal', 'Verbal Warning'),
+ ('warning_letter', 'Written Warning'),
+ ('transfer', 'Transfer'),
+ ('suspension', 'Suspension'),
+ ('dismissal', 'Dismissal'),
+]
+
+
+class hr_infraction_action(osv.Model):
+
+ _name = 'hr.infraction.action'
+ _description = 'Action Based on Infraction'
+
+ _columns = {
+ 'infraction_id': fields.many2one('hr.infraction', 'Infraction', ondelete='cascade',
+ required=True, readonly=True),
+ 'type': fields.selection(ACTION_TYPE_SELECTION, 'Type', required=True),
+ 'memo': fields.text('Notes'),
+ 'employee_id': fields.related('infraction_id', 'employee_id', type='many2one', store=True,
+ obj='hr.employee', string='Employee', readonly=True),
+ 'warning_id': fields.many2one('hr.infraction.warning', 'Warning', readonly=True),
+ 'transfer_id': fields.many2one('hr.department.transfer', 'Transfer', readonly=True),
+ }
+
+ _rec_name = 'type'
+
+ def unlink(self, cr, uid, ids, context=None):
+
+ for action in self.browse(cr, uid, ids, context=context):
+ if action.infraction_id.state not in ['draft']:
+ raise osv.except_osv(_('Error'),
+ _('Actions belonging to Infractions not in "Draft" state may not be removed.'))
+
+ return super(hr_infraction_action, self).unlink(cr, uid, ids, context=context)
+
+
+class hr_warning(osv.Model):
+
+ _name = 'hr.infraction.warning'
+ _description = 'Employee Warning'
+
+ _columns = {
+ 'name': fields.char('Subject', size=256),
+ 'date': fields.date('Date Issued'),
+ 'type': fields.selection([('verbal', 'Verbal'), ('written', 'Written')], 'Type',
+ required=True),
+ 'action_id': fields.many2one('hr.infraction.action', 'Action', ondelete='cascade',
+ readonly=True),
+ 'infraction_id': fields.related('action_id', 'infraction_id', type='many2one',
+ obj='hr.infraction', string='Infraction', readonly=True),
+ 'employee_id': fields.related('infraction_id', 'employee_id', type='many2one',
+ obj='hr.employee', string='Employee', readonly=True),
+ }
+
+ _defaults = {
+ 'type': 'written',
+ 'date': time.strftime(DEFAULT_SERVER_DATE_FORMAT),
+ }
+
+ def unlink(self, cr, uid, ids, context=None):
+
+ for warning in self.browse(cr, uid, ids, context=context):
+ if warning.action_id and warning.action_id.infraction_id.state not in ['draft']:
+ raise osv.except_osv(_('Error'),
+ _('Warnings attached to Infractions not in "Draft" state may not be removed.'))
+
+ return super(hr_warning, self).unlink(cr, uid, ids, context=context)
+
+
+class hr_employee(osv.Model):
+
+ _name = 'hr.employee'
+ _inherit = 'hr.employee'
+
+ _columns = {
+ 'infraction_ids': fields.one2many('hr.infraction', 'employee_id', 'Infractions',
+ readonly=True),
+ 'infraction_action_ids': fields.one2many('hr.infraction.action', 'employee_id',
+ 'Disciplinary Actions', readonly=True),
+ }
=== added file 'hr_infraction/hr_infraction_data.xml'
--- hr_infraction/hr_infraction_data.xml 1970-01-01 00:00:00 +0000
+++ hr_infraction/hr_infraction_data.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Infraction Category -->
+ <record id="infraction_category_care" model="hr.infraction.category">
+ <field name="code">CARE</field>
+ <field name="name">Improper care for Company equipment/materials</field>
+ </record>
+ <record id="infraction_category_life" model="hr.infraction.category">
+ <field name="code">ENDLIFE</field>
+ <field name="name">Intentional endangerment of life or property</field>
+ </record>
+ <record id="infraction_category_safety" model="hr.infraction.category">
+ <field name="code">SAFETY</field>
+ <field name="name">Disregard of safety and accident prevention rules</field>
+ </record>
+ <record id="infraction_category_rule" model="hr.infraction.category">
+ <field name="code">RULE</field>
+ <field name="name">Disregard for Company rules and regulations</field>
+ </record>
+ <record id="infraction_category_instruct" model="hr.infraction.category">
+ <field name="code">INSTRUCT</field>
+ <field name="name">Not following instructions</field>
+ </record>
+ <record id="infraction_category_abuse" model="hr.infraction.category">
+ <field name="code">ABUSE</field>
+ <field name="name">Abusive behaviour towards others</field>
+ </record>
+ <record id="infraction_category_harm" model="hr.infraction.category">
+ <field name="code">HARM</field>
+ <field name="name">Bodily harm to others</field>
+ </record>
+ <record id="infraction_category_theft" model="hr.infraction.category">
+ <field name="code">THEFT</field>
+ <field name="name">Theft of materials, equipment or property</field>
+ </record>
+ <record id="infraction_category_damage" model="hr.infraction.category">
+ <field name="code">DAMAGE</field>
+ <field name="name">Damage to Company materials, property or equipment</field>
+ </record>
+ <record id="infraction_category_intox" model="hr.infraction.category">
+ <field name="code">INTOX</field>
+ <field name="name">Reporting to work in a state of intoxication</field>
+ </record>
+ <record id="infraction_category_medical" model="hr.infraction.category">
+ <field name="code">MEDEXAM</field>
+ <field name="name">Refusal of lawful medical examination</field>
+ </record>
+
+ <!-- Alert-related subtypes for messaging / Chatter -->
+
+ <record id="mt_alert_infraction_confirmed" model="mail.message.subtype">
+ <field name="name">Infraction - Confirmed</field>
+ <field name="res_model">hr.infraction</field>
+ <field name="description">Infraction submitted</field>
+ </record>
+
+ <record id="mt_alert_infraction_action" model="mail.message.subtype">
+ <field name="name">Infraction - Action Taken</field>
+ <field name="res_model">hr.infraction</field>
+ <field name="description">Action has been taken</field>
+ </record>
+
+ <record id="mt_alert_infraction_noaction" model="mail.message.subtype">
+ <field name="name">Infraction - No Action Taken</field>
+ <field name="res_model">hr.infraction</field>
+ <field name="description">No further action necessary</field>
+ </record>
+
+ <!-- Employment Termination Reason -->
+ <record id="term_dismissal" model="hr.employee.termination.reason">
+ <field name="name">Dismissal</field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_infraction/hr_infraction_view.xml'
--- hr_infraction/hr_infraction_view.xml 1970-01-01 00:00:00 +0000
+++ hr_infraction/hr_infraction_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Menu Parent -->
+ <menuitem id="menu_hr_infraction_root" name="Infractions" parent="hr.menu_hr_root" groups="base.group_hr_user" sequence="27"/>
+
+ <!-- Infraction Categories -->
+
+ <record id="hr_infraction_category_tree" model="ir.ui.view">
+ <field name="name">hr.infraction.category.tree</field>
+ <field name="model">hr.infraction.category</field>
+ <field name="arch" type="xml">
+ <tree string="Infraction Categories">
+ <field name="name"/>
+ <field name="code"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="hr_infraction_category_form" model="ir.ui.view">
+ <field name="name">hr.infraction.category.form</field>
+ <field name="model">hr.infraction.category</field>
+ <field name="arch" type="xml">
+ <form string="Infraction Category" version="7.0">
+ <sheet>
+ <field name="name"/>
+ <field name="code"/>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="open_hr_infraction_category" model="ir.actions.act_window">
+ <field name="name">Infraction Categories</field>
+ <field name="res_model">hr.infraction.category</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem action="open_hr_infraction_category"
+ id="menu_hr_infraction_category"
+ parent="hr.menu_hr_configuration"
+ sequence="5"/>
+
+ <!-- Infraction -->
+
+ <record id="hr_infraction_tree" model="ir.ui.view">
+ <field name="name">hr.infraction.tree</field>
+ <field name="model">hr.infraction</field>
+ <field name="arch" type="xml">
+ <tree string="Infractions">
+ <field name="name"/>
+ <field name="employee_id"/>
+ <field name="date"/>
+ <field name="category_id"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="hr_infraction_form" model="ir.ui.view">
+ <field name="name">hr.infraction.form</field>
+ <field name="model">hr.infraction</field>
+ <field name="arch" type="xml">
+ <form string="Infraction" version="7.0">
+ <header>
+ <button name="signal_confirm" type="workflow" states="draft" groups="base.group_hr_user" string="Confirm" class="oe_highlight"/>
+ <button name="%(action_action_wizard)s" type="action" states="confirm,action" groups="base.group_hr_manager" string="Take Action" class="oe_highlight"/>
+ <button name="signal_noaction" type="workflow" states="confirm" groups="base.group_hr_manager" string="No Further Action" class="oe_highlight"/>
+ <field name="state" widget="statusbar"/>
+ </header>
+ <group>
+ <group>
+ <field name="category_id" widget="selection" on_change="onchange_category(category_id)"/>
+ <field name="employee_id"/>
+ <field name="date"/>
+ </group>
+ <group>
+ <field name="name"/>
+ </group>
+ <group string="Description">
+ <field name="memo" nolabel="1" placeholder="Describe the incident here..."/>
+ </group>
+ <group string="Action(s)">
+ <field name="action_ids" nolabel="1">
+ <tree string="Actions Taken">
+ <field name="type"/>
+ <field name="memo"/>
+ </tree>
+ </field>
+ </group>
+ </group>
+ <newline/>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </field>
+ </record>
+
+ <record id="open_hr_infraction" model="ir.actions.act_window">
+ <field name="name">Infractions</field>
+ <field name="res_model">hr.infraction</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem id="menu_hr_infraction"
+ action="open_hr_infraction"
+ parent="menu_hr_infraction_root"
+ sequence="5"/>
+
+ <!-- Warnings -->
+
+ <record id="hr_infraction_warning_tree" model="ir.ui.view">
+ <field name="name">hr.infraction.warning.tree</field>
+ <field name="model">hr.infraction.warning</field>
+ <field name="arch" type="xml">
+ <tree string="Warnings">
+ <field name="employee_id"/>
+ <field name="name"/>
+ <field name="date"/>
+ <field name="type"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="hr_infraction_warning_form" model="ir.ui.view">
+ <field name="name">hr.infraction.warning.form</field>
+ <field name="model">hr.infraction.warning</field>
+ <field name="arch" type="xml">
+ <form string="Warning" version="7.0">
+ <sheet>
+ <group>
+ <group>
+ <field name="name"/>
+ <field name="date"/>
+ <field name="employee_id"/>
+ </group>
+ <group>
+ <field name="type"/>
+ <field name="action_id" invisible="1"/>
+ <field name="infraction_id"/>
+ </group>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="open_hr_infraction_warning" model="ir.actions.act_window">
+ <field name="name">Warnings</field>
+ <field name="res_model">hr.infraction.warning</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem id="menu_hr_infraction_warning"
+ action="open_hr_infraction_warning"
+ parent="menu_hr_infraction_root"
+ sequence="10"/>
+
+ <!-- Infraction Actions -->
+
+ <record id="hr_infraction_action_tree" model="ir.ui.view">
+ <field name="name">hr.infraction.action.tree</field>
+ <field name="model">hr.infraction.action</field>
+ <field name="arch" type="xml">
+ <tree string="Infraction Actions">
+ <field name="employee_id"/>
+ <field name="infraction_id"/>
+ <field name="type"/>
+ <field name="memo"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="hr_infraction_action_form" model="ir.ui.view">
+ <field name="name">hr.infraction.action.form</field>
+ <field name="model">hr.infraction.action</field>
+ <field name="arch" type="xml">
+ <form string="Infraction Action" version="7.0">
+ <sheet>
+ <group>
+ <group>
+ <field name="employee_id"/>
+ <field name="infraction_id"/>
+ <field name="warning_id" attrs="{'invisible': [('type','not in',['warning_verbal','warning_letter'])]}"/>
+ <field name="transfer_id" attrs="{'invisible': [('type','!=','transfer')]}"/>
+ </group>
+ <group>
+ <field name="type"/>
+ </group>
+ </group>
+ <separator string="Notes"/>
+ <field name="memo" nolabel="1"/>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="open_hr_infraction_action" model="ir.actions.act_window">
+ <field name="name">Actions</field>
+ <field name="res_model">hr.infraction.action</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem id="menu_hr_infraction_action"
+ action="open_hr_infraction_action"
+ parent="menu_hr_infraction_root"
+ sequence="15"/>
+
+ <!-- Employee Form -->
+
+ <record id="view_employee_form" model="ir.ui.view">
+ <field name="name">hr.employee.form.inherit.infraction</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr.view_employee_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//page[@string='HR Settings']" position="after">
+ <page string="Disciplinary Information">
+ <group>
+ <group string="Infractions">
+ <field name="infraction_ids" nolabel="1">
+ <tree string="Infractions">
+ <field name="date"/>
+ <field name="name"/>
+ <field name="state" invisible="1"/>
+ </tree>
+ </field>
+ </group>
+ <group string="Disciplinary Actions">
+ <field name="infraction_action_ids" nolabel="1">
+ <tree string="Actions">
+ <field name="type"/>
+ <field name="infraction_id"/>
+ </tree>
+ </field>
+ </group>
+ </group>
+ </page>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_infraction/hr_infraction_workflow.xml'
--- hr_infraction/hr_infraction_workflow.xml 1970-01-01 00:00:00 +0000
+++ hr_infraction/hr_infraction_workflow.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <!-- Workflow Definition -->
+ <record id="wkf_employee_infraction" model="workflow">
+ <field name="name">hr.infraction.basic</field>
+ <field name="osv">hr.infraction</field>
+ <field name="on_create">True</field>
+ </record>
+
+ <!-- Workflow Activities (Stages) -->
+
+ <record id="act_draft" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee_infraction"/>
+ <field name="name">draft</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'draft'})</field>
+ <field name="flow_start">True</field>
+ </record>
+
+ <record id="act_confirm" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee_infraction"/>
+ <field name="name">confirm</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'confirm'})</field>
+ </record>
+
+ <record id="act_action" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee_infraction"/>
+ <field name="name">action</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'action'})</field>
+ <field name="flow_stop">True</field>
+ </record>
+
+ <record id="act_noaction" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_employee_infraction"/>
+ <field name="name">noaction</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'noaction'})</field>
+ <field name="flow_stop">True</field>
+ </record>
+
+ <!-- Workflow Transitions -->
+
+ <record id="draft2confirm" model="workflow.transition">
+ <field name="act_from" ref="act_draft"/>
+ <field name="act_to" ref="act_confirm"/>
+ <field name="signal">signal_confirm</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="confirm2action" model="workflow.transition">
+ <field name="act_from" ref="act_confirm"/>
+ <field name="act_to" ref="act_action"/>
+ <field name="signal">signal_action</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="confirm2noaction" model="workflow.transition">
+ <field name="act_from" ref="act_confirm"/>
+ <field name="act_to" ref="act_noaction"/>
+ <field name="signal">signal_noaction</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_infraction/security'
=== added file 'hr_infraction/security/ir.model.access.csv'
--- hr_infraction/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_infraction/security/ir.model.access.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,12 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_hr_infraction_user,access_hr_infraction,model_hr_infraction,base.group_hr_user,1,1,1,1
+access_hr_infraction_action_user,access_hr_infraction_action,model_hr_infraction_action,base.group_hr_user,1,0,0,0
+access_hr_infraction_action_manager,access_hr_infraction_action,model_hr_infraction_action,base.group_hr_manager,1,1,1,1
+access_hr_infraction_warning_user,access_hr_infraction_warning,model_hr_infraction_warning,base.group_hr_user,1,0,0,0
+access_hr_infraction_warning_manager,access_hr_infraction_warning,model_hr_infraction_warning,base.group_hr_manager,1,1,1,1
+access_hr_infraction_category_user,access_hr_infraction_category,model_hr_infraction_category,base.group_hr_user,1,0,0,0
+access_hr_infraction_category_manager,access_hr_infraction_category,model_hr_infraction_category,base.group_hr_manager,1,1,1,1
+access_hr_infraction_pm,access_hr_infraction,hr_infraction.model_hr_infraction,hr_security.group_payroll_manager,1,0,0,0
+access_hr_infraction_action_pm,access_hr_infraction_action,hr_infraction.model_hr_infraction_action,hr_security.group_payroll_manager,1,0,0,0
+access_hr_infraction_warning_pm,access_hr_infraction_warning,hr_infraction.model_hr_infraction_warning,hr_security.group_payroll_manager,1,0,0,0
+access_hr_infraction_category_pm,access_hr_infraction_category,hr_infraction.model_hr_infraction_category,hr_security.group_payroll_manager,1,0,0,0
=== added directory 'hr_infraction/wizard'
=== added file 'hr_infraction/wizard/__init__.py'
--- hr_infraction/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_infraction/wizard/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import action
=== added file 'hr_infraction/wizard/action.py'
--- hr_infraction/wizard/action.py 1970-01-01 00:00:00 +0000
+++ hr_infraction/wizard/action.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,167 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from openerp import netsvc
+from openerp.osv import fields, osv
+
+from openerp.addons.hr_infraction.hr_infraction import ACTION_TYPE_SELECTION
+
+
+class action_wizard(osv.TransientModel):
+
+ _name = 'hr.infraction.action.wizard'
+ _description = 'Choice of Actions for Infraction'
+
+ _columns = {
+ 'action_type': fields.selection(ACTION_TYPE_SELECTION, 'Action', required=True),
+ 'memo': fields.text('Notes'),
+ 'new_job_id': fields.many2one('hr.job', 'New Job'),
+ 'xfer_effective_date': fields.date('Effective Date'),
+ 'effective_date': fields.date('Effective Date'),
+ }
+
+ def create_action(self, cr, uid, ids, context=None):
+
+ if context == None:
+ context = {}
+ infraction_id = context.get('active_id', False)
+ if not infraction_id:
+ return False
+ data = self.read(cr, uid, ids[0], context=context)
+
+ vals = {
+ 'infraction_id': infraction_id,
+ 'type': data['action_type'],
+ 'memo': data.get('memo', False),
+ }
+ action_id = self.pool.get('hr.infraction.action').create(
+ cr, uid, vals, context=context)
+
+ # Update state of infraction, if not already done so
+ #
+ infraction_obj = self.pool.get('hr.infraction')
+ infraction_data = infraction_obj.read(
+ cr, uid, infraction_id, ['employee_id', 'state'],
+ context=context)
+ if infraction_data['state'] == 'confirm':
+ netsvc.LocalService(
+ 'workflow').trg_validate(uid, 'hr.infraction', infraction_id,
+ 'signal_action', cr)
+
+ infraa_obj = self.pool.get('hr.infraction.action')
+ imd_obj = self.pool.get('ir.model.data')
+ iaa_obj = self.pool.get('ir.actions.act_window')
+
+ # If the action is a warning create the appropriate record, reference it from the action,
+ # and pull it up in the view (in case the user needs to make any changes.
+ #
+ if data['action_type'] in ['warning_verbal', 'warning_letter']:
+ vals = {
+ 'name': (data['action_type'] == 'warning_verbal' and 'Verbal' or 'Written') + ' Warning',
+ 'type': data['action_type'] == 'warning_verbal' and 'verbal' or 'written',
+ 'action_id': action_id,
+ }
+ warning_id = self.pool.get('hr.infraction.warning').create(
+ cr, uid, vals, context=context)
+ infraa_obj.write(cr, uid, action_id, {
+ 'warning_id': warning_id}, context=context)
+
+ res_model, res_id = imd_obj.get_object_reference(
+ cr, uid, 'hr_infraction',
+ 'open_hr_infraction_warning')
+ dict_act_window = iaa_obj.read(cr, uid, res_id, [])
+ dict_act_window['view_mode'] = 'form,tree'
+ dict_act_window['domain'] = [('id', '=', warning_id)]
+ return dict_act_window
+
+ # If the action is a departmental transfer create the appropriate record, reference it from
+ # the action, and pull it up in the view (in case the user needs to make any changes.
+ #
+ elif data['action_type'] == 'transfer':
+ xfer_obj = self.pool.get('hr.department.transfer')
+ ee = self.pool.get(
+ 'hr.employee').browse(cr, uid, infraction_data['employee_id'][0],
+ context=context)
+ _tmp = xfer_obj.onchange_employee(
+ cr, uid, None, ee.id, context=context)
+ vals = {
+ 'employee_id': ee.id,
+ 'src_id': _tmp['value']['src_id'],
+ 'dst_id': data['new_job_id'][0],
+ 'src_contract_id': _tmp['value']['src_contract_id'],
+ 'date': data['xfer_effective_date'],
+ }
+ xfer_id = xfer_obj.create(cr, uid, vals, context=context)
+ infraa_obj.write(cr, uid, action_id, {
+ 'transfer_id': xfer_id}, context=context)
+
+ res_model, res_id = imd_obj.get_object_reference(
+ cr, uid, 'hr_transfer',
+ 'open_hr_department_transfer')
+ dict_act_window = iaa_obj.read(cr, uid, res_id, [])
+ dict_act_window['view_mode'] = 'form,tree'
+ dict_act_window['domain'] = [('id', '=', xfer_id)]
+ return dict_act_window
+
+ # The action is dismissal. Begin the termination process.
+ #
+ elif data['action_type'] == 'dismissal':
+ term_obj = self.pool.get('hr.employee.termination')
+ wkf = netsvc.LocalService('workflow')
+ ee = self.pool.get(
+ 'hr.employee').browse(cr, uid, infraction_data['employee_id'][0],
+ context=context)
+
+ # We must create the employment termination object before we set
+ # the contract state to 'done'.
+ res_model, res_id = imd_obj.get_object_reference(
+ cr, uid, 'hr_infraction', 'term_dismissal')
+ vals = {
+ 'employee_id': ee.id,
+ 'name': data['effective_date'],
+ 'reason_id': res_id,
+ }
+ term_id = term_obj.create(cr, uid, vals, context=context)
+ infraa_obj.write(cr, uid, action_id, {
+ 'termination_id': term_id}, context=context)
+
+ # End any open contracts
+ for contract in ee.contract_ids:
+ if contract.state not in ['done']:
+ wkf.trg_validate(
+ uid, 'hr.contract', contract.id, 'signal_pending_done', cr)
+
+ # Set employee state to pending deactivation
+ wkf.trg_validate(
+ uid, 'hr.employee', ee.id, 'signal_pending_inactive', cr)
+
+ # Trigger confirmation of termination record
+ wkf.trg_validate(
+ uid, 'hr.employee.termination', term_id, 'signal_confirmed', cr)
+
+ res_model, res_id = imd_obj.get_object_reference(
+ cr, uid, 'hr_employee_state',
+ 'open_hr_employee_termination')
+ dict_act_window = iaa_obj.read(cr, uid, res_id, [])
+ dict_act_window['domain'] = [('id', '=', term_id)]
+ return dict_act_window
+
+ return True
=== added file 'hr_infraction/wizard/action.xml'
--- hr_infraction/wizard/action.xml 1970-01-01 00:00:00 +0000
+++ hr_infraction/wizard/action.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <record id="hr_infraction_action_wizard_form" model="ir.ui.view">
+ <field name="name">hr.infraction.action.wizard.form</field>
+ <field name="model">hr.infraction.action.wizard</field>
+ <field name="arch" type="xml">
+ <form string="Choose Action" version="7.0">
+ <header>
+ <button name="create_action" type="object" string="Take Action" class="oe_highlight"/>
+ </header>
+ <div>
+ <h2>Please choose the appropriate action from the list below</h2>
+ </div>
+ <group>
+ <group>
+ <field name="action_type"/>
+ </group>
+ <group></group>
+ </group>
+ <group string="Departmental Transfer" attrs="{'invisible': [('action_type','!=','transfer')]}">
+ <group>
+ <field name="new_job_id" widget="selection"/>
+ <field name="xfer_effective_date"/>
+ </group>
+ <group></group>
+ </group>
+ <group string="Dismissal" attrs="{'invisible': [('action_type','!=','dismissal')]}">
+ <group>
+ <field name="effective_date"/>
+ </group>
+ <group></group>
+ </group>
+ <separator string="Notes"/>
+ <field name="memo" nolabel="1" colspan="4"/>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_action_wizard" model="ir.actions.act_window">
+ <field name="name">Infraction Action</field>
+ <field name="res_model">hr.infraction.action.wizard</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="hr_infraction_action_wizard_form"/>
+ <field name="target">new</field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_job_categories'
=== added file 'hr_job_categories/__init__.py'
--- hr_job_categories/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_job_categories/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr
=== added file 'hr_job_categories/__openerp__.py'
--- hr_job_categories/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_job_categories/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,51 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Job Categories',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Attach Categories (Tags) to Employees Based on Job Position
+===========================================================
+
+This module is useful for tagging employees based on their job positions. For example,
+all Supervisors could be attached to the Supervisors category. Define which categries
+a job belongs to in the configuration for the job. When an employee is assigned a particular
+job the categories attached to that job will be attached to the employee record as well.
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'hr_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_job_categories/hr.py'
--- hr_job_categories/hr.py 1970-01-01 00:00:00 +0000
+++ hr_job_categories/hr.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,118 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+
+import logging
+_l = logging.getLogger(__name__)
+
+
+class hr_job(osv.Model):
+
+ _inherit = 'hr.job'
+
+ _columns = {
+ 'category_ids': fields.many2many('hr.employee.category', 'job_category_rel', 'job_id',
+ 'category_id', 'Associated Tags'),
+ }
+
+
+class hr_contract(osv.Model):
+
+ _name = 'hr.contract'
+ _inherit = 'hr.contract'
+
+ def _remove_tags(self, cr, uid, employee_id, job_id, context=None):
+
+ if not employee_id or not job_id:
+ return
+
+ ee_obj = self.pool.get('hr.employee')
+ eedata = ee_obj.read(
+ cr, uid, employee_id, ['category_ids'], context=context)
+ job = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
+ _l.warning('remv: eedata: %s', eedata)
+ for tag in job.category_ids:
+ if tag.id in eedata['category_ids']:
+ ee_obj.write(cr, uid, employee_id, {
+ 'category_ids': [(3, tag.id)]}, context=context)
+ return
+
+ def _tag_employees(self, cr, uid, employee_id, job_id, context=None):
+
+ if not employee_id or not job_id:
+ return
+
+ ee_obj = self.pool.get('hr.employee')
+ eedata = ee_obj.read(
+ cr, uid, employee_id, ['category_ids'], context=context)
+ job = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
+ _l.warning('tag: eedata: %s', eedata)
+ for tag in job.category_ids:
+ _l.warning('tag: name,id: %s,%s', tag.name, tag.id)
+ if tag.id not in eedata['category_ids']:
+ _l.warning('tag: write()')
+ ee_obj.write(cr, uid, employee_id, {
+ 'category_ids': [(4, tag.id)]}, context=context)
+ return
+
+ def create(self, cr, uid, vals, context=None):
+
+ res = super(hr_contract, self).create(cr, uid, vals, context=context)
+
+ self._tag_employees(
+ cr, uid, vals.get('employee_id', False), vals.get('job_id', False),
+ context)
+
+ return res
+
+ def write(self, cr, uid, ids, vals, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ prev_data = self.read(cr, uid, ids, ['job_id'], context=context)
+ _l.warning('prev_data: %s', prev_data)
+
+ res = super(hr_contract, self).write(
+ cr, uid, ids, vals, context=context)
+
+ # Go through each record and delete tags associated with the previous job, then
+ # add the tags of the new job.
+ #
+ for contract in self.browse(cr, uid, ids, context=context):
+ for data in prev_data:
+ if data['id'] == contract.id:
+ if not vals.get('job_id', False) or data['job_id'][0] != vals['job_id']:
+ prev_job_id = data.get(
+ 'job_id', False) and data['job_id'][0] or False
+ _l.warning(
+ 'prev Job, new job: %s, %s', prev_job_id, vals.get('job_id', False))
+ self._remove_tags(
+ cr, uid, contract.employee_id.id, prev_job_id,
+ context=context)
+ if vals.get('job_id', False):
+ self._tag_employees(
+ cr, uid, contract.employee_id.id, contract.job_id.id,
+ context=context)
+
+ return res
=== added file 'hr_job_categories/hr_view.xml'
--- hr_job_categories/hr_view.xml 1970-01-01 00:00:00 +0000
+++ hr_job_categories/hr_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="view_job_form" model="ir.ui.view">
+ <field name="name">hr.job.categories</field>
+ <field name="model">hr.job</field>
+ <field name="inherit_id" ref="hr.view_hr_job_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@class='oe_title']" position="inside">
+ <label for="category_ids" class="oe_edit_only" groups="base.group_hr_manager"/>
+ <field name="category_ids" widget="many2many_tags" placeholder="e.g. Part Time" groups="base.group_hr_manager"/>
+ </xpath>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_job_hierarchy'
=== added file 'hr_job_hierarchy/__init__.py'
--- hr_job_hierarchy/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_job_hierarchy/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr
=== added file 'hr_job_hierarchy/__openerp__.py'
--- hr_job_hierarchy/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_job_hierarchy/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,53 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Job Hierarchy',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Define Hierarchy of Jobs
+========================
+
+ 1. Define parent/child relationship for jobs, which is useful for
+ determining supervisor/subordinate relationships.
+ 2. Provide a knob in Job configuration to specify if the person with that
+ job should be considered the Department manager.
+ 3. Automatically set the manager of a department based on this knob in job configuration.
+ 4. Automatically set an employee's manager from the department's manager.
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'hr_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_job_hierarchy/hr.py'
--- hr_job_hierarchy/hr.py 1970-01-01 00:00:00 +0000
+++ hr_job_hierarchy/hr.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,209 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+
+import logging
+_l = logging.getLogger(__name__)
+
+
+class hr_job(osv.Model):
+
+ _name = 'hr.job'
+ _inherit = 'hr.job'
+
+ def _get_all_child_ids(self, cr, uid, ids, field_name, arg, context=None):
+ result = dict.fromkeys(ids)
+ for i in ids:
+ result[i] = self.search(
+ cr, uid, [('parent_id', 'child_of', i)], context=context)
+
+ return result
+
+ _columns = {
+ 'department_manager': fields.boolean('Department Manager'),
+ 'parent_id': fields.many2one('hr.job', 'Immediate Superior', ondelete='cascade'),
+ 'child_ids': fields.one2many('hr.job', 'parent_id', 'Immediate Subordinates'),
+ 'all_child_ids': fields.function(_get_all_child_ids, type='many2many', relation='hr.job'),
+ 'parent_left': fields.integer('Left Parent', select=1),
+ 'parent_right': fields.integer('Right Parent', select=1),
+ }
+
+ _parent_name = "parent_id"
+ _parent_store = True
+ _parent_order = 'name'
+ _order = 'parent_left'
+
+ def _check_recursion(self, cr, uid, ids, context=None):
+
+ # Copied from product.category
+ # This is a brute-force approach to the problem, but should be good enough.
+ #
+ level = 100
+ while len(ids):
+ cr.execute(
+ 'select distinct parent_id from hr_job where id IN %s', (tuple(ids),))
+ ids = filter(None, map(lambda x: x[0], cr.fetchall()))
+ if not level:
+ return False
+ level -= 1
+ return True
+
+ _constraints = [
+ (_check_recursion,
+ _('Error!\nYou cannot create recursive jobs.'), ['parent_id']),
+ ]
+
+ def write(self, cr, uid, ids, vals, context=None):
+
+ res = super(hr_job, self).write(cr, uid, ids, vals, context=None)
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ dept_obj = self.pool.get('hr.department')
+ if vals.get('department_manager', False):
+ for di in ids:
+ job = self.browse(cr, uid, di, context=context)
+ dept_id = False
+ if vals.get('department_id', False):
+ dept_id = vals['department_id']
+ else:
+ dept_id = job.department_id.id
+ employee_id = False
+ for ee in job.employee_ids:
+ employee_id = ee.id
+ if employee_id:
+ dept_obj.write(
+ cr, uid, dept_id, {'manager_id': employee_id}, context=context)
+ elif vals.get('department_id', False):
+ for di in ids:
+ job = self.browse(cr, uid, di, context=context)
+ if job.department_manager:
+ employee_id = False
+ for ee in job.employee_ids:
+ employee_id = ee.id
+ dept_obj.write(cr, uid, vals['department_id'], {
+ 'manager_id': employee_id}, context=context)
+ elif vals.get('parent_id', False):
+ ee_obj = self.pool.get('hr.employee')
+ parent_job = self.browse(
+ cr, uid, vals['parent_id'], context=context)
+ parent_id = False
+ for ee in parent_job.employee_ids:
+ parent_id = ee.id
+ for job in self.browse(cr, uid, ids, context=context):
+ for ee in job.employee_ids:
+ ee_obj.write(
+ cr, uid, ee.id, {'parent_id': parent_id}, context=context)
+
+ return res
+
+
+class hr_contract(osv.Model):
+
+ _name = 'hr.contract'
+ _inherit = 'hr.contract'
+
+ def create(self, cr, uid, vals, context=None):
+
+ res = super(hr_contract, self).create(cr, uid, vals, context=context)
+
+ if not vals.get('job_id', False):
+ return res
+
+ ee_obj = self.pool.get('hr.employee')
+ job = self.pool.get('hr.job').browse(
+ cr, uid, vals['job_id'], context=context)
+
+ # Write the employee's manager
+ if job and job.parent_id:
+ parent_id = False
+ for ee in job.parent_id.employee_ids:
+ parent_id = ee.id
+ if parent_id and vals.get('employee_id'):
+ ee_obj.write(
+ cr, uid, vals['employee_id'], {'parent_id': parent_id},
+ context=context)
+
+ # Write any employees with jobs that are immediate descendants of this
+ # job
+ if job:
+ job_child_ids = []
+ [job_child_ids.append(child.id) for child in job.child_ids]
+ if len(job_child_ids) > 0:
+ ee_ids = ee_obj.search(
+ cr, uid, [('job_id', 'in', job_child_ids)])
+ if len(ee_ids) > 0:
+ parent_id = False
+ for ee in job.employee_ids:
+ parent_id = ee.id
+ if parent_id:
+ ee_obj.write(
+ cr, uid, ee_ids, {'parent_id': parent_id}, context=context)
+
+ return res
+
+ def write(self, cr, uid, ids, vals, context=None):
+
+ res = super(hr_contract, self).write(
+ cr, uid, ids, vals, context=context)
+
+ if not vals.get('job_id', False):
+ return res
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ ee_obj = self.pool.get('hr.employee')
+
+ job = self.pool.get('hr.job').browse(
+ cr, uid, vals['job_id'], context=context)
+
+ # Write the employee's manager
+ if job and job.parent_id:
+ parent_id = False
+ for ee in job.parent_id.employee_ids:
+ parent_id = ee.id
+ if parent_id:
+ for contract in self.browse(cr, uid, ids, context=context):
+ ee_obj.write(cr, uid, contract.employee_id.id, {
+ 'parent_id': parent_id}, context=context)
+
+ # Write any employees with jobs that are immediate descendants of this
+ # job
+ if job:
+ job_child_ids = []
+ [job_child_ids.append(child.id) for child in job.child_ids]
+ if len(job_child_ids) > 0:
+ ee_ids = ee_obj.search(
+ cr, uid, [('job_id', 'in', job_child_ids),
+ ('active', '=', True)])
+ if len(ee_ids) > 0:
+ parent_id = False
+ for ee in job.employee_ids:
+ parent_id = ee.id
+ if parent_id:
+ ee_obj.write(
+ cr, uid, ee_ids, {'parent_id': parent_id}, context=context)
+
+ return res
=== added file 'hr_job_hierarchy/hr_view.xml'
--- hr_job_hierarchy/hr_view.xml 1970-01-01 00:00:00 +0000
+++ hr_job_hierarchy/hr_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <!-- Job Form -->
+
+ <record id="job_form_view" model="ir.ui.view">
+ <field name="name">hr.job.form.hierarchy</field>
+ <field name="model">hr.job</field>
+ <field name="inherit_id" ref="hr.view_hr_job_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//field[@name='department_id']" position="after">
+ <field name="parent_id"/>
+ <field name="department_manager"/>
+ </xpath>
+ <xpath expr="//sheet" position="inside">
+ <group string="Immediate Subordinates">
+ <field name="child_ids" nolabel="1"/>
+ </group>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_labour_recruitment'
=== added file 'hr_labour_recruitment/__init__.py'
--- hr_labour_recruitment/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_labour_recruitment/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_recruitment
=== added file 'hr_labour_recruitment/__openerp__.py'
--- hr_labour_recruitment/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_labour_recruitment/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,52 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'New Employee Recruitment and Personnel Requests',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Recruitment of New Employees
+============================
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr_contract',
+ 'hr_contract_state',
+ 'hr_recruitment',
+ 'hr_security',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/ir.model.access.csv',
+ 'hr_recruitment_data.xml',
+ 'hr_recruitment_view.xml',
+ 'hr_recruitment_workflow.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_labour_recruitment/hr_recruitment.py'
--- hr_labour_recruitment/hr_recruitment.py 1970-01-01 00:00:00 +0000
+++ hr_labour_recruitment/hr_recruitment.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,291 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as OE_DATEFORMAT
+from openerp.tools.translate import _
+
+
+class hr_job(osv.Model):
+
+ _name = 'hr.job'
+ _inherit = 'hr.job'
+
+ _columns = {
+ 'max_employees': fields.integer('Maximum Number of Employees'),
+ 'max_employees_fuzz': fields.integer('Expected Turnover',
+ help="Recruitment module will allow you to \
+ create this number of additional applicants and \
+ contracts above the maximum value. Use this \
+ number as a buffer to have additional \
+ employees on hand to cover employee turnover."),
+ }
+
+ # Do not write negative values for no. of recruitment
+ def write(self, cr, uid, ids, vals, context=None):
+
+ value = vals.get('no_of_recruitment', False)
+ if value and value < 0:
+ vals['no_of_recruitment'] = 0
+
+ return super(hr_job, self).write(cr, uid, ids, vals, context=context)
+
+
+class hr_applicant(osv.Model):
+
+ _name = 'hr.applicant'
+ _inherit = 'hr.applicant'
+
+ def create(self, cr, uid, vals, context=None):
+
+ if vals.get('job_id', False):
+ data = self.pool.get('hr.job').read(cr, uid, vals['job_id'],
+ ['max_employees', 'no_of_employee', 'state',
+ 'max_employees_fuzz'],
+ context=context)
+ if data.get('state', False):
+ if data['state'] != 'recruit' and int(data['no_of_employee']) >= (int(data['max_employees']) + data['max_employees_fuzz']):
+ raise osv.except_osv(_('Job not open for recruitment!'),
+ _('You may not register applicants for jobs that are not recruiting.'))
+
+ return super(hr_applicant, self).create(cr, uid, vals, context=context)
+
+
+class hr_contract(osv.Model):
+
+ _name = 'hr.contract'
+ _inherit = 'hr.contract'
+
+ def _get_job_from_applicant(self, cr, uid, context=None):
+ """If the applicant went through recruitment get the job id from there."""
+
+ res = False
+ if context != None:
+ ee_ids = context.get('search_default_employee_id', False)
+ if ee_ids and len(ee_ids) > 0:
+ # If this is the first contract try to obtain job position from
+ # application
+ if len(self.search(cr, uid, [('employee_id', 'in', ee_ids)], context=context)) > 0:
+ return res
+ applicant_obj = self.pool.get('hr.applicant')
+ applicant_ids = applicant_obj.search(
+ cr, uid, [('emp_id', '=', ee_ids[0])], context=context)
+ if len(applicant_ids) > 0:
+ data = applicant_obj.read(
+ cr, uid, applicant_ids[0], ['job_id'], context=context)
+ res = data['job_id'][0]
+
+ return res
+
+ _defaults = {
+ 'job_id': _get_job_from_applicant,
+ }
+
+ def create(self, cr, uid, vals, context=None):
+
+ # If the contract is for an employee with a pre-existing contract for
+ # the same job, then bypass checks.
+ employee_id = vals.get('employee_id', False)
+ if employee_id:
+ contract_ids = self.search(cr, uid, [
+ ('employee_id', '=', employee_id),
+ ('state', 'not in', [
+ 'draft', 'done']),
+ ],
+ context=context)
+ for contract in self.browse(cr, uid, contract_ids, context=context):
+ if vals.get('job_id', False) == contract.job_id.id:
+ return super(hr_contract, self).create(cr, uid, vals, context=context)
+
+ # 1. Verify job is in recruitment
+ if vals.get('job_id', False):
+ data = self.pool.get('hr.job').read(cr, uid, vals['job_id'],
+ ['name', 'max_employees', 'max_employees_fuzz',
+ 'no_of_employee', 'state'],
+ context=context)
+ if data.get('state', False):
+ if data['state'] != 'recruit' and int(data['no_of_employee']) >= (int(data['max_employees']) + data['max_employees_fuzz']):
+ raise osv.except_osv(
+ _('The Job "%s" is not in recruitment!') % (
+ data['name']),
+ _('You may not create contracts for jobs that are not in recruitment state.'))
+
+ # 2. Verify that the number of open contracts < total expected
+ # employees
+ if vals.get('job_id', False):
+ contract_ids = self.search(cr, uid, [
+ ('job_id', '=', vals[
+ 'job_id']),
+ ('state', 'not in', ['done']),
+ ],
+ context=context)
+
+ data = self.pool.get('hr.job').read(cr, uid, vals['job_id'],
+ ['name', 'expected_employees',
+ 'max_employees', 'max_employees_fuzz'],
+ context=context)
+ expected_employees = data.get(
+ 'expected_employees', False) and data['expected_employees'] or 0
+ max_employees = data.get(
+ 'max_employees', False) and data['max_employees'] or 0
+ max_employees_fuzz = data.get(
+ 'max_employees_fuzz', False) and data['max_employees_fuzz'] or 0
+
+ if len(contract_ids) >= max(expected_employees, max_employees + max_employees_fuzz):
+ raise osv.except_osv(
+ _('Maximum Number of Employees Exceeded!'),
+ _('The maximum number of employees for "%s" has been exceeded.') % (data['name']))
+
+ return super(hr_contract, self).create(cr, uid, vals, context=context)
+
+
+class hr_recruitment_request(osv.Model):
+
+ _name = 'hr.recruitment.request'
+ _description = 'Request for recruitment of additional personnel'
+ _inherit = ['mail.thread', 'ir.needaction_mixin']
+
+ _columns = {
+ 'name': fields.char('Description', size=64),
+ 'user_id': fields.many2one('res.users', 'Requesting User', required=True),
+ 'department_id': fields.many2one('hr.department', 'Department'),
+ 'job_id': fields.many2one('hr.job', 'Job', required=True),
+ 'number': fields.integer('Number to Recruit'),
+ 'current_number': fields.related('job_id', 'no_of_employee', type='integer', string="Current Number of Employees", readonly=True),
+ 'max_number': fields.related('job_id', 'max_employees', type='integer', string="Maximum Number of Employees", readonly=True),
+ 'reason': fields.text('Reason for Request'),
+ 'state': fields.selection([('draft', 'Draft'),
+ ('confirm', 'Confirmed'),
+ ('exception', 'Exception'),
+ ('recruitment', 'In Recruitment'),
+ ('decline', 'Declined'),
+ ('done', 'Done'),
+ ('cancel', 'Cancelled'),
+ ],
+ 'State', readonly=True),
+ }
+
+ _order = 'department_id, job_id'
+
+ _defaults = {
+ 'number': 1,
+ 'user_id': lambda self, cr, uid, context=None: uid,
+ }
+
+ _track = {
+ 'state': {
+ 'hr_labour_recruitment.mt_alert_request_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
+ 'hr_labour_recruitment.mt_alert_request_exception': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'exception',
+ 'hr_labour_recruitment.mt_alert_request_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'recruitment',
+ 'hr_labour_recruitment.mt_alert_request_declined': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'decline',
+ },
+ }
+
+ def onchange_job(self, cr, uid, ids, job_id, context=None):
+
+ res = {'value': {'deparment_id': False, 'name': False}}
+ if job_id:
+ data = self.pool.get('hr.job').read(
+ cr, uid, job_id, ['name', 'department_id'], context=context)
+ if data.get('department_id', False):
+ res['value']['department_id'] = data['department_id'][0]
+ res['value']['name'] = 'Personnel Request: ' + str(data['name'])
+
+ return res
+
+ def _needaction_domain_get(self, cr, uid, context=None):
+
+ users_obj = self.pool.get('res.users')
+
+ domain = []
+ has_prev_domain = False
+ if users_obj.has_group(cr, uid, 'base.group_hr_manager'):
+ domain = [('state', '=', 'recruitment')]
+ has_prev_domain = True
+ if users_obj.has_group(cr, uid, 'hr_security.group_hr_director'):
+ if has_prev_domain:
+ domain = ['|'] + domain
+ domain = domain + [('state', 'in', ['confirm', 'exception'])]
+
+ if len(domain) == 0:
+ return False
+
+ return domain
+
+ def condition_exception(self, cr, uid, ids, context=None):
+
+ for req in self.browse(cr, uid, ids, context=context):
+ if req.number + req.job_id.expected_employees > req.job_id.max_employees:
+ return True
+
+ return False
+
+ def _state(self, cr, uid, ids, state, context=None):
+
+ job_obj = self.pool.get('hr.job')
+
+ for req in self.browse(cr, uid, ids, context=context):
+
+ if state == 'recruitment':
+ job_obj.write(cr, uid, req.job_id.id, {
+ 'no_of_recruitment': req.number}, context=context)
+ job_obj.job_recruitement(cr, uid, [req.job_id.id])
+ elif state in ['done', 'cancel']:
+ job_obj.job_open(cr, uid, [req.job_id.id])
+
+ self.write(cr, uid, req.id, {'state': state}, context=context)
+
+ return True
+
+ def _state_subscribe_users(self, cr, uid, ids, state, context=None):
+
+ imd_obj = self.pool.get('ir.model.data')
+ model, group1_id = imd_obj.get_object_reference(
+ cr, uid, 'base', 'group_hr_manager')
+ model, group2_id = imd_obj.get_object_reference(
+ cr, uid, 'hr_security', 'group_hr_director')
+ data = self.pool.get('res.groups').read(
+ cr, uid, [group1_id, group2_id], ['users'], context=context)
+ user_ids = list(set(data[0]['users'] + data[1]['users']))
+ self.message_subscribe_users(
+ cr, uid, ids, user_ids=user_ids, context=context)
+
+ return self._state(cr, uid, ids, state, context=context)
+
+ def state_confirm(self, cr, uid, ids, context=None):
+
+ return self._state_subscribe_users(cr, uid, ids, 'confirm', context=context)
+
+ def state_exception(self, cr, uid, ids, context=None):
+
+ return self._state_subscribe_users(cr, uid, ids, 'exception', context=context)
+
+ def state_recruitment(self, cr, uid, ids, context=None):
+
+ return self._state_subscribe_users(cr, uid, ids, 'recruitment', context=context)
+
+ def state_done(self, cr, uid, ids, context=None):
+
+ return self._state(cr, uid, ids, 'done', context=context)
+
+ def state_cancel(self, cr, uid, ids, context=None):
+
+ return self._state(cr, uid, ids, 'cancel', context=context)
=== added file 'hr_labour_recruitment/hr_recruitment_data.xml'
--- hr_labour_recruitment/hr_recruitment_data.xml 1970-01-01 00:00:00 +0000
+++ hr_labour_recruitment/hr_recruitment_data.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Alert-related subtypes for messaging / Chatter -->
+
+ <record id="mt_alert_request_confirmed" model="mail.message.subtype">
+ <field name="name">Personnel Request Confirmed</field>
+ <field name="res_model">hr.recruitment.request</field>
+ <field name="description">Personnel Request is awaiting approval</field>
+ </record>
+
+ <record id="mt_alert_request_exception" model="mail.message.subtype">
+ <field name="name">Personnel Request in Exception</field>
+ <field name="res_model">hr.recruitment.request</field>
+ <field name="description">Personnel Request exceeds maximum number of employees for the job. Awaiting approval.</field>
+ </record>
+
+ <record id="mt_alert_request_approved" model="mail.message.subtype">
+ <field name="name">Personnel Request Approved</field>
+ <field name="res_model">hr.recruitment.request</field>
+ <field name="description">Personnel Request has been approved</field>
+ </record>
+
+ <record id="mt_alert_request_declined" model="mail.message.subtype">
+ <field name="name">Personnel Request Declined</field>
+ <field name="res_model">hr.contract</field>
+ <field name="description">Personnel Request has been declined</field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_labour_recruitment/hr_recruitment_view.xml'
--- hr_labour_recruitment/hr_recruitment_view.xml 1970-01-01 00:00:00 +0000
+++ hr_labour_recruitment/hr_recruitment_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <!-- Menus -->
+
+ <menuitem name="Recruitment"
+ id="submenu_hr_recruitment"
+ parent="hr.menu_hr_main"
+ sequence="110" groups="base.group_hr_user"/>
+
+ <!-- Delete Recruitment top-level menu and put it under our own submenu -->
+ <delete model="ir.ui.menu" id="base.menu_crm_case_job_req_main"/>
+ <delete model="ir.ui.menu" id="hr_recruitment.menu_crm_case_categ0_act_job"/>
+ <menuitem
+ name="Applications"
+ parent="submenu_hr_recruitment"
+ id="menu_crm_case_categ0_act_job" action="hr_recruitment.crm_case_categ0_act_job" sequence="1"/>
+
+ <!-- Recruitment Requests -->
+
+ <record id="view_hr_recruitment_request_filter" model="ir.ui.view">
+ <field name="name">hr.recruitment.request.filter</field>
+ <field name="model">hr.recruitment.request</field>
+ <field name="arch" type="xml">
+ <search string="Search Personnel Requests">
+ <filter name="draft" string="In Draft" domain="[('state','=','draft')]" help="Unconfirmed Requests"/>
+ <filter name="to_approve" string="To Approve" domain="[('state','in',['confirm','exception'])]" help="Confirmed Requests"/>
+ <filter name="to_complete" string="To Complete" domain="[('state','=','recruitment')]" help="Requests in Recruitment"/>
+ <field name="job_id"/>
+ <field name="department_id"/>
+ <group expand="0" string="Group By...">
+ <filter string="Job" icon="terp-personal" domain="[]" context="{'group_by':'job_id'}"/>
+ <filter string="Department" icon="terp-personal+" domain="[]" context="{'group_by':'department_id'}"/>
+ </group>
+ </search>
+ </field>
+ </record>
+
+ <record id="view_recruitment_request_tree" model="ir.ui.view">
+ <field name="name">hr.recruitment.request.tree</field>
+ <field name="model">hr.recruitment.request</field>
+ <field name="arch" type="xml">
+ <tree string="Personnel Requests">
+ <field name="job_id"/>
+ <field name="department_id"/>
+ <field name="user_id"/>
+ <field name="number"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_recruitment_request_form" model="ir.ui.view">
+ <field name="name">hr.recruitment.request.form</field>
+ <field name="model">hr.recruitment.request</field>
+ <field name="arch" type="xml">
+ <form string="Personnel Request" version="7.0">
+ <header>
+ <button name="signal_confirm" type="workflow" states="draft" groups="base.group_hr_user" string="Confirm" class="oe_highlight"/>
+ <button name="signal_approve" type="workflow" states="confirm,exception" groups="hr_security.group_hr_director" string="Approve" class="oe_highlight"/>
+ <button name="signal_decline" type="workflow" states="confirm,exception" groups="hr_security.group_hr_director" string="Decline" class="oe_highlight"/>
+ <button name="signal_done" type="workflow" states="recruitment" groups="base.group_hr_manager" string="Done" class="oe_highlight"/>
+ <button name="signal_cancel" type="workflow" states="confirm,exception,recruitment" groups="base.group_hr_user" string="Cancel" class="oe_highlight"/>
+ <field name="state" widget="statusbar" statusbar_visible="draft,confirm,exception,recruitment" statusbar_colors='{"exception":"read"}'/>
+ </header>
+ <group>
+ <group>
+ <field name="user_id"/>
+ <field name="job_id" on_change="onchange_job(job_id)"/>
+ <field name="number"/>
+ </group>
+ <group>
+ <field name="name"/>
+ <field name="department_id"/>
+ </group>
+ <group>
+ <field name="current_number"/>
+ <field name="max_number"/>
+ </group>
+ </group>
+ <separator string="Reason for Request"/>
+ <field name="reason" nolabel="1"/>
+ <newline/>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </field>
+ </record>
+ <record id="open_recruitment_request" model="ir.actions.act_window">
+ <field name="name">Personnel Requests</field>
+ <field name="res_model">hr.recruitment.request</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" eval="False"/>
+ <field name="context">{'search_default_to_approve':1, 'search_default_to_complete':1}</field>
+ <field name="search_view_id" ref="view_hr_recruitment_request_filter"/>
+ <field name="help" type="html">
+ <p class="oe_view_nocontent_create">
+ New Personnel Requests to approve or to complete.
+ </p><p>
+ You should fill out a Personnel Request form to recruit new employees. Once
+ you have confirmed it the HR Director should approve it.
+ </p>
+ </field>
+ </record>
+ <menuitem id="menu_recruitment_request"
+ action="open_recruitment_request"
+ parent="submenu_hr_recruitment"
+ groups="base.group_hr_user"
+ sequence="15"/>
+
+ <!-- Jobs -->
+ <record id="view_job_form" model="ir.ui.view">
+ <field name="name">hr.job.form.inherit</field>
+ <field name="model">hr.job</field>
+ <field name="inherit_id" ref="hr.view_hr_job_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//button[@name='job_recruitement']" position="replace"/>
+ <xpath expr="//button[@name='job_open']" position="replace"/>
+ <xpath expr="//field[@name='expected_employees']" position="after">
+ <field name="max_employees"/>
+ <field name="max_employees_fuzz"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ <record id="view_job_tree" model="ir.ui.view">
+ <field name="name">hr.job.tree.inherit</field>
+ <field name="model">hr.job</field>
+ <field name="inherit_id" ref="hr.view_hr_job_tree"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='expected_employees']" position="before">
+ <field name="max_employees"/>
+ <field name="max_employees_fuzz"/>
+ </xpath>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_labour_recruitment/hr_recruitment_workflow.xml'
--- hr_labour_recruitment/hr_recruitment_workflow.xml 1970-01-01 00:00:00 +0000
+++ hr_labour_recruitment/hr_recruitment_workflow.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <!-- Employee Workflow Definition -->
+ <record id="wkf_recruitment_request" model="workflow">
+ <field name="name">hr.recruitment.request.basic</field>
+ <field name="osv">hr.recruitment.request</field>
+ <field name="on_create">True</field>
+ </record>
+
+ <!-- Workflow Activities (Stages) -->
+
+ <record id="act_draft" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_recruitment_request"/>
+ <field name="name">draft</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'draft'})</field>
+ <field name="flow_start">True</field>
+ </record>
+
+ <record id="act_confirm" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_recruitment_request"/>
+ <field name="name">confirm</field>
+ <field name="kind">function</field>
+ <field name="action">state_confirm()</field>
+ </record>
+
+ <record id="act_exception" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_recruitment_request"/>
+ <field name="name">exception</field>
+ <field name="kind">function</field>
+ <field name="action">state_exception()</field>
+ </record>
+
+ <record id="act_recruitment" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_recruitment_request"/>
+ <field name="name">recruitment</field>
+ <field name="kind">function</field>
+ <field name="action">state_recruitment()</field>
+ </record>
+
+ <record id="act_decline" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_recruitment_request"/>
+ <field name="name">decline</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'decline'})</field>
+ </record>
+
+ <record id="act_done" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_recruitment_request"/>
+ <field name="name">done</field>
+ <field name="kind">function</field>
+ <field name="action">state_done()</field>
+ <field name="flow_stop">True</field>
+ </record>
+
+ <record id="act_cancel" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_recruitment_request"/>
+ <field name="name">cancel</field>
+ <field name="kind">function</field>
+ <field name="action">state_cancel()</field>
+ <field name="flow_stop">True</field>
+ </record>
+
+
+ <!-- Workflow Transitions -->
+
+ <record id="draft2confirm" model="workflow.transition">
+ <field name="act_from" ref="act_draft"/>
+ <field name="act_to" ref="act_confirm"/>
+ <field name="condition">condition_exception() == False</field>
+ <field name="signal">signal_confirm</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="draft2exception" model="workflow.transition">
+ <field name="act_from" ref="act_draft"/>
+ <field name="act_to" ref="act_exception"/>
+ <field name="condition">condition_exception() == True</field>
+ <field name="signal">signal_confirm</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="confirm2recruitment" model="workflow.transition">
+ <field name="act_from" ref="act_confirm"/>
+ <field name="act_to" ref="act_recruitment"/>
+ <field name="signal">signal_approve</field>
+ <field name="group_id" ref="hr_security.group_hr_director"/>
+ </record>
+
+ <record id="exception2recruitment" model="workflow.transition">
+ <field name="act_from" ref="act_exception"/>
+ <field name="act_to" ref="act_recruitment"/>
+ <field name="signal">signal_approve</field>
+ <field name="group_id" ref="hr_security.group_hr_director"/>
+ </record>
+
+ <record id="confirm2decline" model="workflow.transition">
+ <field name="act_from" ref="act_confirm"/>
+ <field name="act_to" ref="act_decline"/>
+ <field name="signal">signal_decline</field>
+ <field name="group_id" ref="hr_security.group_hr_director"/>
+ </record>
+
+ <record id="recruitment2done" model="workflow.transition">
+ <field name="act_from" ref="act_recruitment"/>
+ <field name="act_to" ref="act_done"/>
+ <field name="signal">signal_done</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="confirm2cancel" model="workflow.transition">
+ <field name="act_from" ref="act_confirm"/>
+ <field name="act_to" ref="act_cancel"/>
+ <field name="signal">signal_cancel</field>
+ <field name="group_id" ref="base.group_hr_user"/>
+ </record>
+
+ <record id="recruitment2cancel" model="workflow.transition">
+ <field name="act_from" ref="act_recruitment"/>
+ <field name="act_to" ref="act_cancel"/>
+ <field name="signal">signal_cancel</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_labour_recruitment/security'
=== added file 'hr_labour_recruitment/security/ir.model.access.csv'
--- hr_labour_recruitment/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_labour_recruitment/security/ir.model.access.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,4 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_hr_recruitment_request_user,access_hr_recruitment_request,model_hr_recruitment_request,base.group_hr_user,1,1,1,1
+access_hr_recruitment_request_manager,access_hr_recruitment_request,model_hr_recruitment_request,base.group_hr_manager,1,1,1,1
+access_hr_recruitment_request_director,access_hr_recruitment_request,model_hr_recruitment_request,hr_security.group_hr_director,1,1,0,0
=== added directory 'hr_labour_union'
=== added file 'hr_labour_union/__init__.py'
--- hr_labour_union/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_labour_union/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr
=== added file 'hr_labour_union/__openerp__.py'
--- hr_labour_union/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_labour_union/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,52 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Labour Union',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Labour Union
+=====================
+
+This module adds:
+ * Field on employee record denoting whether the employee is a member of a labour union
+ * Salary rule for deducting union fee from union members
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr',
+ 'hr_payroll',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'hr_labour_union_data.xml',
+ 'hr_labour_union_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added file 'hr_labour_union/hr.py'
--- hr_labour_union/hr.py 1970-01-01 00:00:00 +0000
+++ hr_labour_union/hr.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,44 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from openerp.osv import fields, osv
+
+
+class hr_employee(osv.Model):
+
+ _name = 'hr.employee'
+ _inherit = 'hr.employee'
+
+ _columns = {
+ 'is_labour_union': fields.boolean('Labour Union Member'),
+ 'labour_union_date': fields.date('Date of Membership'),
+ }
+
+
+class hr_contract(osv.Model):
+
+ _name = 'hr.contract'
+ _inherit = 'hr.contract'
+
+ _columns = {
+ 'is_labour_union': fields.related('employee_id', 'is_labour_union', type='boolean',
+ store=True, string='Labour Union Member'),
+ }
=== added file 'hr_labour_union/hr_labour_union_data.xml'
--- hr_labour_union/hr_labour_union_data.xml 1970-01-01 00:00:00 +0000
+++ hr_labour_union/hr_labour_union_data.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <!-- Payroll Register -->
+ <record id="hr_register_labour_union" model="hr.contribution.register">
+ <field name="name">Register for Labour Union Dues</field>
+ </record>
+
+ <record id="hr_payroll_rule_labour_union" model="hr.salary.rule">
+ <field name="code">LU</field>
+ <field name="name">Labour Union Contribution</field>
+ <field name="category_id" ref="hr_payroll.DED"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = contract.is_labour_union and categories.GROSS >= 0.01</field>
+ <field name="amount_select">fix</field>
+ <field eval="0" name="amount_fix"/>
+ <field name="register_id" ref="hr_register_labour_union"/>
+ <field name="sequence" eval="2200"/>
+ <field name="note"></field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_labour_union/hr_labour_union_view.xml'
--- hr_labour_union/hr_labour_union_view.xml 1970-01-01 00:00:00 +0000
+++ hr_labour_union/hr_labour_union_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<openerp>
+ <data>
+
+ <record id="view_hr_employee_form" model="ir.ui.view">
+ <field name="name">hr.employee.form.labourunion</field>
+ <field name="model">hr.employee</field>
+ <field name="inherit_id" ref="hr_contract.hr_hr_employee_view_form2"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='manager']" position="after">
+ <field name="is_labour_union"/>
+ <field name="labour_union_date"/>
+ </xpath>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_payroll_extension'
=== added file 'hr_payroll_extension/__init__.py'
--- hr_payroll_extension/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_payroll_extension/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_payroll
=== added file 'hr_payroll_extension/__openerp__.py'
--- hr_payroll_extension/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_payroll_extension/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,61 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Payroll Extension',
+ 'category': 'Human Resources',
+ 'author': 'Michael Telahun Makonnen',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'version': '1.0',
+ 'description': """
+Extended set of Payroll Rules and Structures
+============================================
+
+ - Detailed caclculatation of worked hours, leaves, overtime, etc
+ - Overtime
+ - Paid and Unpaid Leaves
+ - Federal Income Tax Withholding rules
+ - Provident/Pension Fund contributions
+ - Various Earnings and Deductions
+ - Payroll Report
+ """,
+ 'depends': [
+ 'hr_attendance',
+ 'hr_payroll',
+ 'hr_payroll_period',
+ 'hr_policy_absence',
+ 'hr_policy_ot',
+ 'hr_policy_presence',
+ 'hr_public_holidays',
+ 'hr_schedule',
+ ],
+ 'init_xml': [],
+ 'update_xml': [
+ 'data/hr_payroll_extension_data.xml',
+ 'data/hr.salary.rule.csv',
+ 'hr_payroll_view.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [],
+ 'active': False,
+ 'installable': True,
+}
=== added directory 'hr_payroll_extension/data'
=== added file 'hr_payroll_extension/data/hr.salary.rule.csv'
--- hr_payroll_extension/data/hr.salary.rule.csv 1970-01-01 00:00:00 +0000
+++ hr_payroll_extension/data/hr.salary.rule.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,13 @@
+id,code,name,condition_select,condition_range,condition_range_min,condition_range_max,amount_select,amount_percentage,amount_fix,amount_percentage_base,parent_rule_id/id,category_id/id,sequence
+fitw_10,2003FIT00,Exempt,range,TXBLINCOM,0,150,fix,0,0,,hr_payroll_rule_fit,hr_categ_fitcalc,1000
+fitw_11,2003FIT10,10.00%,range,TXBLINCOM,151,650,percentage,10,0,TXBLINCOM - 150,hr_payroll_rule_fit,hr_categ_fitcalc,1010
+fitw_12,2003FIT15A,15.00%,range,TXBLINCOM,650,1400,percentage,15,0,TXBLINCOM - 650,hr_payroll_rule_fit,hr_categ_fitcalc,1015
+fitw_13,2003FIT15B,15%-Fixed,range,TXBLINCOM,650,1400,fix,0,50,,hr_payroll_rule_fit,hr_categ_fitcalc,1016
+fitw_14,2003FIT20A,20.00%,range,TXBLINCOM,1400,2350,percentage,20,0,TXBLINCOM - 1400,hr_payroll_rule_fit,hr_categ_fitcalc,1020
+fitw_15,2003FIT20B,20%-Fixed,range,TXBLINCOM,1400,2350,fix,0,162.5,,hr_payroll_rule_fit,hr_categ_fitcalc,1021
+fitw_16,2003FIT25A,25.00%,range,TXBLINCOM,2350,3550,percentage,25,0,TXBLINCOM - 2350,hr_payroll_rule_fit,hr_categ_fitcalc,1025
+fitw_17,2003FIT25B,25%-Fixed,range,TXBLINCOM,2350,3550,fix,0,352.5,,hr_payroll_rule_fit,hr_categ_fitcalc,1026
+fitw_18,2003FIT30A,30.00%,range,TXBLINCOM,3550,5000,percentage,30,0,TXBLINCOM - 3550,hr_payroll_rule_fit,hr_categ_fitcalc,1030
+fitw_19,2003FIT30B,30%-Fixed,range,TXBLINCOM,3550,5000,fix,0,652.5,,hr_payroll_rule_fit,hr_categ_fitcalc,1031
+fitw_20,2003FIT35A,35.00%,range,TXBLINCOM,5000,1000000000,percentage,35,0,TXBLINCOM - 5000,hr_payroll_rule_fit,hr_categ_fitcalc,1035
+fitw_21,2003FIT35B,35%-Fixed,range,TXBLINCOM,5000,1000000000,fix,0,1087.5,,hr_payroll_rule_fit,hr_categ_fitcalc,1036
=== added file 'hr_payroll_extension/data/hr_payroll_extension_data.xml'
--- hr_payroll_extension/data/hr_payroll_extension_data.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_extension/data/hr_payroll_extension_data.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,546 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data noupdate="1">
+
+ <!-- Delete some default leave types from the database -->
+ <delete model="hr.holidays.status" search="[('name','like','Legal Leaves%')]"/>
+ <delete model="hr.holidays.status" search="[('name','=','Compensatory Days')]"/>
+ <delete model="hr.holidays.status" search="[('name','=','Unpaid')]"/>
+
+ <!-- Contribution Register -->
+
+ <record id="hr_register_ot" model="hr.contribution.register">
+ <field name="name">Register for Overtime</field>
+ </record>
+
+ <record id="hr_register_transport_allowance" model="hr.contribution.register">
+ <field name="name">Register for Transport Allowance</field>
+ </record>
+
+ <record id="hr_register_bonus" model="hr.contribution.register">
+ <field name="name">Register for Bonus</field>
+ </record>
+
+ <record id="hr_register_ule" model="hr.contribution.register">
+ <field name="name">Register for Unused Leaves converted into cash</field>
+ </record>
+
+ <record id="hr_register_provident_fund" model="hr.contribution.register">
+ <field name="name">Register for Provident Fund</field>
+ </record>
+
+ <record id="hr_register_pension_fund" model="hr.contribution.register">
+ <field name="name">Register for Pension Fund</field>
+ </record>
+
+ <record id="hr_register_fit" model="hr.contribution.register">
+ <field name="name">Register for Federal Income Tax</field>
+ </record>
+
+ <record id="hr_company_transport_register" model="hr.contribution.register">
+ <field name="name">Register for Company Provided Transport Deduction</field>
+ </record>
+
+ <record id="hr_register_penalty" model="hr.contribution.register">
+ <field name="name">Register for Penalties</field>
+ </record>
+
+ <record id="hr_register_disciplinary" model="hr.contribution.register">
+ <field name="name">Register for Disciplinary Penalties</field>
+ </record>
+
+ <record id="hr_register_idcard" model="hr.contribution.register">
+ <field name="name">Register for ID Card Replacement</field>
+ </record>
+
+ <record id="hr_register_advance" model="hr.contribution.register">
+ <field name="name">Register for Repayment of Salary Advances</field>
+ </record>
+
+ <record id="hr_register_other_deductions" model="hr.contribution.register">
+ <field name="name">Register for Other Deductions</field>
+ </record>
+
+ <!--
+ DO NOT CHANGE THE CODES BELOW!!!
+ The codes are used in the payroll register module to print out
+ the payroll register.
+ -->
+
+ <!-- Salary Rule Categories -->
+
+ <record id="hr_categ_ot" model="hr.salary.rule.category">
+ <field name="name">Overtime</field>
+ <field name="code">OT</field>
+ </record>
+
+ <record id="hr_categ_txbl" model="hr.salary.rule.category">
+ <field name="name">Taxable Earnings</field>
+ <field name="code">TXBL</field>
+ </record>
+
+ <record id="hr_categ_txexmpt" model="hr.salary.rule.category">
+ <field name="name">Tax Exempt Earnings</field>
+ <field name="code">TXEXMPT</field>
+ </record>
+
+ <record id="hr_categ_fitcalc" model="hr.salary.rule.category">
+ <field name="code">FITCALC</field>
+ <field name="name">Federal Income Tax Withholding</field>
+ </record>
+
+ <record id="hr_categ_dedtotal" model="hr.salary.rule.category">
+ <field name="code">DEDTOTAL</field>
+ <field name="name">Deductions Total</field>
+ </record>
+
+ <record id="hr_categ_er" model="hr.salary.rule.category">
+ <field name="code">ER</field>
+ <field name="name">Employer Contribution</field>
+ </record>
+
+ <!-- Basic Income based on attendance -->
+ <record id="hr_salary_rule_basic_attendance" model="hr.salary.rule">
+ <field name="code">WORKWAGE</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = contract.wage_hourly</field>
+ <field name="quantity">((utils.PPF.amount < 0.99) and worked_days.MAX.number_of_hours or 208) - worked_days.AWOL.number_of_hours</field>
+ <field name="category_id" ref="hr_payroll.BASIC"/>
+ <field name="name">Basic Salary based on Attendance</field>
+ <field name="sequence" eval="2"/>
+ <field name="note">
+ Calculate monthly basic wage according to attendance records of employee. Does not include Overtime.
+ </field>
+ </record>
+
+ <!-- Basic Income reduced based on no. of hours absent -->
+ <record id="hr_salary_rule_adjust_attendance" model="hr.salary.rule">
+ <field name="code">ATTADJUST</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = worked_days.AWOL.number_of_hours == worked_days.MAX.number_of_hours and -(categories.BASIC / worked_days.AWOL.number_of_hours) or -(contract.wage_hourly)</field>
+ <field name="quantity">min(worked_days.AWOL.number_of_hours, 208)</field>
+ <field name="category_id" ref="hr_payroll.BASIC"/>
+ <field name="name">Salary Adjustment Based on Attendance</field>
+ <field name="sequence" eval="3"/>
+ <field name="note">
+ Adjust basic salary by number of hours absent without leave.
+ </field>
+ </record>
+
+ <!-- Overtime rules -->
+
+ <record id="hr_salary_rule_daily_ot" model="hr.salary.rule">
+ <field name="code">OTD</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = worked_days.WORKOTD.number_of_hours > 0</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = worked_days.WORKOTD.rate * contract.wage_hourly</field>
+ <field name="quantity">worked_days.WORKOTD.number_of_hours</field>
+ <field name="category_id" ref="hr_categ_ot"/>
+ <field name="name">Day OverTime</field>
+ <field name="register_id" ref="hr_register_ot"/>
+ <field name="sequence" eval="30"/>
+ <field name="note">Applicable to both salaried and hourly employees.</field>
+ </record>
+
+ <record id="hr_salary_rule_night_ot" model="hr.salary.rule">
+ <field name="code">OTN</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = worked_days.WORKOTN.number_of_hours > 0</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = worked_days.WORKOTN.rate * contract.wage_hourly</field>
+ <field name="quantity">worked_days.WORKOTN.number_of_hours</field>
+ <field name="category_id" ref="hr_categ_ot"/>
+ <field name="name">Night OverTime</field>
+ <field name="register_id" ref="hr_register_ot"/>
+ <field name="sequence" eval="33"/>
+ <field name="note">Applicable to both salaried and hourly employees.</field>
+ </record>
+
+ <record id="hr_salary_rule_weekly_ot" model="hr.salary.rule">
+ <field name="code">OTW</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = worked_days.WORKOTW.number_of_hours > 0</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = worked_days.WORKOTW.rate * contract.wage_hourly</field>
+ <field name="quantity">worked_days.WORKOTW.number_of_hours</field>
+ <field name="category_id" ref="hr_categ_ot"/>
+ <field name="name">Weekly OverTime</field>
+ <field name="register_id" ref="hr_register_ot"/>
+ <field name="sequence" eval="35"/>
+ <field name="note">Applicable to both salaried and hourly employees.</field>
+ </record>
+
+ <record id="hr_salary_rule_restday_ot" model="hr.salary.rule">
+ <field name="code">OTR</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = worked_days.WORKOTR.number_of_hours > 0</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = worked_days.WORKOTR.rate * contract.wage_hourly</field>
+ <field name="quantity">worked_days.WORKOTR.number_of_hours</field>
+ <field name="category_id" ref="hr_categ_ot"/>
+ <field name="name">Rest Day OverTime</field>
+ <field name="register_id" ref="hr_register_ot"/>
+ <field name="sequence" eval="36"/>
+ <field name="note">Applicable to both salaried and hourly employees.</field>
+ </record>
+
+ <record id="hr_salary_rule_holiday_ot" model="hr.salary.rule">
+ <field name="code">OTH</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = worked_days.WORKOTH.number_of_hours > 0</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = worked_days.WORKOTH.rate * contract.wage_hourly</field>
+ <field name="quantity">worked_days.WORKOTH.number_of_hours</field>
+ <field name="category_id" ref="hr_categ_ot"/>
+ <field name="name">Holiday OverTime</field>
+ <field name="register_id" ref="hr_register_ot"/>
+ <field name="sequence" eval="40"/>
+ <field name="note">Not applicable to salaried employees.</field>
+ </record>
+
+ <!-- Hr Salary Rules for allowance -->
+
+ <record id="hr_salary_rule_houserentallowance" model="hr.salary.rule">
+ <field name="code">HRA</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = worked_days.WORK100.number_of_hours and contract.hra_amount or 0</field>
+ <field name="category_id" ref="hr_payroll.ALW"/>
+ <field name="name">House Rent Allowance</field>
+ <field name="sequence" eval="50"/>
+ <field name="note">
+ An allowance given to the employee for taking care of living accommodation expenses.
+ </field>
+ </record>
+
+ <record id="hr_salary_trans_allowance" model="hr.salary.rule">
+ <field name="code">TRA</field>
+ <field name="name">Transport Allowance</field>
+ <field name="category_id" ref="hr_payroll.ALW"/>
+ <field name="amount_select">fix</field>
+ <field eval="0" name="amount_fix"/>
+ <field name="register_id" ref="hr_register_transport_allowance"/>
+ <field name="sequence" eval="100"/>
+ <field name="note"></field>
+ </record>
+
+ <!-- If you change python condition here don't forget to also change
+ corresponding tax exemption rule -->
+ <record id="hr_salary_rule_trans_variable_allowance" model="hr.salary.rule">
+ <field name="code">TRVA</field>
+ <field name="name">Transport Allowance</field>
+ <field name="category_id" ref="hr_payroll.ALW"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = contract.wage < 5000</field>
+ <field name="amount_select">fix</field>
+ <field eval="0" name="amount_fix"/>
+ <field name="quantity">worked_days.WORK100.number_of_days + worked_days.WORKOTR.number_of_days + worked_days.WORKOTH.number_of_days</field>
+ <field name="register_id" ref="hr_register_transport_allowance"/>
+ <field name="sequence" eval="150"/>
+ </record>
+
+ <record id="hr_salary_rule_bonus" model="hr.salary.rule">
+ <field name="code">BONUS</field>
+ <field name="name">Bonus</field>
+ <field name="category_id" ref="hr_payroll.ALW"/>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = inputs.BNS.amount</field>
+ <field name="register_id" ref="hr_register_bonus"/>
+ <field name="sequence" eval="250"/>
+ <field name="note"></field>
+ </record>
+
+ <record id="hr_salary_rule_arrears" model="hr.salary.rule">
+ <field name="code">ARRE</field>
+ <field name="name">Arrears</field>
+ <field name="category_id" ref="hr_payroll.ALW"/>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = inputs.ARS.amount</field>
+ <field eval="0.0" name="amount_fix"/>
+ <field name="sequence" eval="300"/>
+ <field name="note"></field>
+ </record>
+
+ <record id="hr_salary_rule_ule" model="hr.salary.rule">
+ <field name="code">ULE</field>
+ <field name="name">Unused Leave</field>
+ <field name="category_id" ref="hr_payroll.ALW"/>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = inputs.ULE.amount</field>
+ <field name="register_id" ref="hr_register_ule"/>
+ <field name="sequence" eval="350"/>
+ <field name="note"></field>
+ </record>
+
+ <record id="hr_trans_allowance_exempt" model="hr.salary.rule">
+ <field name="code">TRAEX</field>
+ <field name="name">Transport Allowance - Tax Exempt</field>
+ <field name="category_id" ref="hr_categ_txexmpt"/>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = min(rules.TRA.amount_fix,BASIC*0.15)</field>
+ <field name="sequence" eval="700"/>
+ <field name="note">
+ Tax-Exempt portion of transport allowance.
+ </field>
+ </record>
+
+ <!-- If you change python condition here don't forget to also change
+ corresponding allowance -->
+ <record id="hr_trans_variable_allowance_exempt" model="hr.salary.rule">
+ <field name="code">TRVAEX</field>
+ <field name="name">Transport Allowance - Tax Exempt</field>
+ <field name="category_id" ref="hr_categ_txexmpt"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = contract.wage < 5000</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = min(rules.TRVA.amount_fix * (worked_days.WORK100.number_of_days + worked_days.WORKOTR.number_of_days + worked_days.WORKOTH.number_of_days), categories.BASIC*0.15)</field>
+ <field name="sequence" eval="750"/>
+ <field name="note">
+ Tax-Exempt portion of variable transport allowance.
+ </field>
+ </record>
+
+ <record id="hr_payroll_rule_taxable_income" model="hr.salary.rule">
+ <field name="code">TXBLINCOM</field>
+ <field name="name">Taxable Earnings</field>
+ <field name="category_id" ref="hr_categ_txbl"/>
+ <field name="condition_select">none</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = categories.BASIC + categories.OT + categories.ALW - categories.TXEXMPT</field>
+ <field name="sequence" eval="980"/>
+ </record>
+
+ <record id="hr_payroll.hr_rule_taxable" model="hr.salary.rule">
+ <field name="sequence" eval="990"/>
+ <field name="amount_python_compute">result = categories.BASIC + categories.OT + categories.ALW</field>
+ </record>
+
+ <record id="hr_payroll.hr_rule_net" model="hr.salary.rule">
+ <field name="amount_python_compute">result = categories.BASIC + categories.OT + categories.ALW - categories.FITCALC - categories.DED</field>
+ <field name="sequence" eval="4000"/>
+ </record>
+
+ <!-- hr salary rules for Deductions -->
+
+ <record id="hr_payroll_rule_fit" model="hr.salary.rule">
+ <field name="code">FIT</field>
+ <field name="name">Federal Income Tax Withholding</field>
+ <field name="category_id" ref="hr_categ_fitcalc"/>
+ <field name="condition_select">none</field>
+ <field name="appears_on_payslip" eval="False"/>
+ <field name="register_id" ref="hr_register_fit"/>
+ <field name="sequence" eval="1000"/>
+ <field name="note"></field>
+ </record>
+
+ <record id="hr_payroll_rule_provfee" model="hr.salary.rule">
+ <field name="code">PROVFEE</field>
+ <field name="name">EE Provident Fund Contribution</field>
+ <field name="category_id" ref="hr_payroll.DED"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.GROSS >= 0.01</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = categories.BASIC * 0.06</field>
+ <field name="register_id" ref="hr_register_provident_fund"/>
+ <field name="sequence" eval="2000"/>
+ <field name= "note">Employee contribution to Provident Fund.</field>
+ </record>
+
+ <record id="hr_payroll_rule_penfee" model="hr.salary.rule">
+ <field name="code">PENFEE</field>
+ <field name="name">EE Pension Fund Contribution</field>
+ <field name="category_id" ref="hr_payroll.DED"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.GROSS >= 0.01</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = categories.BASIC * 0.06</field>
+ <field name="register_id" ref="hr_register_pension_fund"/>
+ <field name="sequence" eval="2100"/>
+ <field name= "note">Employee contribution to Pension Fund.</field>
+ </record>
+
+ <record id="hr_payroll_rule_cpt" model="hr.salary.rule">
+ <field name="code">CPT</field>
+ <field name="name">Deduction for Company Provided Transport</field>
+ <field name="category_id" ref="hr_payroll.DED"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.GROSS >= 0.01</field>
+ <field name="amount_select">fix</field>
+ <field eval="0" name="amount_fix"/>
+ <field name="register_id" ref="hr_company_transport_register"/>
+ <field name="sequence" eval="2300"/>
+ <field name="appears_on_payslip" eval="False"/>
+ <field name="note">
+ Deductions for company provided transport such as bus service for staff.
+ </field>
+ </record>
+
+ <record id="hr_payroll_rule_idcard" model="hr.salary.rule">
+ <field name="code">IDCARD</field>
+ <field name="name">ID Card Replacement</field>
+ <field name="category_id" ref="hr_payroll.DED"/>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = inputs.IDCARD.amount</field>
+ <field name="register_id" ref="hr_register_idcard"/>
+ <field name="sequence" eval="2320"/>
+ </record>
+
+ <record id="hr_payroll_rule_advance" model="hr.salary.rule">
+ <field name="code">ADV</field>
+ <field name="name">Salary Advance Repayment</field>
+ <field name="category_id" ref="hr_payroll.DED"/>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = inputs.ADVANCE.amount</field>
+ <field name="register_id" ref="hr_register_advance"/>
+ <field name="sequence" eval="2330"/>
+ </record>
+
+ <record id="hr_payroll_rule_penalty" model="hr.salary.rule">
+ <field name="code">PD</field>
+ <field name="name">Penalty</field>
+ <field name="category_id" ref="hr_payroll.DED"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.GROSS >= 0.01</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = inputs.PENALTY.amount</field>
+ <field name="register_id" ref="hr_register_penalty"/>
+ <field name="sequence" eval="2400"/>
+ <field name="note">Non-disciplinary penalties such as library late fees, etc...</field>
+ </record>
+
+ <record id="hr_payroll_rule_dad" model="hr.salary.rule">
+ <field name="code">DAD</field>
+ <field name="name">Disciplinary Action</field>
+ <field name="category_id" ref="hr_payroll.DED"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.GROSS >= 0.01</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = inputs.DISCA.amount</field>
+ <field name="register_id" ref="hr_register_disciplinary"/>
+ <field name="sequence" eval="2500"/>
+ <field name="note">Deductions as a result of disciplinary action.</field>
+ </record>
+
+ <record id="hr_payroll_rule_od" model="hr.salary.rule">
+ <field name="code">OD</field>
+ <field name="name">Other Deductions</field>
+ <field name="category_id" ref="hr_payroll.DED"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.GROSS >= 0.01</field>
+ <field name="amount_select">fix</field>
+ <field eval="0" name="amount_fix"/>
+ <field name="register_id" ref="hr_register_other_deductions"/>
+ <field name="sequence" eval="2600"/>
+ <field name="note"></field>
+ </record>
+
+ <record id="hr_payroll_rule_dedtotal" model="hr.salary.rule">
+ <field name="code">DEDTOTAL</field>
+ <field name="name">Deductions Total</field>
+ <field name="category_id" ref="hr_categ_dedtotal"/>
+ <field name="condition_select">none</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = categories.FITCALC + categories.DED</field>
+ <field name="sequence" eval="2990"/>
+ </record>
+
+ <record id="hr_payroll_rule_provfer" model="hr.salary.rule">
+ <field name="code">PROVFER</field>
+ <field name="name">ER Provident Fund Contribution</field>
+ <field name="category_id" ref="hr_categ_er"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.GROSS >= 0.01</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = categories.BASIC * 0.08</field>
+ <field name="appears_on_payslip" eval="True"/>
+ <field name="register_id" ref="hr_register_provident_fund"/>
+ <field name="sequence" eval="3000"/>
+ <field name="note">Employer contribution to provident fund.</field>
+ </record>
+
+ <record id="hr_payroll_rule_penfer" model="hr.salary.rule">
+ <field name="code">PENFER</field>
+ <field name="name">ER Pension Fund Contribution</field>
+ <field name="category_id" ref="hr_categ_er"/>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.GROSS >= 0.01</field>
+ <field name="amount_select">code</field>
+ <field name="amount_python_compute">result = categories.BASIC * 0.08</field>
+ <field name="appears_on_payslip" eval="True"/>
+ <field name="register_id" ref="hr_register_pension_fund"/>
+ <field name="sequence" eval="3100"/>
+ <field name="note">Employer contribution to pension fund.</field>
+ </record>
+
+ <!-- Rule Inputs -->
+
+ <record id="hr_rule_input_bonus" model="hr.rule.input">
+ <field name="code">BNS</field>
+ <field name="name">Employee Bonus</field>
+ <field name="input_id" ref="hr_salary_rule_bonus"/>
+ </record>
+
+ <record id="hr_rule_input_arrears" model="hr.rule.input">
+ <field name="code">ARS</field>
+ <field name="name">Arrears</field>
+ <field name="input_id" ref="hr_salary_rule_arrears"/>
+ </record>
+
+ <record id="hr_rule_input_ule" model="hr.rule.input">
+ <field name="code">ULE</field>
+ <field name="name">Unused Leave</field>
+ <field name="input_id" ref="hr_salary_rule_ule"/>
+ </record>
+
+ <record id="hr_rule_input_idcard" model="hr.rule.input">
+ <field name="code">IDCARD</field>
+ <field name="name">ID Card Replacement Fee</field>
+ <field name="input_id" ref="hr_payroll_rule_idcard"/>
+ </record>
+
+ <record id="hr_rule_input_advance" model="hr.rule.input">
+ <field name="code">ADVANCE</field>
+ <field name="name">Salary Advance</field>
+ <field name="input_id" ref="hr_payroll_rule_advance"/>
+ </record>
+
+ <record id="hr_rule_input_penalty" model="hr.rule.input">
+ <field name="code">PENALTY</field>
+ <field name="name">Penalty</field>
+ <field name="input_id" ref="hr_payroll_rule_penalty"/>
+ </record>
+
+ <record id="hr_rule_input_dad" model="hr.rule.input">
+ <field name="code">DISCA</field>
+ <field name="name">Disciplinary Action</field>
+ <field name="input_id" ref="hr_payroll_rule_dad"/>
+ </record>
+
+ <!-- Decimal Precision for intermediate payroll values -->
+ <record forcecreate="True" id="decimal_intermediate_payroll" model="decimal.precision">
+ <field name="name">Intermediate Payroll</field>
+ <field name="digits">6</field>
+ </record>
+
+ <!-- Additional Payslip Exceptions -->
+
+ <record id="payslip_exception_net_salary_over" model="hr.payslip.exception.rule">
+ <field name="name">Net Salary exceeds previous payslip</field>
+ <field name="code">NETOVER</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = utils.PREVPS.exists and (categories.NET.amount > (utils.PREVPS.net + (utils.PREVPS.net * 0.20)))</field>
+ <field name="sequence" eval="30"/>
+ <field name="severity">high</field>
+ </record>
+
+ <record id="payslip_exception_net_salary_under" model="hr.payslip.exception.rule">
+ <field name="name">Net Salary less than previous payslip</field>
+ <field name="code">NETUNDER</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = utils.PREVPS.exists and (categories.NET.amount < (utils.PREVPS.net - (utils.PREVPS.net * 0.20)))</field>
+ <field name="sequence" eval="30"/>
+ <field name="severity">high</field>
+ </record>
+
+ </data>
+</openerp>
\ No newline at end of file
=== added file 'hr_payroll_extension/data/leave_types.xml'
--- hr_payroll_extension/data/leave_types.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_extension/data/leave_types.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data noupdate="1">
+
+ <!-- Leave Types -->
+
+ <record model="hr.holidays.status" id="holiday_status_bereavement">
+ <field name="name">Bereavement Leave</field>
+ <field name="code">LVBEREAVEMENT</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">blue</field>
+ </record>
+
+ <record model="hr.holidays.status" id="holiday_status_civic">
+ <field name="name">Civic/Legal/Union Leave</field>
+ <field name="code">LVCIVIC</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">black</field>
+ </record>
+
+ <record model="hr.holidays.status" id="holiday_status_maternity">
+ <field name="name">Maternity/Paternity Leave</field>
+ <field name="code">LVMATERNITY</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">blue</field>
+ </record>
+
+ <record model="hr.holidays.status" id="holiday_status_maternity_medical">
+ <field name="name">Maternity Medical Leave</field>
+ <field name="code">LVMMEDICAL</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">blue</field>
+ </record>
+
+ <record model="hr.holidays.status" id="hr_holidays.holiday_status_sl">
+ <field name="name">Sick Leave</field>
+ <field name="code">LVSICK</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">magenta</field>
+ </record>
+
+ <record model="hr.holidays.status" id="hr_holidays.holiday_status_sl50">
+ <field name="name">Sick Leave at 50%</field>
+ <field name="code">LVSICK50</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">magenta</field>
+ </record>
+
+ <record model="hr.holidays.status" id="hr_holidays.holiday_status_sl00">
+ <field name="name">Sick Leave Without Pay</field>
+ <field name="code">LVSICK00</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">magenta</field>
+ </record>
+
+ <record model="hr.holidays.status" id="holiday_status_paid">
+ <field name="name">Paid Time Off</field>
+ <field name="code">LVPTO</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">blue</field>
+ </record>
+
+ <record model="hr.holidays.status" id="holiday_status_wedding">
+ <field name="name">Wedding Leave</field>
+ <field name="code">LVWEDDING</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">blue</field>
+ </record>
+
+ <record model="hr.holidays.status" id="holiday_status_unpaid">
+ <field name="name">Unpaid Time Off</field>
+ <field name="code">LVUTO</field>
+ <field name="limit" eval="True"/>
+ <field name="color_name">brown</field>
+ </record>
+
+ </data>
+</openerp>
\ No newline at end of file
=== added file 'hr_payroll_extension/hr_payroll.py'
--- hr_payroll_extension/hr_payroll.py 1970-01-01 00:00:00 +0000
+++ hr_payroll_extension/hr_payroll.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,1514 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from pytz import timezone, utc
+
+import openerp.addons.decimal_precision as dp
+from datetime import datetime, timedelta
+
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as OE_DATEFORMAT
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as OE_DATETIMEFORMAT
+from openerp.tools.translate import _
+from openerp.osv import fields, osv
+
+
+class last_X_days:
+
+ """Last X Days
+ Keeps track of the days an employee worked/didn't work in the last X days.
+ """
+
+ def __init__(self, days=6):
+ self.limit = days
+ self.arr = []
+
+ def push(self, worked=False):
+ if len(self.arr) == self.limit:
+ self.arr.pop(0)
+ self.arr.append(worked)
+ return [v for v in self.arr]
+
+ def days_worked(self):
+ res = 0
+ for d in self.arr:
+ if d == True:
+ res += 1
+ return res
+
+
+class hr_payslip(osv.osv):
+
+ _name = 'hr.payslip'
+ _inherit = 'hr.payslip'
+
+ def _get_policy(self, policy_group, policy_ids, dDay):
+ "Return a policy with an effective date before dDay but greater than all others"
+
+ if not policy_group or not policy_ids:
+ return None
+
+ res = None
+ for policy in policy_ids:
+ dPolicy = datetime.strptime(policy.date, OE_DATEFORMAT).date()
+ if dPolicy <= dDay:
+ if res == None:
+ res = policy
+ elif dPolicy > datetime.strptime(res.date, OE_DATEFORMAT).date():
+ res = policy
+
+ return res
+
+ def _get_ot_policy(self, policy_group, dDay):
+ "Return an OT policy with an effective date before dDay but greater than all others"
+
+ return self._get_policy(policy_group, policy_group.ot_policy_ids, dDay)
+
+ def _get_absence_policy(self, policy_group, dDay):
+ "Return an Absence policy with an effective date before dDay but greater than all others"
+
+ return self._get_policy(policy_group, policy_group.absence_policy_ids, dDay)
+
+ def _get_presence_policy(self, policy_group, dDay):
+ "Return a Presence Policy with an effective date before dDay but greater than all others"
+
+ return self._get_policy(policy_group, policy_group.presence_policy_ids, dDay)
+
+ def _get_applied_time(self, worked_hours, pol_active_after, pol_duration=None):
+ '''Returns worked time in hours according to pol_active_after and pol_duration.'''
+
+ applied_min = (worked_hours * 60) - pol_active_after
+ if applied_min > 0.01:
+ applied_min = (pol_duration != None and applied_min >
+ pol_duration) and pol_duration or applied_min
+ else:
+ applied_min = 0
+ applied_hours = float(applied_min) / 60.0
+ return applied_hours
+
+ def _book_holiday_hours(
+ self, cr, uid, contract, presence_policy, ot_policy, attendances,
+ holiday_obj, dtDay, rest_days, lsd, worked_hours, context=None):
+
+ done = False
+ push_lsd = True
+ hours = worked_hours
+
+ # Process normal working hours
+ for line in presence_policy.line_ids:
+ if line.type == 'holiday':
+ holiday_hours = self._get_applied_time(
+ worked_hours, line.active_after,
+ line.duration)
+ attendances[line.code]['number_of_hours'] += holiday_hours
+ attendances[line.code]['number_of_days'] += 1.0
+ hours -= holiday_hours
+ done = True
+
+ # Process OT hours
+ for line in ot_policy.line_ids:
+ if line.type == 'holiday':
+ ot_hours = self._get_applied_time(
+ worked_hours, line.active_after)
+ attendances[line.code]['number_of_hours'] += ot_hours
+ attendances[line.code]['number_of_days'] += 1.0
+ hours -= ot_hours
+ done = True
+
+ if done and (dtDay.weekday() in rest_days or lsd.days_worked == 6):
+ # Mark this day as *not* worked so that subsequent days
+ # are not treated as over-time.
+ lsd.push(False)
+ push_lsd = False
+
+ if hours > -0.01 and hours < 0.01:
+ hours = 0
+ return hours, push_lsd
+
+ def _book_restday_hours(
+ self, cr, uid, contract, presence_policy, ot_policy, attendances,
+ dtDay, rest_days, lsd, worked_hours, context=None):
+
+ done = False
+ push_lsd = True
+ hours = worked_hours
+
+ # Process normal working hours
+ for line in presence_policy.line_ids:
+ if line.type == 'restday' and dtDay.weekday() in rest_days:
+ rd_hours = self._get_applied_time(
+ worked_hours, line.active_after, line.duration)
+ attendances[line.code]['number_of_hours'] += rd_hours
+ attendances[line.code]['number_of_days'] += 1.0
+ hours -= rd_hours
+ done = True
+
+ # Process OT hours
+ for line in ot_policy.line_ids:
+ if line.type == 'restday' and dtDay.weekday() in rest_days:
+ ot_hours = self._get_applied_time(
+ worked_hours, line.active_after)
+ attendances[line.code]['number_of_hours'] += ot_hours
+ attendances[line.code]['number_of_days'] += 1.0
+ hours -= ot_hours
+ done = True
+
+ if done and (dtDay.weekday() in rest_days or lsd.days_worked == 6):
+ # Mark this day as *not* worked so that subsequent days
+ # are not treated as over-time.
+ lsd.push(False)
+ push_lsd = False
+
+ if hours > -0.01 and hours < 0.01:
+ hours = 0
+ return hours, push_lsd
+
+ def _book_weekly_restday_hours(
+ self, cr, uid, contract, presence_policy, ot_policy, attendances,
+ dtDay, rest_days, lsd, worked_hours, context=None):
+
+ done = False
+ push_lsd = True
+ hours = worked_hours
+
+ # Process normal working hours
+ for line in presence_policy.line_ids:
+ if line.type == 'restday':
+ if lsd.days_worked() == line.active_after:
+ rd_hours = self._get_applied_time(
+ worked_hours, line.active_after, line.duration)
+ attendances[line.code]['number_of_hours'] += rd_hours
+ attendances[line.code]['number_of_days'] += 1.0
+ hours -= rd_hours
+ done = True
+
+ # Process OT hours
+ for line in ot_policy.line_ids:
+ if line.type == 'weekly' and line.weekly_working_days and line.weekly_working_days > 0:
+ if lsd.days_worked() == line.weekly_working_days:
+ ot_hours = self._get_applied_time(
+ worked_hours, line.active_after)
+ attendances[line.code]['number_of_hours'] += ot_hours
+ attendances[line.code]['number_of_days'] += 1.0
+ hours -= ot_hours
+ done = True
+
+ if done and (dtDay.weekday() in rest_days or lsd.days_worked == 6):
+ # Mark this day as *not* worked so that subsequent days
+ # are not treated as over-time.
+ lsd.push(False)
+ push_lsd = False
+
+ if hours > -0.01 and hours < 0.01:
+ hours = 0
+ return hours, push_lsd
+
+ def holidays_list_init(self, cr, uid, dFrom, dTo, context=None):
+
+ holiday_obj = self.pool.get('hr.holidays.public')
+ res = holiday_obj.get_holidays_list(
+ cr, uid, dFrom.year, context=context)
+ if dTo.year != dFrom.year:
+ res += holiday_obj.get_holidays_list(
+ cr, uid, dTo.year, context=context)
+ return res
+
+ def holidays_list_contains(self, d, holidays_list):
+
+ if d.strftime(OE_DATEFORMAT) in holidays_list:
+ return True
+ return False
+
+ def attendance_dict_init(self, cr, uid, contract, dFrom, dTo, context=None):
+
+ att_obj = self.pool.get('hr.attendance')
+
+ res = {}
+ att_list = att_obj.punches_list_init(
+ cr, uid, contract.employee_id.id, contract.pps_id,
+ dFrom, dTo, context=context)
+ res.update({'raw_list': att_list})
+ d = dFrom
+ while d <= dTo:
+ res[d.strftime(
+ OE_DATEFORMAT)] = att_obj.total_hours_on_day(
+ cr, uid, contract, d,
+ punches_list=att_list,
+ context=context)
+ d += timedelta(days=+1)
+
+ return res
+
+ def attendance_dict_hours_on_day(self, d, attendance_dict):
+
+ return attendance_dict[d.strftime(OE_DATEFORMAT)]
+
+ def attendance_dict_list(self, att_dict):
+
+ return att_dict['raw_list']
+
+ def leaves_list_init(self, cr, uid, employee_id, dFrom, dTo, tz, context=None):
+ '''Returns a list of tuples containing start, end dates for leaves within
+ the specified period.'''
+
+ leave_obj = self.pool.get('hr.holidays')
+ dtS = datetime.strptime(
+ dFrom.strftime(OE_DATEFORMAT) + ' 00:00:00', OE_DATETIMEFORMAT)
+ dtE = datetime.strptime(
+ dTo.strftime(OE_DATEFORMAT) + ' 23:59:59', OE_DATETIMEFORMAT)
+ utcdt_dayS = timezone(tz).localize(dtS).astimezone(utc)
+ utcdt_dayE = timezone(tz).localize(dtE).astimezone(utc)
+ utc_dayS = utcdt_dayS.strftime(OE_DATETIMEFORMAT)
+ utc_dayE = utcdt_dayE.strftime(OE_DATETIMEFORMAT)
+
+ leave_ids = leave_obj.search(
+ cr, uid, [('state', 'in', ['validate', 'validate1']),
+ ('employee_id', '=', employee_id),
+ ('type', '=', 'remove'),
+ ('date_from', '<=', utc_dayE),
+ ('date_to', '>=', utc_dayS)],
+ context=context)
+ res = []
+ if len(leave_ids) == 0:
+ return res
+
+ for leave in leave_obj.browse(cr, uid, leave_ids, context=context):
+ res.append({
+ 'code': leave.holiday_status_id.code,
+ 'tz': tz,
+ 'start': utc.localize(datetime.strptime(leave.date_from, OE_DATETIMEFORMAT)),
+ 'end': utc.localize(datetime.strptime(leave.date_to, OE_DATETIMEFORMAT))
+ })
+
+ return res
+
+ def leaves_list_get_hours(
+ self, cr, uid, employee_id, contract_id, sched_tpl_id, d,
+ leaves_list, context=None):
+ '''Return the number of hours of leave on a given date, d.'''
+
+ code = False
+ hours = 0
+ if len(leaves_list) == 0:
+ return code, hours
+
+ dtS = datetime.strptime(
+ d.strftime(OE_DATEFORMAT) + ' 00:00:00', OE_DATETIMEFORMAT)
+ dtE = datetime.strptime(
+ d.strftime(OE_DATEFORMAT) + ' 23:59:59', OE_DATETIMEFORMAT)
+ for l in leaves_list:
+ utcBegin = l['start']
+ utcEnd = l['end']
+ dtLvBegin = datetime.strptime(
+ utcBegin.strftime(OE_DATETIMEFORMAT), OE_DATETIMEFORMAT)
+ dtLvEnd = datetime.strptime(
+ utcEnd.strftime(OE_DATETIMEFORMAT), OE_DATETIMEFORMAT)
+ utcdt_dayS = timezone(l['tz']).localize(dtS).astimezone(utc)
+ utcdt_dayE = timezone(l['tz']).localize(dtE).astimezone(utc)
+ if utcdt_dayS <= utcEnd and utcdt_dayE >= utcBegin:
+ code = l['code']
+ sched_tpl_obj = self.pool.get('hr.schedule.template')
+ if utcBegin.date() < utcdt_dayS.date() and utcEnd.date() > utcdt_dayS.date():
+ hours = 24
+ elif utcBegin.date() == utcdt_dayE.date():
+ hours = float((utcdt_dayE - utcBegin).seconds / 60) / 60.0
+ elif utcBegin.date() == utcdt_dayS.date():
+ shift_times = self.pool.get(
+ 'hr.schedule.detail').scheduled_begin_end_times(cr, uid,
+ employee_id,
+ contract_id,
+ dtS,
+ context=context)
+ if len(shift_times) > 0:
+ for dtStart, dtEnd in shift_times:
+ if dtLvBegin < dtEnd:
+ dt = (
+ dtLvBegin < dtStart) and dtStart or dtLvBegin
+ hours += float(
+ (dtEnd - dt).seconds / 60) / 60.0
+ dtLvBegin = dtEnd
+ else:
+ hours = sched_tpl_obj.get_hours_by_weekday(
+ cr, uid, sched_tpl_id, d.weekday(),
+ context=context) or 8
+ else: # dtTo.date() == dToday
+ shift_times = self.pool.get(
+ 'hr.schedule.detail').scheduled_begin_end_times(cr, uid,
+ employee_id,
+ contract_id,
+ dtS,
+ context=context)
+ if len(shift_times) > 0:
+ for dtStart, dtEnd in shift_times:
+ if dtLvEnd > dtStart:
+ dt = (dtLvEnd > dtEnd) and dtEnd or dtLvEnd
+ hours += float(
+ (dt - dtStart).seconds / 60) / 60.0
+ else:
+ hours = sched_tpl_obj.get_hours_by_weekday(
+ cr, uid, sched_tpl_id, d.weekday(),
+ context=context) or 8
+
+ return code, hours
+
+ # Copied from addons/hr_payroll so that we can override worked days calculation to
+ # handle Overtime and absence
+ #
+ 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
+ """
+
+ sched_tpl_obj = self.pool.get('hr.schedule.template')
+ sched_obj = self.pool.get('hr.schedule')
+ sched_detail_obj = self.pool.get('hr.schedule.detail')
+ ot_obj = self.pool.get('hr.policy.ot')
+ presence_obj = self.pool.get('hr.policy.presence')
+ absence_obj = self.pool.get('hr.policy.absence')
+ holiday_obj = self.pool.get('hr.holidays.public')
+
+ day_from = datetime.strptime(date_from, "%Y-%m-%d").date()
+ day_to = datetime.strptime(date_to, "%Y-%m-%d").date()
+ nb_of_days = (day_to - day_from).days + 1
+
+ # Initialize list of public holidays. We only need to calculate it once during
+ # the lifetime of this object so attach it directly to it.
+ #
+ try:
+ public_holidays_list = self._mtm_public_holidays_list
+ except AttributeError:
+ self._mtm_public_holidays_list = self.holidays_list_init(
+ cr, uid, day_from, day_to,
+ context=context)
+ public_holidays_list = self._mtm_public_holidays_list
+
+ def get_ot_policies(policy_group_id, day, data):
+
+ if data == None or not data['_reuse']:
+ data = {
+ 'policy': None,
+ 'daily': None,
+ 'restday2': None,
+ 'restday': None,
+ 'weekly': None,
+ 'holiday': None,
+ '_reuse': False,
+ }
+ elif data['_reuse']:
+ return data
+
+ ot_policy = self._get_ot_policy(policy_group_id, day)
+ daily_ot = ot_policy and len(
+ ot_obj.daily_codes(cr, uid, ot_policy.id, context=context)) > 0 or None
+ restday2_ot = ot_policy and len(ot_obj.restday2_codes(
+ cr, uid, ot_policy.id, context=context)) > 0 or None
+ restday_ot = ot_policy and len(ot_obj.restday_codes(
+ cr, uid, ot_policy.id, context=context)) > 0 or None
+ weekly_ot = ot_policy and len(
+ ot_obj.weekly_codes(cr, uid, ot_policy.id, context=context)) > 0 or None
+ holiday_ot = ot_policy and len(ot_obj.holiday_codes(
+ cr, uid, ot_policy.id, context=context)) > 0 or None
+
+ data['policy'] = ot_policy
+ data['daily'] = daily_ot
+ data['restday2'] = restday2_ot
+ data['restday'] = restday_ot
+ data['weekly'] = weekly_ot
+ data['holiday'] = holiday_ot
+ return data
+
+ def get_absence_policies(policy_group_id, day, data):
+
+ if data == None or not data['_reuse']:
+ data = {
+ 'policy': None,
+ '_reuse': False,
+ }
+ elif data['_reuse']:
+ return data
+
+ absence_policy = self._get_absence_policy(policy_group_id, day)
+
+ data['policy'] = absence_policy
+ return data
+
+ def get_presence_policies(policy_group_id, day, data):
+
+ if data == None or not data['_reuse']:
+ data = {
+ 'policy': None,
+ '_reuse': False,
+ }
+ elif data['_reuse']:
+ return data
+
+ policy = self._get_presence_policy(policy_group_id, day)
+
+ data['policy'] = policy
+ return data
+
+ res = []
+ for contract in self.pool.get('hr.contract').browse(cr, uid, contract_ids, context=context):
+
+ worked_hours_in_week = 0
+
+ # Initialize list of leave's taken by the employee during the month
+ leaves_list = self.leaves_list_init(
+ cr, uid, contract.employee_id.id,
+ day_from, day_to, contract.pps_id.tz, context=context)
+
+ # Get default set of rest days for this employee/contract
+ contract_rest_days = sched_tpl_obj.get_rest_days(cr, uid,
+ contract.schedule_template_id.id,
+ context=context)
+
+ # Initialize dictionary of dates in this payslip and the hours the
+ # employee was scheduled to work on each
+ sched_hours_dict = sched_detail_obj.scheduled_begin_end_times_range(
+ cr, uid,
+ contract.employee_id.id,
+ contract.id,
+ day_from, day_to,
+ context=context)
+
+ # Initialize dictionary of hours worked per day
+ working_hours_dict = self.attendance_dict_init(
+ cr, uid, contract, day_from, day_to,
+ context=None)
+
+ # Short-circuit:
+ # If the policy for the first day is the same as the one for the
+ # last day assume that it will also be the same for the days in
+ # between, and reuse the same policy instead of checking for every day.
+ #
+ ot_data = None
+ data2 = None
+ ot_data = get_ot_policies(
+ contract.policy_group_id, day_from, ot_data)
+ data2 = get_ot_policies(contract.policy_group_id, day_to, data2)
+ if (ot_data['policy'] and data2['policy']) and ot_data['policy'].id == data2['policy'].id:
+ ot_data['_reuse'] = True
+
+ absence_data = None
+ data2 = None
+ absence_data = get_absence_policies(
+ contract.policy_group_id, day_from, absence_data)
+ data2 = get_absence_policies(
+ contract.policy_group_id, day_to, data2)
+ if (absence_data['policy'] and data2['policy']) and absence_data['policy'].id == data2['policy'].id:
+ absence_data['_reuse'] = True
+
+ presence_data = None
+ data2 = None
+ presence_data = get_presence_policies(
+ contract.policy_group_id, day_from, presence_data)
+ data2 = get_presence_policies(
+ contract.policy_group_id, day_to, data2)
+ if (presence_data['policy'] and data2['policy']) and presence_data['policy'].id == data2['policy'].id:
+ presence_data['_reuse'] = True
+
+ # Calculate the number of days worked in the last week of
+ # the previous month. Necessary to calculate Weekly Rest Day OT.
+ #
+ lsd = last_X_days()
+ att_obj = self.pool.get('hr.attendance')
+ att_ids = []
+ if len(lsd.arr) == 0:
+ d = day_from - timedelta(days=6)
+ while d < day_from:
+ att_ids = att_obj.search(cr, uid, [
+ ('employee_id', '=', contract.employee_id.id),
+ ('day', '=', d.strftime(
+ '%Y-%m-%d')),
+ ],
+ order='name',
+ # XXX - necessary to keep
+ # order: in,out,in,out,...
+ context=context)
+ if len(att_ids) > 1:
+ lsd.push(True)
+ else:
+ lsd.push(False)
+ d += timedelta(days=1)
+
+ attendances = {
+ 'MAX': {
+ 'name': _("Maximum Possible Working Hours"),
+ 'sequence': 1,
+ 'code': 'MAX',
+ 'number_of_days': 0.0,
+ 'number_of_hours': 0.0,
+ 'contract_id': contract.id,
+ },
+ }
+ leaves = {}
+ att_obj = self.pool.get('hr.attendance')
+ awol_code = False
+ import logging
+ _l = logging.getLogger(__name__)
+ for day in range(0, nb_of_days):
+ dtDateTime = datetime.strptime(
+ (day_from + timedelta(days=day)).strftime('%Y-%m-%d'), '%Y-%m-%d')
+ rest_days = contract_rest_days
+ normal_working_hours = 0
+
+ # Get Presence data
+ #
+ presence_data = get_presence_policies(
+ contract.policy_group_id, dtDateTime.date(), presence_data)
+ presence_policy = presence_data['policy']
+ presence_codes = presence_policy and presence_obj.get_codes(
+ cr, uid, presence_policy.id, context=context) or []
+ presence_sequence = 2
+
+ for pcode, pname, ptype, prate, pduration in presence_codes:
+ if attendances.get(pcode, False):
+ continue
+ if ptype == 'normal':
+ normal_working_hours += float(pduration) / 60.0
+ attendances[pcode] = {
+ 'name': pname,
+ 'code': pcode,
+ 'sequence': presence_sequence,
+ 'number_of_days': 0.0,
+ 'number_of_hours': 0.0,
+ 'rate': prate,
+ 'contract_id': contract.id,
+ }
+ presence_sequence += 1
+
+ # Get OT data
+ #
+ ot_data = get_ot_policies(
+ contract.policy_group_id, dtDateTime.date(), ot_data)
+ ot_policy = ot_data['policy']
+ daily_ot = ot_data['daily']
+ restday2_ot = ot_data['restday2']
+ restday_ot = ot_data['restday']
+ weekly_ot = ot_data['weekly']
+ ot_codes = ot_policy and ot_obj.get_codes(
+ cr, uid, ot_policy.id, context=context) or []
+ ot_sequence = 3
+
+ for otcode, otname, ottype, otrate in ot_codes:
+ if attendances.get(otcode, False):
+ continue
+ attendances[otcode] = {
+ 'name': otname,
+ 'code': otcode,
+ 'sequence': ot_sequence,
+ 'number_of_days': 0.0,
+ 'number_of_hours': 0.0,
+ 'rate': otrate,
+ 'contract_id': contract.id,
+ }
+ ot_sequence += 1
+
+ # Get Absence data
+ #
+ absence_data = get_absence_policies(
+ contract.policy_group_id, dtDateTime.date(), absence_data)
+ absence_policy = absence_data['policy']
+ absence_codes = absence_policy and absence_obj.get_codes(
+ cr, uid, absence_policy.id, context=context) or []
+ absence_sequence = 50
+
+ for abcode, abname, abtype, abrate, useawol in absence_codes:
+ if leaves.get(abcode, False):
+ continue
+ if useawol:
+ awol_code = abcode
+ if abtype == 'unpaid':
+ abrate = 0
+ elif abtype == 'dock':
+ abrate = -abrate
+ leaves[abcode] = {
+ 'name': abname,
+ 'code': abcode,
+ 'sequence': absence_sequence,
+ 'number_of_days': 0.0,
+ 'number_of_hours': 0.0,
+ 'rate': abrate,
+ 'contract_id': contract.id,
+ }
+ absence_sequence += 1
+
+ # For Leave related computations:
+ # actual_rest_days: days that are rest days in schedule that was actualy used
+ # scheduled_hours: nominal number of full-time hours for the working day. If
+ # the employee is scheduled for this day we use those hours. If
+ # not we try to determine the hours he/she would have worked
+ # based on the schedule template attached to the contract.
+ #
+ actual_rest_days = sched_obj.get_rest_days(
+ cr, uid, contract.employee_id.id,
+ dtDateTime, context=context)
+ scheduled_hours = sched_detail_obj.scheduled_hours_on_day_from_range(
+ dtDateTime.date(),
+ sched_hours_dict)
+
+ # If the calculated rest days and actual rest days differ, use
+ # actual rest days
+ if actual_rest_days != None and len(rest_days) != len(actual_rest_days):
+ rest_days = actual_rest_days
+ elif actual_rest_days != None:
+ for d in actual_rest_days:
+ if d not in rest_days:
+ rest_days = actual_rest_days
+ break
+
+ if scheduled_hours == 0 and dtDateTime.weekday() not in rest_days:
+ scheduled_hours = sched_tpl_obj.get_hours_by_weekday(
+ cr, uid, contract.schedule_template_id.id,
+ dtDateTime.weekday(
+ ),
+ context=context)
+
+ # Actual number of hours worked on the day. Based on attendance
+ # records.
+ working_hours_on_day = self.attendance_dict_hours_on_day(
+ dtDateTime.date(), working_hours_dict)
+
+ # Is today a holiday?
+ public_holiday = self.holidays_list_contains(
+ dtDateTime.date(), public_holidays_list)
+
+ # Keep count of the number of hours worked during the week for
+ # weekly OT
+ if dtDateTime.weekday() == contract.pps_id.ot_week_startday:
+ worked_hours_in_week = working_hours_on_day
+ else:
+ worked_hours_in_week += working_hours_on_day
+
+ push_lsd = True
+ if working_hours_on_day:
+ done = False
+
+ if public_holiday:
+ _hours, push_lsd = self._book_holiday_hours(
+ cr, uid, contract, presence_policy, ot_policy, attendances,
+ holiday_obj, dtDateTime, rest_days, lsd,
+ working_hours_on_day, context=context)
+ if _hours == 0:
+ done = True
+ else:
+ working_hours_on_day = _hours
+
+ if not done and restday2_ot:
+ _hours, push_lsd = self._book_restday_hours(
+ cr, uid, contract, presence_policy, ot_policy,
+ attendances, dtDateTime, rest_days, lsd,
+ working_hours_on_day, context=context)
+ if _hours == 0:
+ done = True
+ else:
+ working_hours_on_day = _hours
+
+ if not done and restday_ot:
+ _hours, push_lsd = self._book_weekly_restday_hours(
+ cr, uid, contract, presence_policy, ot_policy,
+ attendances, dtDateTime, rest_days, lsd,
+ working_hours_on_day, context=context)
+ if _hours == 0:
+ done = True
+ else:
+ working_hours_on_day = _hours
+
+ if not done and weekly_ot:
+ for line in ot_policy.line_ids:
+ if line.type == 'weekly' and (not line.weekly_working_days or line.weekly_working_days == 0):
+ _active_after = float(line.active_after) / 60.0
+ if worked_hours_in_week > _active_after:
+ if worked_hours_in_week - _active_after > working_hours_on_day:
+ attendances[line.code][
+ 'number_of_hours'] += working_hours_on_day
+ else:
+ attendances[line.code][
+ 'number_of_hours'] += worked_hours_in_week - _active_after
+ attendances[line.code][
+ 'number_of_days'] += 1.0
+ done = True
+
+ if not done and daily_ot:
+
+ # Do the OT between specified times (partial OT) first, so that it
+ # doesn't get double-counted in the regular OT.
+ #
+ partial_hr = 0
+ hours_after_ot = working_hours_on_day
+ for line in ot_policy.line_ids:
+ active_after_hrs = float(line.active_after) / 60.0
+ if line.type == 'daily' and working_hours_on_day > active_after_hrs and line.active_start_time:
+ partial_hr = att_obj.partial_hours_on_day(
+ cr, uid, contract,
+ dtDateTime, active_after_hrs,
+ line.active_start_time,
+ line.active_end_time,
+ line.tz,
+ punches_list=self.attendance_dict_list(
+ working_hours_dict),
+ context=context)
+ if partial_hr > 0:
+ attendances[line.code][
+ 'number_of_hours'] += partial_hr
+ attendances[line.code][
+ 'number_of_days'] += 1.0
+ hours_after_ot -= partial_hr
+
+ for line in ot_policy.line_ids:
+ active_after_hrs = float(line.active_after) / 60.0
+ if line.type == 'daily' and hours_after_ot > active_after_hrs and not line.active_start_time:
+ attendances[line.code][
+ 'number_of_hours'] += hours_after_ot - (float(line.active_after) / 60.0)
+ attendances[line.code]['number_of_days'] += 1.0
+
+ if not done:
+ for line in presence_policy.line_ids:
+ if line.type == 'normal':
+ normal_hours = self._get_applied_time(
+ working_hours_on_day,
+ line.active_after,
+ line.duration)
+ attendances[line.code][
+ 'number_of_hours'] += normal_hours
+ attendances[line.code]['number_of_days'] += 1.0
+ done = True
+ _l.warning('nh: %s', normal_hours)
+ _l.warning('att: %s', attendances[line.code])
+
+ if push_lsd:
+ lsd.push(True)
+ else:
+ lsd.push(False)
+
+ leave_type, leave_hours = self.leaves_list_get_hours(
+ cr, uid, contract.employee_id.id,
+ contract.id, contract.schedule_template_id.id,
+ day_from +
+ timedelta(
+ days=day),
+ leaves_list, context=context)
+ if leave_type and (working_hours_on_day or scheduled_hours > 0 or dtDateTime.weekday() not in rest_days):
+ if leave_type in leaves:
+ leaves[leave_type]['number_of_days'] += 1.0
+ leaves[leave_type]['number_of_hours'] += (
+ leave_hours > scheduled_hours) and scheduled_hours or leave_hours
+ else:
+ leaves[leave_type] = {
+ 'name': leave_type,
+ 'sequence': 8,
+ 'code': leave_type,
+ 'number_of_days': 1.0,
+ 'number_of_hours': (leave_hours > scheduled_hours) and scheduled_hours or leave_hours,
+ 'contract_id': contract.id,
+ }
+ elif awol_code and (scheduled_hours > 0 and working_hours_on_day < scheduled_hours) and not public_holiday:
+ hours_diff = scheduled_hours - working_hours_on_day
+ leaves[awol_code]['number_of_days'] += 1.0
+ leaves[awol_code]['number_of_hours'] += hours_diff
+
+ # Calculate total possible working hours in the month
+ if dtDateTime.weekday() not in rest_days:
+ attendances['MAX'][
+ 'number_of_hours'] += normal_working_hours
+ attendances['MAX']['number_of_days'] += 1
+
+ leaves = [value for key, value in leaves.items()]
+ attendances = [value for key, value in attendances.items()]
+ res += attendances + leaves
+ return res
+
+ def _partial_period_factor(self, payslip, contract):
+
+ dpsFrom = datetime.strptime(payslip.date_from, OE_DATEFORMAT).date()
+ dpsTo = datetime.strptime(payslip.date_to, OE_DATEFORMAT).date()
+ dcStart = datetime.strptime(contract.date_start, OE_DATEFORMAT).date()
+ dcEnd = False
+ if (contract.date_end):
+ dcEnd = datetime.strptime(contract.date_end, OE_DATEFORMAT).date()
+
+ # both start and end of contract are out of the bounds of the payslip
+ if dcStart <= dpsFrom and (not dcEnd or dcEnd >= dpsTo):
+ return 1
+
+ # One or both start and end of contract are within the bounds of the payslip
+ #
+ nocontract_days = 0
+ if dcStart > dpsFrom:
+ nocontract_days += (dcStart - dpsFrom).days
+ if dcEnd and dcEnd < dpsTo:
+ nocontract_days += (dpsTo - dcEnd).days
+
+ total_days = (dpsTo - dpsFrom).days + 1
+ contract_days = total_days - nocontract_days
+ return (float(contract_days) / float(total_days))
+
+ def get_utilities_dict(self, cr, uid, contract, payslip, context=None):
+
+ res = {}
+ if not contract or not payslip:
+ return res
+
+ # Calculate percentage of pay period in which contract lies
+ res.update(
+ {'PPF': {'amount': self._partial_period_factor(payslip, contract)}})
+
+ # Calculate net amount of previous payslip
+ imd_obj = self.pool.get('ir.model.data')
+ ps_obj = self.pool.get('hr.payslip')
+ ps_ids = ps_obj.search(
+ cr, uid, [('employee_id', '=', contract.employee_id.id)],
+ order='date_from', context=context)
+ res.update({'PREVPS': {'exists': 0,
+ 'net': 0}
+ })
+ if ps_ids > 0:
+ # Get database ID of Net salary category
+ res_model, net_id = imd_obj.get_object_reference(
+ cr, uid, 'hr_payroll', 'NET')
+
+ ps = ps_obj.browse(cr, uid, ps_ids[-1], context=context)
+ res['PREVPS']['exists'] = 1
+ total = 0
+ for line in ps.line_ids:
+ if line.salary_rule_id.category_id.id == net_id:
+ total += line.total
+ res['PREVPS']['net'] = total
+
+ return res
+
+ # XXX
+ # Copied (almost) verbatim from hr_payroll for the sole purpose of adding the 'utils'
+ # object to localdict.
+ #
+ def get_payslip_lines(self, cr, uid, contract_ids, payslip_id, context):
+ def _sum_salary_rule_category(localdict, category, amount):
+ if category.parent_id:
+ localdict = _sum_salary_rule_category(
+ localdict, category.parent_id, amount)
+ if category.code in localdict['categories'].dict:
+ localdict['categories'].dict[category.code] = localdict[
+ 'categories'].dict[category.code] + amount
+ else:
+ localdict['categories'].dict[category.code] = 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')
+ obj_rule = self.pool.get('hr.salary.rule')
+ 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
+
+ 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
+ rule_ids = self.pool.get('hr.payroll.structure').get_all_rules(
+ cr, uid, structure_ids, context=context)
+ # run the rules by sequence
+ sorted_rule_ids = [
+ id for id, sequence in sorted(rule_ids, key=lambda x:x[1])]
+
+ for contract in self.pool.get('hr.contract').browse(cr, uid, contract_ids, context=context):
+ temp_dict = {}
+ utils_dict = self.get_utilities_dict(
+ cr, uid, contract, payslip, context=context)
+ for k, v in utils_dict.iteritems():
+ k_obj = BrowsableObject(
+ self.pool, cr, uid, payslip.employee_id.id, v)
+ temp_dict.update({k: k_obj})
+ utils_obj = BrowsableObject(
+ self.pool, cr, uid, payslip.employee_id.id, temp_dict)
+ employee = contract.employee_id
+ localdict.update(
+ {'employee': employee, 'contract': contract, 'utils': utils_obj})
+ 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, qty, rate = 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
+ tot_rule = amount * qty * rate / 100.0
+ localdict[rule.code] = tot_rule
+ rules[rule.code] = rule
+ # sum the amount for its salary category
+ localdict = _sum_salary_rule_category(
+ localdict, rule.category_id, tot_rule - previous_amount)
+ # create/overwrite the rule in the temporary results
+ result_dict[key] = {
+ 'salary_rule_id': rule.id,
+ 'contract_id': contract.id,
+ 'name': rule.name,
+ 'code': rule.code,
+ 'category_id': rule.category_id.id,
+ 'sequence': rule.sequence,
+ 'appears_on_payslip': rule.appears_on_payslip,
+ 'condition_select': rule.condition_select,
+ 'condition_python': rule.condition_python,
+ 'condition_range': rule.condition_range,
+ 'condition_range_min': rule.condition_range_min,
+ 'condition_range_max': rule.condition_range_max,
+ 'amount_select': rule.amount_select,
+ 'amount_fix': rule.amount_fix,
+ 'amount_python_compute': rule.amount_python_compute,
+ 'amount_percentage': rule.amount_percentage,
+ 'amount_percentage_base': rule.amount_percentage_base,
+ 'register_id': rule.register_id.id,
+ 'amount': amount,
+ 'employee_id': contract.employee_id.id,
+ 'quantity': qty,
+ 'rate': rate,
+ }
+ else:
+ # blacklist this rule and its children
+ blacklist += [id for id, seq in self.pool.get(
+ 'hr.salary.rule')._recursive_search_of_rules(cr, uid, [rule], context=context)]
+
+ result = [value for code, value in result_dict.items()]
+ return result
+
+hr_payslip()
+
+
+class hr_payslip_line(osv.osv):
+
+ _name = 'hr.payslip.line'
+ _inherit = 'hr.payslip.line'
+
+ _columns = {
+ 'amount': fields.float('Amount', digits_compute=dp.get_precision('Intermediate Payroll')),
+ }
+
+
+class hr_attendance(osv.osv):
+
+ _name = 'hr.attendance'
+ _inherit = 'hr.attendance'
+
+ def _calculate_rollover(self, utcdt, rollover_hours):
+
+ # XXX - assume time part of utcdt is already set to midnight
+ return utcdt + timedelta(hours=int(rollover_hours))
+
+ def punches_list_init(self, cr, uid, employee_id, pps_template, dFrom, dTo, context=None):
+ '''Returns a dict containing a key for each day in range dFrom - dToday and a
+ corresponding tuple containing two list: in punches, and the corresponding out punches'''
+
+ res = []
+
+ # Convert datetime to tz aware datetime according to tz in pay period schedule,
+ # then to UTC, and then to naive datetime for comparison with values in db.
+ #
+ # Also, includue records 48 hours previous to and 48 hours after the desired
+ # dates so that any requests for rollover, sessions, etc are can be satisfied
+ #
+ dtFrom = datetime.strptime(
+ dFrom.strftime(OE_DATEFORMAT) + ' 00:00:00', OE_DATETIMEFORMAT)
+ dtFrom += timedelta(hours=-48)
+ dtTo = datetime.strptime(
+ dTo.strftime(OE_DATEFORMAT) + ' 00:00:00', OE_DATETIMEFORMAT)
+ dtTo += timedelta(hours=+48)
+ utcdtFrom = timezone(pps_template.tz).localize(
+ dtFrom, is_dst=False).astimezone(utc)
+ utcdtTo = timezone(pps_template.tz).localize(
+ dtTo, is_dst=False).astimezone(utc)
+ utcdtDay = utcdtFrom
+ utcdtDayEnd = utcdtTo + timedelta(days=+1, seconds=-1)
+ ndtDay = utcdtDay.replace(tzinfo=None)
+ ndtDayEnd = utcdtDayEnd.replace(tzinfo=None)
+
+ ids = self.search(cr, uid, [('employee_id', '=', employee_id),
+ '&', (
+ 'name', '>=', ndtDay.strftime(OE_DATETIMEFORMAT)),
+ ('name', '<=', ndtDayEnd.strftime(OE_DATETIMEFORMAT))],
+ order='name', context=context)
+
+ for a in self.browse(cr, uid, ids, context=context):
+ res.append((a.action, a.name))
+
+ return res
+
+ def punches_list_search(self, cr, uid, ndtFrom, ndtTo, punches_list, context=None):
+
+ res = []
+ for action, name in punches_list:
+ ndtName = datetime.strptime(name, OE_DATETIMEFORMAT)
+ if ndtName >= ndtFrom and ndtName <= ndtTo:
+ res.append((action, name))
+ return res
+
+ def _get_normalized_punches(self, cr, uid, employee_id, pps_template, dDay, punches_list, context=None):
+ '''Returns a tuple containing two lists: in punches, and corresponding out punches'''
+
+ #
+ # We assume that:
+ # - No dangling sign-in or sign-out
+ #
+
+ # Convert datetime to tz aware datetime according to tz in pay period schedule,
+ # then to UTC, and then to naive datetime for comparison with values in db.
+ #
+ dt = datetime.strptime(
+ dDay.strftime(OE_DATEFORMAT) + ' 00:00:00', OE_DATETIMEFORMAT)
+ utcdtDay = timezone(pps_template.tz).localize(
+ dt, is_dst=False).astimezone(utc)
+ utcdtDayEnd = utcdtDay + timedelta(days=+1, seconds=-1)
+ ndtDay = utcdtDay.replace(tzinfo=None)
+ ndtDayEnd = utcdtDayEnd.replace(tzinfo=None)
+ my_list = self.punches_list_search(
+ cr, uid, ndtDay, ndtDayEnd, punches_list, context=context)
+ if len(my_list) == 0:
+ return [], []
+
+ # We are assuming attendances are normalized: (in, out, in, out, ...)
+ sin = []
+ sout = []
+ for action, name in my_list:
+ if action == 'sign_in':
+ sin.append(name)
+ elif action == 'sign_out':
+ sout.append(name)
+
+ if len(sin) == 0 and len(sout) == 0:
+ return [], []
+
+ # CHECKS AT THE START OF THE DAY
+ # Remove sessions that would have been included in yesterday's
+ # attendance.
+
+ # We may have a a session *FROM YESTERDAY* that crossed-over into
+ # today. If it is greater than the maximum continuous hours allowed into
+ # the next day (as configured in the pay period schedule), then count
+ # only the difference between the actual and the maximum continuous
+ # hours.
+ #
+ dtRollover = (self._calculate_rollover(
+ utcdtDay, pps_template.ot_max_rollover_hours)).replace(tzinfo=None)
+ if (len(sout) - len(sin)) == 0:
+
+ if len(sout) > 0:
+ dtSout = datetime.strptime(sout[0], OE_DATETIMEFORMAT)
+ dtSin = datetime.strptime(sin[0], OE_DATETIMEFORMAT)
+ if dtSout > dtRollover and (dtSout < dtSin):
+ sin = [dtRollover.strftime(OE_DATETIMEFORMAT)] + sin
+ elif dtSout < dtSin:
+ sout = sout[1:]
+ # There may be another session that starts within the
+ # rollover period
+ if dtSin < dtRollover and float((dtSin - dtSout).seconds) / 60.0 >= pps_template.ot_max_rollover_gap:
+ sin = sin[1:]
+ sout = sout[1:]
+ else:
+ return [], []
+ elif (len(sout) - len(sin)) == 1:
+ dtSout = datetime.strptime(sout[0], OE_DATETIMEFORMAT)
+ if dtSout > dtRollover:
+ sin = [dtRollover.strftime(OE_DATETIMEFORMAT)] + sin
+ else:
+ sout = sout[1:]
+ # There may be another session that starts within the rollover
+ # period
+ dtSin = False
+ if len(sin) > 0:
+ dtSin = datetime.strptime(sin[0], OE_DATETIMEFORMAT)
+ if dtSin and dtSin < dtRollover and float((dtSin - dtSout).seconds) / 60.0 >= pps_template.ot_max_rollover_gap:
+ sin = sin[1:]
+ sout = sout[1:]
+
+ # If the first sign-in was within the rollover gap *AT* midnight check to
+ # see if there are any sessions within the rollover gap before it.
+ #
+ if len(sout) > 0:
+ ndtSin = datetime.strptime(sin[0], OE_DATETIMEFORMAT)
+ if (ndtSin - timedelta(minutes=pps_template.ot_max_rollover_gap)) <= ndtDay:
+ my_list4 = self.punches_list_search(
+ cr, uid, ndtDay + timedelta(hours=-24),
+ ndtDay + timedelta(seconds=-1), punches_list, context=context)
+ if len(my_list4) > 0:
+ if (my_list4[-1].action == 'sign_out'):
+ ndtSout = datetime.strptime(
+ my_list4[-1].name, OE_DATETIMEFORMAT)
+ if (ndtSin <= ndtSout + timedelta(minutes=pps_template.ot_max_rollover_gap)):
+ sin = sin[1:]
+ sout = sout[1:]
+
+ # CHECKS AT THE END OF THE DAY
+ # Include sessions from tomorrow that should be included in today's
+ # attendance.
+
+ # We may have a session that crosses the midnight boundary. If so, add it to today's
+ # session.
+ #
+ dtRollover = (self._calculate_rollover(ndtDay + timedelta(days=1),
+ pps_template.ot_max_rollover_hours)).replace(tzinfo=None)
+ if (len(sin) - len(sout)) == 1:
+
+ my_list2 = self.punches_list_search(
+ cr, uid, ndtDayEnd + timedelta(seconds=+1),
+ ndtDayEnd + timedelta(days=1), punches_list, context=context)
+ if len(my_list2) == 0:
+ name = self.pool.get('hr.employee').read(
+ cr, uid, employee_id, ['name'])['name']
+ raise osv.except_osv(_('Attendance Error!'),
+ _('There is not a final sign-out record for %s on %s') % (name, dDay))
+
+ action, name = my_list2[0]
+ if action == 'sign_out':
+ dtSout = datetime.strptime(name, OE_DATETIMEFORMAT)
+ if dtSout > dtRollover:
+ sout.append(dtRollover.strftime(OE_DATETIMEFORMAT))
+ else:
+ sout.append(name)
+ # There may be another session within the OT max. rollover
+ # gap
+ if len(my_list2) > 2 and my_list2[1][0] == 'sign_in':
+ dtSin = datetime.strptime(name, OE_DATETIMEFORMAT)
+ if float((dtSin - dtSout).seconds) / 60.0 < pps_template.ot_max_rollover_gap:
+ sin.append(my_list2[1][1])
+ sout.append(my_list2[2][1])
+
+ else:
+ name = self.pool.get('hr.employee').read(
+ cr, uid, employee_id, ['name'])['name']
+ raise osv.except_osv(_('Attendance Error!'),
+ _('There is a sign-in with no corresponding sign-out for %s on %s') % (name, dDay))
+
+ # If the last sign-out was within the rollover gap *BEFORE* midnight check to
+ # see if there are any sessions within the rollover gap after it.
+ #
+ if len(sout) > 0:
+ ndtSout = datetime.strptime(sout[-1], OE_DATETIMEFORMAT)
+ if (ndtDayEnd - timedelta(minutes=pps_template.ot_max_rollover_gap)) <= ndtSout:
+ my_list3 = self.punches_list_search(
+ cr, uid, ndtDayEnd + timedelta(seconds=+1),
+ ndtDayEnd + timedelta(hours=+24), punches_list, context=context)
+ if len(my_list3) > 0:
+ action, name = my_list3[0]
+ ndtSin = datetime.strptime(name, OE_DATETIMEFORMAT)
+ if (ndtSin <= ndtSout + timedelta(minutes=pps_template.ot_max_rollover_gap)) and action == 'sign_in':
+ sin.append(name)
+ sout.append(my_list3[1][1])
+
+ return sin, sout
+
+ def _on_day(self, cr, uid, contract, dDay, punches_list=None, context=None):
+ '''Return two lists: the first is sign-in times, and the second is corresponding sign-outs.'''
+
+ if punches_list == None:
+ punches_list = self.punches_list_init(
+ cr, uid, contract.employee_id.id, contract.pps_id,
+ dDay, dDay, context)
+
+ sin, sout = self._get_normalized_punches(
+ cr, uid, contract.employee_id.id, contract.pps_id,
+ dDay, punches_list, context=context)
+ if len(sin) != len(sout):
+ raise osv.except_osv(
+ _('Number of Sign-in and Sign-out records do not match!'),
+ _('Employee: %s\nSign-in(s): %s\nSign-out(s): %s') % (contract.employee_id.name, sin, sout))
+
+ return sin, sout
+
+ def punch_names_on_day(self, cr, uid, contract, dDay, punches_list=None, context=None):
+ '''Return a list of tuples containing in and corresponding out punches for the day.'''
+
+ sin, sout = self._on_day(
+ cr, uid, contract, dDay, punches_list=punches_list, context=context)
+
+ res = []
+ for i in range(0, len(sin)):
+ res.append((sin[i], sout[i]))
+
+ return res
+
+ def punch_ids_on_day(self, cr, uid, contract, dDay, punches_list=None, context=None):
+ '''Return a list of database ids of punches for the day.'''
+
+ sin, sout = self._on_day(
+ cr, uid, contract, dDay, punches_list=punches_list, context=context)
+
+ names = []
+ for i in range(0, len(sin)):
+ names.append(sin[i])
+ names.append(sout[i])
+
+ return self.search(
+ cr, uid, [('employee_id', '=', contract.employee_id.id),
+ ('name', 'in', names)],
+ order='name', context=context)
+
+ def total_hours_on_day(self, cr, uid, contract, dDay, punches_list=None, context=None):
+ '''Calculate the number of hours worked on specified date.'''
+
+ sin, sout = self._on_day(
+ cr, uid, contract, dDay, punches_list=punches_list, context=context)
+
+ worked_hours = 0
+ for i in range(0, len(sin)):
+ start = datetime.strptime(sin[i], '%Y-%m-%d %H:%M:%S')
+ end = datetime.strptime(sout[i], '%Y-%m-%d %H:%M:%S')
+ worked_hours += float((end - start).seconds) / 60.0 / 60.0
+
+ return worked_hours
+
+ def partial_hours_on_day(
+ self, cr, uid, contract, dtDay, active_after, begin, stop, tz,
+ punches_list=None, context=None):
+ '''Calculate the number of hours worked between begin and stop hours, but
+ after active_after hours past the beginning of the first sign-in on specified date.'''
+
+ # Since OpenERP stores datetime in db as UTC, but in naive format we have to do
+ # the following to compare our partial time to the time in db:
+ # 1. Make our partial time into a naive datetime
+ # 2. Localize the naive datetime to the timezone specified by our caller
+ # 3. Convert our localized datetime to UTC
+ # 4. Convert our UTC datetime back into naive datetime format
+ #
+ dtBegin = datetime.strptime(
+ dtDay.strftime(OE_DATEFORMAT) + ' ' + begin + ':00', OE_DATETIMEFORMAT)
+ dtStop = datetime.strptime(
+ dtDay.strftime(OE_DATEFORMAT) + ' ' + stop + ':00', OE_DATETIMEFORMAT)
+ if dtStop <= dtBegin:
+ dtStop += timedelta(days=1)
+ utcdtBegin = timezone(tz).localize(
+ dtBegin, is_dst=False).astimezone(utc)
+ utcdtStop = timezone(tz).localize(dtStop, is_dst=False).astimezone(utc)
+ dtBegin = utcdtBegin.replace(tzinfo=None)
+ dtStop = utcdtStop.replace(tzinfo=None)
+
+ if punches_list == None:
+ punches_list = self.punches_list_init(
+ cr, uid, contract.employee_id.id, contract.pps_id,
+ dtDay.date(), dtDay.date(), context)
+ sin, sout = self._get_normalized_punches(
+ cr, uid, contract.employee_id.id, contract.pps_id,
+ dtDay.date(), punches_list, context=context)
+
+ worked_hours = 0
+ lead_hours = 0
+ for i in range(0, len(sin)):
+ start = datetime.strptime(sin[i], '%Y-%m-%d %H:%M:%S')
+ end = datetime.strptime(sout[i], '%Y-%m-%d %H:%M:%S')
+ if worked_hours == 0 and end <= dtBegin:
+ lead_hours += float((end - start).seconds) / 60.0 / 60.0
+ elif worked_hours == 0 and end > dtBegin:
+ if start < dtBegin:
+ lead_hours += float(
+ (dtBegin - start).seconds) / 60.0 / 60.0
+ start = dtBegin
+ if end > dtStop:
+ end = dtStop
+ worked_hours = float((end - start).seconds) / 60.0 / 60.0
+ elif worked_hours > 0 and start < dtStop:
+ if end > dtStop:
+ end = dtStop
+ worked_hours += float((end - start).seconds) / 60.0 / 60.0
+
+ if worked_hours == 0:
+ return 0
+ elif lead_hours >= active_after:
+ return worked_hours
+
+ return max(0, (worked_hours + lead_hours) - active_after)
+
+
+class hr_contract(osv.osv):
+
+ _name = 'hr.contract'
+ _inherit = 'hr.contract'
+
+ def _hourly(self, cr, uid, ids, field_name, args, context=None):
+
+ res = {}
+ for contract in self.browse(cr, uid, ids, context=context):
+ rate = 0.0
+ if contract.wage_type == 'hourly':
+ rate = contract.wage
+ elif contract.wage_type == 'daily':
+ rate = contract.wage / 8.0
+ elif contract.wage_type == 'salary':
+ rate = contract.wage / 26.0 / 8.0
+ res[contract.id] = rate
+ return res
+
+ def _daily(self, cr, uid, ids, field_name, args, context=None):
+
+ res = {}
+ for contract in self.browse(cr, uid, ids, context=context):
+ rate = 0.0
+ if contract.wage_type == 'hourly':
+ rate = contract.wage * 8.0
+ elif contract.wage_type == 'daily':
+ rate = contract.wage
+ elif contract.wage_type == 'salary':
+ rate = contract.wage / 26.0
+ res[contract.id] = rate
+ return res
+
+ def _monthly(self, cr, uid, ids, field_name, args, context=None):
+
+ res = {}
+ for contract in self.browse(cr, uid, ids, context=context):
+ rate = 0.0
+ if contract.wage_type == 'hourly':
+ rate = contract.wage * 8.0 * 26.0
+ elif contract.wage_type == 'daily':
+ rate = contract.wage * 26
+ elif contract.wage_type == 'salary':
+ rate = contract.wage
+ res[contract.id] = rate
+ return res
+
+ _columns = {
+ 'wage_type': fields.selection((('hourly', 'Hourly'),
+ ('daily', 'Daily'),
+ ('salary', 'Salary')),
+ 'Wage Type', required=True),
+ 'wage_hourly': fields.function(_hourly, type='float', digits_compute=dp.get_precision('Intermediate Payroll'), string='Hourly Wages'),
+ 'wage_daily': fields.function(_daily, type='float', digits_compute=dp.get_precision('Intermediate Payroll'), string='Daily Wages'),
+ 'wage_monthly': fields.function(_monthly, type='float', digits_compute=dp.get_precision('Intermediate Payroll'), string='Monthly Wages'),
+ }
+
+ _defaults = {
+ 'wage_type': 'salary',
+ }
+
+
+class hr_salary_rule(osv.Model):
+
+ _name = 'hr.salary.rule'
+ _inherit = 'hr.salary.rule'
+
+ _columns = {
+ 'quantity': fields.char('Quantity', size=512, 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."),
+ }
+
+
+class hr_payslip_worked_days(osv.Model):
+
+ _name = 'hr.payslip.worked_days'
+ _inherit = 'hr.payslip.worked_days'
+
+ _columns = {
+ 'rate': fields.float('Rate', required=True),
+ }
+
+ _defaults = {
+ 'rate': 0.0,
+ }
=== added file 'hr_payroll_extension/hr_payroll_view.xml'
--- hr_payroll_extension/hr_payroll_view.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_extension/hr_payroll_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="view_salary_rule_form" model="ir.ui.view">
+ <field name="name">hr.salary.rule.form.inherit</field>
+ <field name="model">hr.salary.rule</field>
+ <field name="inherit_id" ref="hr_payroll.hr_salary_rule_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//field[@name='quantity']" position="replace">
+ <field name="quantity" attrs="{'required':[('amount_select','!=','code')]}"/><newline/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_payroll_extension/src'
=== added directory 'hr_payroll_extension/src/static'
=== added directory 'hr_payroll_extension/src/static/src'
=== added directory 'hr_payroll_extension/src/static/src/img'
=== added file 'hr_payroll_extension/src/static/src/img/icon.png'
Binary files hr_payroll_extension/src/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and hr_payroll_extension/src/static/src/img/icon.png 2013-09-27 21:15:53 +0000 differ
=== added directory 'hr_payroll_extension/static'
=== added directory 'hr_payroll_extension/static/src'
=== added directory 'hr_payroll_extension/static/src/img'
=== added file 'hr_payroll_extension/static/src/img/icon.png'
Binary files hr_payroll_extension/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and hr_payroll_extension/static/src/img/icon.png 2013-09-27 21:15:53 +0000 differ
=== added directory 'hr_payroll_period'
=== added file 'hr_payroll_period/__init__.py'
--- hr_payroll_period/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,24 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import hr_attendance
+from . import hr_payroll_period
+from . import wizard
=== added file 'hr_payroll_period/__openerp__.py'
--- hr_payroll_period/__openerp__.py 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/__openerp__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,68 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+{
+ 'name': 'Payroll Period',
+ 'version': '1.0',
+ 'category': 'Generic Modules/Human Resources',
+ 'description': """
+Easy Payroll Management
+=======================
+This module implements a more formal payroll cycle. This cycle is based on payroll
+period schedules configured by the user. An end-of-pay-period wizard guides the
+HR officer or manager through the payroll process. For each payroll period a specific set
+of criteria have to be met in order to proceed to the next stage of the process. For
+example:
+ - Attendance records are complete
+ """,
+ 'author': 'Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>',
+ 'website': 'http://miketelahun.wordpress.com',
+ 'depends': [
+ 'hr_contract',
+ 'hr_contract_init',
+ 'hr_employee_state',
+ 'hr_payroll',
+ 'hr_payroll_register',
+ 'hr_payslip_amendment',
+ 'hr_public_holidays',
+ 'hr_schedule',
+ 'hr_security',
+ ],
+ 'init_xml': [
+ ],
+ 'update_xml': [
+ 'security/ir.model.access.csv',
+ 'security/ir_rule.xml',
+ 'data/hr_payroll_period_data.xml',
+ 'data/mail_group_data.xml',
+ 'wizard/payroll_period_end_view.xml',
+ 'hr_payroll_period_view.xml',
+ 'hr_attendance_workflow.xml',
+ 'hr_payroll_period_workflow.xml',
+ 'hr_payroll_period_cron.xml',
+ ],
+ 'test': [
+ ],
+ 'demo_xml': [
+ ],
+ 'installable': True,
+ 'active': False,
+}
=== added directory 'hr_payroll_period/data'
=== added file 'hr_payroll_period/data/hr_payroll_period_data.xml'
--- hr_payroll_period/data/hr_payroll_period_data.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/data/hr_payroll_period_data.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data noupdate="1">
+
+ <!-- Messaging / Chatter -->
+
+ <record id="mt_state_open" model="mail.message.subtype">
+ <field name="name">Create</field>
+ <field name="res_model">hr.payroll.period</field>
+ <field name="description">Payroll Period opened</field>
+ </record>
+ <record id="mt_state_end" model="mail.message.subtype">
+ <field name="name">Ended</field>
+ <field name="res_model">hr.payroll.period</field>
+ <field name="description">Begin end-of-period processing</field>
+ </record>
+ <record id="mt_state_lock" model="mail.message.subtype">
+ <field name="name">Locked</field>
+ <field name="res_model">hr.payroll.period</field>
+ <field name="description">Payroll Period has been locked</field>
+ </record>
+ <record id="mt_state_generate" model="mail.message.subtype">
+ <field name="name">Generate Payslips</field>
+ <field name="res_model">hr.payroll.period</field>
+ <field name="description">Pay Slip generation has begun</field>
+ </record>
+ <record id="mt_state_payment" model="mail.message.subtype">
+ <field name="name">Payment</field>
+ <field name="res_model">hr.payroll.period</field>
+ <field name="description">Payment has started</field>
+ </record>
+ <record id="mt_state_close" model="mail.message.subtype">
+ <field name="name">Closed</field>
+ <field name="res_model">hr.payroll.period</field>
+ <field name="description">Payroll Period closed</field>
+ </record>
+
+ <!-- Payroll Exception Rules -->
+
+ <record id="payslip_exception_third" model="hr.payslip.exception.rule">
+ <field name="name">Net Salary is less than 1/3 of Gross</field>
+ <field name="code">NET13GROSS</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.NET.amount < (categories.GROSS.amount / 3.0)</field>
+ <field name="sequence" eval="10"/>
+ <field name="severity">critical</field>
+ <field name="note">In some jurisdictions the employee's Net Salary cannot be less than 1/3 of Gross.</field>
+ </record>
+
+ <record id="payslip_exception_negative" model="hr.payslip.exception.rule">
+ <field name="name">Net Salary is negative</field>
+ <field name="code">NETNEG</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.NET.amount <= -0.01</field>
+ <field name="sequence" eval="15"/>
+ <field name="severity">critical</field>
+ <field name="note">An employee can never have a negative salary.</field>
+ </record>
+
+ <record id="payslip_exception_zero" model="hr.payslip.exception.rule">
+ <field name="name">Net Salary is Zero</field>
+ <field name="code">NETZERO</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = categories.NET.amount > -0.01 and categories.NET.amount < 0.01</field>
+ <field name="sequence" eval="20"/>
+ <field name="severity">low</field>
+ </record>
+
+ <record id="payslip_exception_ot2basic_ratio" model="hr.payslip.exception.rule">
+ <field name="name">OverTime / Basic Salary ratio threshold exceeded</field>
+ <field name="code">OTBSR</field>
+ <field name="condition_select">python</field>
+ <field name="condition_python">result = (categories.OT / categories.BASIC.amount) > 0.5</field>
+ <field name="sequence" eval="25"/>
+ <field name="severity">high</field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_payroll_period/data/mail_group_data.xml'
--- hr_payroll_period/data/mail_group_data.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/data/mail_group_data.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data noupdate="1">
+
+ <!-- Main HR Working Group -->
+ <record model="mail.group" id="mail_group_hr_main">
+ <field name="name">HR Information</field>
+ <field name="description">Main point contact for HR related events, actions and discussions.</field>
+ </record>
+
+ </data>
+</openerp>
\ No newline at end of file
=== added file 'hr_payroll_period/hr_attendance.py'
--- hr_payroll_period/hr_attendance.py 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/hr_attendance.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,99 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from openerp.tools.translate import _
+from osv import fields, osv
+
+
+class hr_attendance(osv.osv):
+
+ _name = 'hr.attendance'
+ _inherit = 'hr.attendance'
+
+ _columns = {
+ 'state': fields.selection((
+ ('draft', 'Unverified'), (
+ 'verified', 'Verified'), ('locked', 'Locked'),
+ ), 'State', required=True, readonly=True),
+ }
+
+ _defaults = {
+ 'state': 'draft',
+ }
+
+ def is_locked(self, cr, uid, employee_id, utcdt_str, context=None):
+
+ res = False
+ pp_obj = self.pool.get('hr.payroll.period')
+ ee_data = self.pool.get('hr.employee').read(cr, uid, employee_id,
+ ['contract_ids'], context=context)
+ pp_ids = pp_obj.search(cr, uid, [
+ ('state', 'in', [
+ 'locked', 'generate', 'payment', 'closed']),
+ '&', ('date_start', '<=', utcdt_str),
+ ('date_end', '>=', utcdt_str),
+ ], context=context)
+ for pp in pp_obj.browse(cr, uid, pp_ids, context=context):
+ pp_contract_ids = [c.id for c in pp.schedule_id.contract_ids]
+ for c_id in ee_data['contract_ids']:
+ if c_id in pp_contract_ids:
+ res = True
+ break
+ if res == True:
+ break
+
+ return res
+
+ def create(self, cr, uid, vals, context=None):
+
+ if self.is_locked(cr, uid, vals['employee_id'], vals['name'], context=context):
+ ee_data = self.pool.get(
+ 'hr.employee').read(cr, uid, vals['employee_id'], ['name'],
+ context=context)
+ raise osv.except_osv(_('The period is Locked!'),
+ _('You may not add an attendace record to a locked period.\nEmployee: %s\nTime: %s') % (ee_data['name'], vals['name']))
+
+ return super(hr_attendance, self).create(cr, uid, vals, context=context)
+
+ def unlink(self, cr, uid, ids, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ for punch in self.browse(cr, uid, ids, context=context):
+ if punch.state in ['verified', 'locked']:
+ raise osv.except_osv(_('The Record cannot be deleted!'),
+ _('You may not delete a record that is in a %s state:\nEmployee: %s, Date: %s, Action: %s') % (punch.state, punch.employee_id.name, punch.name, punch.action))
+
+ return super(hr_attendance, self).unlink(cr, uid, ids, context=context)
+
+ def write(self, cr, uid, ids, vals, context=None):
+
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+
+ for punch in self.browse(cr, uid, ids, context=context):
+ if punch.state in ['verified', 'locked'] and (vals.get('name', False) or vals.get('action', False) or vals.get('employee_id', False)):
+ raise osv.except_osv(
+ _('The record cannot be modified!'), _('You may not write to a record that is in a %s state:\nEmployee: %s, Date: %s, Action: %s') %
+ (punch.state, punch.employee_id.name, punch.name, punch.action))
+
+ return super(hr_attendance, self).write(cr, uid, ids, vals, context=context)
=== added file 'hr_payroll_period/hr_attendance_workflow.xml'
--- hr_payroll_period/hr_attendance_workflow.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/hr_attendance_workflow.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Workflow Definition -->
+ <record id="wkf_attendance" model="workflow">
+ <field name="name">hr.attendance.basic</field>
+ <field name="osv">hr.attendance</field>
+ <field name="on_create">True</field>
+ </record>
+
+ <!-- Workflow Activities (Stages) -->
+
+ <record id="act_draft" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_attendance"/>
+ <field name="name">draft</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'draft'})</field>
+ <field name="flow_start">True</field>
+ </record>
+
+ <record id="act_verified" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_attendance"/>
+ <field name="name">verified</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'verified'})</field>
+ </record>
+
+ <record id="act_locked" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_attendance"/>
+ <field name="name">locked</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'locked'})</field>
+ </record>
+
+ <!-- Workflow Transitions -->
+
+ <record id="draft2locked" model="workflow.transition">
+ <field name="act_from" ref="act_draft"/>
+ <field name="act_to" ref="act_locked"/>
+ <field name="signal">signal_lock</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="locked2draft" model="workflow.transition">
+ <field name="act_from" ref="act_locked"/>
+ <field name="act_to" ref="act_draft"/>
+ <field name="signal">signal_unlock</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_payroll_period/hr_payroll_period.py'
--- hr_payroll_period/hr_payroll_period.py 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/hr_payroll_period.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,808 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2011,2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import calendar
+import netsvc
+import time
+from datetime import date, datetime, timedelta
+from dateutil.relativedelta import relativedelta
+from openerp.tools.safe_eval import safe_eval as eval
+from openerp.tools.translate import _
+from osv import fields, osv
+from pytz import common_timezones, timezone, utc
+from tools.translate import _
+import logging
+_logger = logging.getLogger(__name__)
+
+# Obtained from: http://stackoverflow.com/questions/4130922/how-to-increment-datetime-month-in-python
+#
+
+
+def add_months(sourcedate, months):
+ month = sourcedate.month - 1 + months
+ year = sourcedate.year + month / 12
+ month = month % 12 + 1
+ day = min(sourcedate.day, calendar.monthrange(year, month)[1])
+ return datetime(year, month, day)
+
+
+class hr_payroll_period(osv.osv):
+
+ _name = 'hr.payroll.period'
+
+ _inherit = ['mail.thread', 'ir.needaction_mixin']
+
+ _columns = {
+ 'name': fields.char('Description', size=256, required=True),
+ 'schedule_id': fields.many2one('hr.payroll.period.schedule', 'Payroll Period Schedule',
+ required=True),
+ 'date_start': fields.datetime('Start Date', required=True),
+ 'date_end': fields.datetime('End Date', required=True),
+ 'register_id': fields.many2one('hr.payroll.register', 'Payroll Register', readonly=True,
+ states={
+ 'generate': [('readonly', False)]}),
+ 'state': fields.selection([('open', 'Open'),
+ ('ended', 'End of Period Processing'),
+ ('locked', 'Locked'),
+ ('generate', 'Generating Payslips'),
+ ('payment', 'Payment'),
+ ('closed', 'Closed')],
+ 'State', select=True, readonly=True),
+ }
+
+ _order = "date_start, name desc"
+
+ _defaults = {
+ 'state': 'open',
+ }
+
+ _track = {
+ 'state': {
+ 'hr_payroll_period.mt_state_open': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open',
+ 'hr_payroll_period.mt_state_end': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'ended',
+ 'hr_payroll_period.mt_state_lock': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'locked',
+ 'hr_payroll_period.mt_state_generate': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'generate',
+ 'hr_payroll_period.mt_state_payment': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'payment',
+ 'hr_payroll_period.mt_state_close': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'closed',
+ },
+ }
+
+ def _needaction_domain_get(self, cr, uid, context=None):
+
+ users_obj = self.pool.get('res.users')
+ domain = []
+
+ if users_obj.has_group(cr, uid, 'hr_security.group_payroll_manager'):
+ domain = [('state', 'not in', ['open', 'closed'])]
+ return domain
+
+ return False
+
+ def is_ended(self, cr, uid, period_id, context=None):
+
+ #
+ # XXX - Someone who cares about DST should update this code to handle it.
+ #
+
+ flag = False
+ if period_id:
+ utc_tz = timezone('UTC')
+ utcDtNow = utc_tz.localize(datetime.now(), is_dst=False)
+ period = self.browse(cr, uid, period_id, context=context)
+ if period:
+ dtEnd = datetime.strptime(period.date_end, '%Y-%m-%d %H:%M:%S')
+ utcDtEnd = utc_tz.localize(dtEnd, is_dst=False)
+ if utcDtNow > utcDtEnd + timedelta(minutes=(period.schedule_id.ot_max_rollover_hours * 60)):
+ flag = True
+ return flag
+
+ def try_signal_end_period(self, cr, uid, context=None):
+ """Method called, usually by cron, to transition any payroll periods
+ that are past their end date.
+ """
+
+ #
+ # XXX - Someone who cares about DST should update this code to handle it.
+ #
+
+ utc_tz = timezone('UTC')
+ utcDtNow = utc_tz.localize(datetime.now(), is_dst=False)
+ period_ids = self.search(cr, uid, [
+ ('state', 'in', ['open']),
+ ('date_end', '<=', utcDtNow.strftime(
+ '%Y-%m-%d %H:%M:%S')),
+ ], context=context)
+ if len(period_ids) == 0:
+ return
+
+ wf_service = netsvc.LocalService('workflow')
+ for pid in period_ids:
+ wf_service.trg_validate(
+ uid, 'hr.payroll.period', pid, 'end_period', cr)
+
+ def set_state_ended(self, cr, uid, ids, context=None):
+
+ #
+ # XXX - Someone who cares about DST should update this code to handle it.
+ #
+
+ wf_service = netsvc.LocalService('workflow')
+ attendance_obj = self.pool.get('hr.attendance')
+ detail_obj = self.pool.get('hr.schedule.detail')
+ holiday_obj = self.pool.get('hr.holidays')
+ for period in self.browse(cr, uid, ids, context=context):
+ utc_tz = timezone('UTC')
+ dt = datetime.strptime(period.date_start, '%Y-%m-%d %H:%M:%S')
+ utcDtStart = utc_tz.localize(dt, is_dst=False)
+ dt = datetime.strptime(period.date_end, '%Y-%m-%d %H:%M:%S')
+ utcDtEnd = utc_tz.localize(dt, is_dst=False)
+ if period.state in ['locked', 'generate']:
+ for contract in period.schedule_id.contract_ids:
+ employee = contract.employee_id
+
+ # Unlock attendance
+ punch_ids = attendance_obj.search(cr, uid, [
+ ('employee_id', '=', employee.id),
+ '&', (
+ 'name', '>=', utcDtStart.strftime('%Y-%m-%d %H:%M:%S')),
+ ('name', '<=', utcDtEnd.strftime(
+ '%Y-%m-%d %H:%M:%S')),
+ ], order='name', context=context)
+ [wf_service.trg_validate(uid, 'hr.attendance', pid, 'signal_unlock', cr)
+ for pid in punch_ids]
+
+ # Unlock schedules
+ detail_ids = detail_obj.search(cr, uid, [
+ ('schedule_id.employee_id', '=', employee.id),
+ '&', (
+ 'date_start', '>=', utcDtStart.strftime('%Y-%m-%d %H:%M:%S')),
+ ('date_start', '<=', utcDtEnd.strftime(
+ '%Y-%m-%d %H:%M:%S')),
+ ], order='date_start', context=context)
+ [wf_service.trg_validate(uid, 'hr.schedule.detail', did, 'signal_unlock', cr)
+ for did in detail_ids]
+
+ # Unlock holidays/leaves that end in the current period
+ holiday_ids = holiday_obj.search(cr, uid, [
+ ('employee_id', '=', employee.id),
+ '&', (
+ 'date_to', '>=', utcDtStart.strftime('%Y-%m-%d %H:%M:%S')),
+ ('date_to', '<=', utcDtEnd.strftime(
+ '%Y-%m-%d %H:%M:%S')),
+ ],
+ context=context)
+ for hid in holiday_ids:
+ holiday_obj.write(
+ cr, uid, [hid], {
+ 'payroll_period_state': 'unlocked'},
+ context=context)
+
+ self.write(cr, uid, period.id, {'state': 'ended'}, context=context)
+
+ return True
+
+ def set_state_locked(self, cr, uid, ids, context=None):
+
+ #
+ # XXX - Someone who cares about DST should update this code to handle it.
+ #
+
+ wkf_service = netsvc.LocalService('workflow')
+ attendance_obj = self.pool.get('hr.attendance')
+ detail_obj = self.pool.get('hr.schedule.detail')
+ holiday_obj = self.pool.get('hr.holidays')
+ for period in self.browse(cr, uid, ids, context=context):
+ utc_tz = timezone('UTC')
+ dt = datetime.strptime(period.date_start, '%Y-%m-%d %H:%M:%S')
+ utcDtStart = utc_tz.localize(dt, is_dst=False)
+ dt = datetime.strptime(period.date_end, '%Y-%m-%d %H:%M:%S')
+ utcDtEnd = utc_tz.localize(dt, is_dst=False)
+ for contract in period.schedule_id.contract_ids:
+ employee = contract.employee_id
+
+ # Lock sign-in and sign-out attendance records
+ punch_ids = attendance_obj.search(cr, uid, [
+ ('employee_id', '=', employee.id),
+ '&', (
+ 'name', '>=', utcDtStart.strftime('%Y-%m-%d %H:%M:%S')),
+ ('name', '<=', utcDtEnd.strftime(
+ '%Y-%m-%d %H:%M:%S')),
+ ], order='name', context=context)
+ for pid in punch_ids:
+ wkf_service.trg_validate(
+ uid, 'hr.attendance', pid, 'signal_lock', cr)
+
+ # Lock schedules
+ detail_ids = detail_obj.search(cr, uid, [
+ ('schedule_id.employee_id', '=', employee.id),
+ '&', (
+ 'date_start', '>=', utcDtStart.strftime('%Y-%m-%d %H:%M:%S')),
+ ('date_start', '<=', utcDtEnd.strftime(
+ '%Y-%m-%d %H:%M:%S')),
+ ], order='date_start', context=context)
+ for did in detail_ids:
+ wkf_service.trg_validate(
+ uid, 'hr.schedule.detail', did, 'signal_lock', cr)
+
+ # Lock holidays/leaves that end in the current period
+ holiday_ids = holiday_obj.search(cr, uid, [
+ ('employee_id', '=', employee.id),
+ '&', (
+ 'date_to', '>=', utcDtStart.strftime('%Y-%m-%d %H:%M:%S')),
+ ('date_to', '<=', utcDtEnd.strftime(
+ '%Y-%m-%d %H:%M:%S')),
+ ],
+ context=context)
+ for hid in holiday_ids:
+ holiday_obj.write(
+ cr, uid, [hid], {'payroll_period_state': 'locked'},
+ context=context)
+
+ self.write(cr, uid, period.id, {
+ 'state': 'locked'}, context=context)
+
+ return True
+
+ def set_state_closed(self, cr, uid, ids, context=None):
+
+ return self.write(cr, uid, ids, {'state': 'closed'}, context=context)
+
+
+class hr_payperiod_schedule(osv.osv):
+
+ _name = 'hr.payroll.period.schedule'
+
+ def _tz_list(self, cr, uid, context=None):
+
+ res = tuple()
+ for name in common_timezones:
+ res += ((name, name),)
+ return res
+
+ _columns = {
+ 'name': fields.char('Description', size=256, required=True),
+ 'tz': fields.selection(_tz_list, 'Time Zone', required=True),
+ 'paydate_biz_day': fields.boolean('Pay Date on a Business Day'),
+ 'ot_week_startday': fields.selection([
+ ('0', _('Sunday')),
+ ('1', _('Monday')),
+ ('2', _('Tuesday')),
+ ('3', _('Wednesday')),
+ ('4', _('Thursday')),
+ ('5', _('Friday')),
+ ('6', _('Saturday')),
+ ],
+ 'Start of Week', required=True),
+ 'ot_max_rollover_hours': fields.integer('OT Max. Continous Hours', required=True),
+ 'ot_max_rollover_gap': fields.integer('OT Max. Continuous Hours Gap (in Min.)', required=True),
+ 'type': fields.selection([
+ ('manual', 'Manual'),
+ ('monthly', 'Monthly'),
+ ],
+ 'Type', required=True),
+ 'mo_firstday': fields.selection([
+ ('1', '1'), ('2', '2'), ('3', '3'), (
+ '4', '4'), ('5', '5'), ('6', '6'), ('7', '7'),
+ ('8', '8'), ('9', '9'), ('10', '10'), ('11', '11'), (
+ '12', '12'), ('13', '13'), ('14', '14'),
+ ('15', '15'), ('16', '16'), ('17', '17'), (
+ '18', '18'), ('19', '19'), ('20', '20'), ('21', '21'),
+ ('22', '22'), ('23', '23'), ('24', '24'), (
+ '25', '25'), ('26', '26'), ('27', '27'), ('28', '28'),
+ ('29', '29'), (
+ '30', '30'), ('31', '31'),
+ ],
+ 'Start Day'),
+ 'mo_paydate': fields.selection([
+ ('1', '1'), ('2', '2'), ('3', '3'), (
+ '4', '4'), ('5', '5'), ('6', '6'), ('7', '7'),
+ ('8', '8'), ('9', '9'), ('10', '10'), ('11', '11'), (
+ '12', '12'), ('13', '13'), ('14', '14'),
+ ('15', '15'), ('16', '16'), ('17', '17'), (
+ '18', '18'), ('19', '19'), ('20', '20'), ('21', '21'),
+ ('22', '22'), ('23', '23'), ('24', '24'), (
+ '25', '25'), ('26', '26'), ('27', '27'), ('28', '28'),
+ ('29', '29'), (
+ '30', '30'), ('31', '31'),
+ ],
+ 'Pay Date'),
+ 'contract_ids': fields.one2many('hr.contract', 'pps_id', 'Contracts'),
+ 'pay_period_ids': fields.one2many('hr.payroll.period', 'schedule_id', 'Pay Periods'),
+ 'initial_period_date': fields.date('Initial Period Start Date'),
+ 'active': fields.boolean('Active'),
+ }
+
+ _defaults = {
+ 'ot_week_startday': '1',
+ 'ot_max_rollover_hours': 6,
+ 'ot_max_rollover_gap': 60,
+ 'mo_firstday': '1',
+ 'mo_paydate': '3',
+ 'type': 'monthly',
+ 'active': True,
+ }
+
+ def _check_initial_date(self, cr, uid, ids, context=None):
+
+ for obj in self.browse(cr, uid, ids, context=context):
+ if obj.type in ['monthly'] and not obj.initial_period_date:
+ return False
+
+ return True
+
+ _constraints = [
+ (_check_initial_date,
+ 'You must supply an Initial Period Start Date', ['type']),
+ ]
+
+ def add_pay_period(self, cr, uid, ids, context=None):
+
+ def get_period_year(dt):
+
+ month_number = 0
+ year_number = 0
+ if dt.day < 15:
+ month_number = dt.month
+ year_number = dt.year
+ else:
+ dtTmp = add_months(dt, 1)
+ month_number = dtTmp.month
+ year_number = dtTmp.year
+ return month_number, year_number
+
+ #
+ # XXX - Someone who cares about DST should update this code to handle it.
+ #
+
+ schedule_obj = self.pool.get('hr.payroll.period.schedule')
+
+ data = None
+ latest = None
+ for sched in schedule_obj.browse(cr, uid, ids, context=context):
+ for p in sched.pay_period_ids:
+ if not latest:
+ latest = p
+ continue
+ if datetime.strptime(p.date_end, '%Y-%m-%d %H:%M:%S') > datetime.strptime(latest.date_end, '%Y-%m-%d %H:%M:%S'):
+ latest = p
+ local_tz = timezone(sched.tz)
+ if not latest:
+ # No pay periods have been defined yet for this pay period
+ # schedule.
+ if sched.type == 'monthly':
+ dtStart = datetime.strptime(
+ sched.initial_period_date, '%Y-%m-%d')
+ if dtStart.day > int(sched.mo_firstday):
+ dtStart = add_months(dtStart, 1)
+ dtStart = datetime(
+ dtStart.year, dtStart.month, int(sched.mo_firstday), 0, 0, 0)
+ elif dtStart.day < int(sched.mo_firstday):
+ dtStart = datetime(
+ dtStart.year, dtStart.month, int(sched.mo_firstday), 0, 0, 0)
+ else:
+ dtStart = datetime(
+ dtStart.year, dtStart.month, dtStart.day, 0, 0, 0)
+ dtEnd = add_months(dtStart, 1) - timedelta(days=1)
+ dtEnd = datetime(
+ dtEnd.year, dtEnd.month, dtEnd.day, 23, 59, 59)
+ month_number, year_number = get_period_year(dtStart)
+
+ # Convert from time zone of punches to UTC for storage
+ utcStart = local_tz.localize(dtStart, is_dst=None)
+ utcStart = utcStart.astimezone(utc)
+ utcEnd = local_tz.localize(dtEnd, is_dst=None)
+ utcEnd = utcEnd.astimezone(utc)
+
+ data = {
+ 'name': 'Pay Period ' + str(month_number) + '/' + str(year_number),
+ 'schedule_id': sched.id,
+ 'date_start': utcStart.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date_end': utcEnd.strftime('%Y-%m-%d %H:%M:%S'),
+ }
+ else:
+ if sched.type == 'monthly':
+ # Convert from UTC to timezone of punches
+ utcStart = datetime.strptime(
+ latest.date_end, '%Y-%m-%d %H:%M:%S')
+ utc_tz = timezone('UTC')
+ utcStart = utc_tz.localize(utcStart, is_dst=None)
+ utcStart += timedelta(seconds=1)
+ dtStart = utcStart.astimezone(local_tz)
+
+ # Roll forward to the next pay period start and end times
+ dtEnd = add_months(dtStart, 1) - timedelta(days=1)
+ dtEnd = datetime(
+ dtEnd.year, dtEnd.month, dtEnd.day, 23, 59, 59)
+ month_number, year_number = get_period_year(dtStart)
+
+ # Convert from time zone of punches to UTC for storage
+ utcStart = dtStart.astimezone(utc_tz)
+ utcEnd = local_tz.localize(dtEnd, is_dst=None)
+ utcEnd = utcEnd.astimezone(utc)
+
+ data = {
+ 'name': 'Pay Period ' + str(month_number) + '/' + str(year_number),
+ 'schedule_id': sched.id,
+ 'date_start': utcStart.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date_end': utcEnd.strftime('%Y-%m-%d %H:%M:%S'),
+ }
+ if data != None:
+ schedule_obj.write(cr, uid, sched.id, {
+ 'pay_period_ids': [(0, 0, data)]}, context=context)
+
+ def _get_latest_period(self, cr, uid, sched_id, context=None):
+
+ sched = self.browse(cr, uid, sched_id, context=context)
+ latest_period = False
+ for period in sched.pay_period_ids:
+ if not latest_period:
+ latest_period = period
+ continue
+ if datetime.strptime(period.date_end, '%Y-%m-%d %H:%M:%S') > datetime.strptime(latest_period.date_end, '%Y-%m-%d %H:%M:%S'):
+ latest_period = period
+
+ return latest_period
+
+ def try_create_new_period(self, cr, uid, context=None):
+ '''Try and create pay periods for up to 3 months from now.'''
+
+ #
+ # XXX - Someone who cares about DST should update this code to handle it.
+ #
+
+ dtNow = datetime.now()
+ utc_tz = timezone('UTC')
+ sched_obj = self.pool.get('hr.payroll.period.schedule')
+ sched_ids = sched_obj.search(cr, uid, [], context=context)
+ for sched in sched_obj.browse(cr, uid, sched_ids, context=context):
+ if sched.type == 'monthly':
+ firstday = sched.mo_firstday
+ else:
+ continue
+ dtNow = datetime.strptime(
+ dtNow.strftime('%Y-%m-' + firstday + ' 00:00:00'), '%Y-%m-%d %H:%M:%S')
+ loclDTNow = timezone(sched.tz).localize(dtNow, is_dst=False)
+ utcDTFuture = loclDTNow.astimezone(
+ utc_tz) + relativedelta(months=+3)
+
+ if not sched.pay_period_ids:
+ self.add_pay_period(cr, uid, [sched.id], context=context)
+
+ latest_period = self._get_latest_period(
+ cr, uid, sched.id, context=context)
+ utcDTStart = utc_tz.localize(
+ datetime.strptime(latest_period.date_start, '%Y-%m-%d %H:%M:%S'), is_dst=False)
+ while utcDTFuture > utcDTStart:
+ self.add_pay_period(cr, uid, [sched.id], context=context)
+ latest_period = self._get_latest_period(
+ cr, uid, sched.id, context=context)
+ utcDTStart = utc_tz.localize(
+ datetime.strptime(latest_period.date_start, '%Y-%m-%d %H:%M:%S'), is_dst=False)
+
+
+class contract_init(osv.Model):
+
+ _inherit = 'hr.contract.init'
+
+ _columns = {
+ 'pay_sched_id': fields.many2one('hr.payroll.period.schedule', 'Payroll Period Schedule',
+ readonly=True, states={
+ 'draft': [('readonly', False)]}),
+ }
+
+
+class hr_contract(osv.osv):
+
+ _name = 'hr.contract'
+ _inherit = 'hr.contract'
+
+ _columns = {
+ 'pps_id': fields.many2one('hr.payroll.period.schedule', 'Payroll Period Schedule', required=True),
+ }
+
+ def _get_pay_sched(self, cr, uid, context=None):
+
+ res = False
+ init = self.get_latest_initial_values(cr, uid, context=context)
+ if init != None and init.pay_sched_id:
+ res = init.pay_sched_id.id
+ return res
+
+ _defaults = {
+ 'pps_id': _get_pay_sched,
+ }
+
+
+class hr_payslip(osv.osv):
+
+ _name = 'hr.payslip'
+ _inherit = 'hr.payslip'
+
+ _columns = {
+ 'exception_ids': fields.one2many('hr.payslip.exception', 'slip_id',
+ 'Exceptions', readonly=True),
+ }
+
+ def compute_sheet(self, cr, uid, ids, context=None):
+
+ super(hr_payslip, self).compute_sheet(cr, uid, ids, context=context)
+
+ 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
+
+ rule_obj = self.pool.get('hr.payslip.exception.rule')
+ rule_ids = rule_obj.search(
+ cr, uid, [('active', '=', True)], context=context)
+ rule_seq = []
+ for i in rule_ids:
+ data = rule_obj.read(cr, uid, i, ['sequence'], context=context)
+ rule_seq.append((i, data['sequence']))
+ sorted_rule_ids = [
+ id for id, sequence in sorted(rule_seq, key=lambda x:x[1])]
+
+ for payslip in self.browse(cr, uid, ids, context=context):
+ payslip_obj = Payslips(
+ self.pool, cr, uid, payslip.employee_id.id, payslip)
+
+ codes = []
+ categories = {}
+ for line in payslip.details_by_salary_rule_category:
+ if line.code not in codes:
+ categories[line.code] = line
+ codes.append(line.code)
+ categories_obj = BrowsableObject(
+ self.pool, cr, uid, payslip.employee_id.id, categories)
+
+ worked_days = {}
+ for line in payslip.worked_days_line_ids:
+ worked_days[line.code] = line
+ worked_days_obj = WorkedDays(
+ self.pool, cr, uid, payslip.employee_id.id, worked_days)
+
+ inputs = {}
+ for line in payslip.input_line_ids:
+ inputs[line.code] = line
+ input_obj = InputLine(
+ self.pool, cr, uid, payslip.employee_id.id, inputs)
+
+ temp_dict = {}
+ utils_dict = self.get_utilities_dict(
+ cr, uid, payslip.contract_id, payslip, context=context)
+ for k, v in utils_dict.iteritems():
+ k_obj = BrowsableObject(
+ self.pool, cr, uid, payslip.employee_id.id, v)
+ temp_dict.update({k: k_obj})
+ utils_obj = BrowsableObject(
+ self.pool, cr, uid, payslip.employee_id.id, temp_dict)
+
+ localdict = {'categories': categories_obj,
+ 'payslip': payslip_obj,
+ 'worked_days': worked_days_obj,
+ 'inputs': input_obj,
+ 'utils': utils_obj}
+ localdict['result'] = None
+
+ for rule in rule_obj.browse(cr, uid, sorted_rule_ids, context=context):
+ if rule_obj.satisfy_condition(cr, uid, rule.id, localdict, context=context):
+ val = {
+ 'name': rule.name,
+ 'slip_id': payslip.id,
+ 'rule_id': rule.id,
+ }
+ self.pool.get('hr.payslip.exception').create(
+ cr, uid, val, context=context)
+
+ return True
+
+
+class hr_payslip_exception(osv.osv):
+
+ _name = 'hr.payslip.exception'
+ _description = 'Payroll Exception'
+
+ _columns = {
+ 'name': fields.char('Name', size=256, required=True, readonly=True),
+ 'rule_id': fields.many2one('hr.payslip.exception.rule', 'Rule', ondelete='cascade', readonly=True),
+ 'slip_id': fields.many2one('hr.payslip', 'Pay Slip', ondelete='cascade', readonly=True),
+ 'severity': fields.related('rule_id', 'severity', type="char", string="Severity", store=True, readonly=True),
+ }
+
+# This is almost 100% lifted from hr_payroll/hr.salary.rule
+# I ommitted the parts I don't use.
+#
+
+
+class hr_payslip_exception_rule(osv.osv):
+
+ _name = 'hr.payslip.exception.rule'
+ _description = 'Rules describing pay slips in an abnormal state'
+
+ _columns = {
+ 'name': fields.char('Name', size=256, required=True),
+ 'code': fields.char('Code', size=64, required=True),
+ 'sequence': fields.integer('Sequence', required=True, help='Use to arrange calculation sequence', select=True),
+ 'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the rule without removing it."),
+ 'company_id': fields.many2one('res.company', 'Company'),
+ 'condition_select': fields.selection([('none', 'Always True'), ('python', 'Python Expression')], "Condition Based on", required=True),
+ 'condition_python': fields.text('Python Condition', readonly=False, help='The condition that triggers the exception.'),
+ 'severity': fields.selection((
+ ('low', 'Low'),
+ ('medium', 'Medium'),
+ ('high', 'High'),
+ ('critical', 'Critical'),
+ ), 'Severity', required=True),
+ 'note': fields.text('Description'),
+ }
+
+ _defaults = {
+ 'active': True,
+ 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.payslip.exception.rule', context=context),
+ 'sequence': 5,
+ 'severity': 'low',
+ 'condition_select': 'none',
+ 'condition_python':
+'''
+# Available variables:
+#----------------------
+# payslip: object containing the payslips
+# contract: hr.contract object
+# 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 = categories.GROSS.amount > categories.NET.amount''',
+ }
+
+ def satisfy_condition(self, cr, uid, rule_id, localdict, context=None):
+ """
+ @param rule_id: id of hr.payslip.exception.rule to be tested
+ @param contract_id: id of hr.contract to be tested
+ @return: returns True if the given rule match the condition for the given contract. Return False otherwise.
+ """
+ rule = self.browse(cr, uid, rule_id, context=context)
+
+ if rule.condition_select == 'none':
+ return True
+ else: # python code
+ try:
+ eval(rule.condition_python,
+ localdict, mode='exec', nocopy=True)
+ return 'result' in localdict and localdict['result'] or False
+ except:
+ raise osv.except_osv(
+ _('Error!'), _('Wrong python condition defined for payroll exception rule %s (%s).') % (rule.name, rule.code))
+
+
+class hr_payslip_amendment(osv.osv):
+
+ _name = 'hr.payslip.amendment'
+ _inherit = 'hr.payslip.amendment'
+
+ _columns = {
+ 'pay_period_id': fields.many2one('hr.payroll.period', 'Pay Period', domain=[('state', 'in', ['open', 'ended', 'locked', 'generate'])], required=False, readonly=True, states={'draft': [('readonly', False)], 'validate': [('required', True)], 'done': [('required', True)]}),
+ }
+
+
+class hr_holidays_status(osv.osv):
+
+ _name = 'hr.holidays.status'
+ _inherit = 'hr.holidays.status'
+
+ _columns = {
+ 'code': fields.char('Code', size=16, required=True),
+ }
+
+ _sql_constraints = [
+ ('code_unique', 'UNIQUE(code)', 'Codes for leave types must be unique!')]
+
+
+class hr_holidays(osv.Model):
+
+ _name = 'hr.holidays'
+ _inherit = 'hr.holidays'
+
+ _columns = {
+ 'payroll_period_state': fields.selection([('unlocked', 'Unlocked'), ('locked', 'Locked')],
+ 'Payroll Period State', readonly=True),
+ }
+
+ _defaults = {
+ 'payroll_period_state': 'unlocked',
+ }
+
+ def unlink(self, cr, uid, ids, context=None):
+ for h in self.browse(cr, uid, ids, context=context):
+ if h.payroll_period_state == 'locked':
+ raise osv.except_osv(_('Warning!'),
+ _('You cannot delete a leave which belongs to a payroll period that has been locked.'))
+ return super(hr_holidays, self).unlink(cr, uid, ids, context)
+
+ def write(self, cr, uid, ids, vals, context=None):
+ for h in self.browse(cr, uid, ids, context=context):
+ if h.payroll_period_state == 'locked' and not vals.get('payroll_period_state', False):
+ raise osv.except_osv(_('Warning!'),
+ _('You cannot modify a leave which belongs to a payroll period that has been locked.'))
+ return super(hr_holidays, self).write(cr, uid, ids, vals, context=context)
=== added file 'hr_payroll_period/hr_payroll_period_cron.xml'
--- hr_payroll_period/hr_payroll_period_cron.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/hr_payroll_period_cron.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record model="ir.cron" id="hr_payroll_period_ended_cron">
+ <field name="name">Payroll Periods Past End Date</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">hours</field>
+ <field name="numbercall">-1</field>
+ <field eval="(DateTime.now() + timedelta(minutes=60)).strftime('%Y-%m-%d %H:05:00')" name="nextcall"/>
+ <field eval="False" name="doall"/>
+ <field eval="'hr.payroll.period'" name="model"/>
+ <field eval="'try_signal_end_period'" name="function"/>
+ <field eval="'()'" name="args"/>
+ </record>
+
+ <record model="ir.cron" id="hr_payroll_period_create_cron">
+ <field name="name">Create New Payroll Periods</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">hours</field>
+ <field name="numbercall">-1</field>
+ <field eval="(DateTime.now() + timedelta(minutes=60)).strftime('%Y-%m-%d %H:05:00')" name="nextcall"/>
+ <field eval="False" name="doall"/>
+ <field eval="'hr.payroll.period.schedule'" name="model"/>
+ <field eval="'try_create_new_period'" name="function"/>
+ <field eval="'()'" name="args"/>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_payroll_period/hr_payroll_period_report.xml'
--- hr_payroll_period/hr_payroll_period_report.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/hr_payroll_period_report.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<openerp>
+ <data>
+
+ <report
+ auto="False"
+ id="payslip_report"
+ model="hr.payslip"
+ name="payslip"
+ rml="hr_payroll/report/report_payslip.rml"
+ string="Employee PaySlip" />
+
+ <report
+ auto="False"
+ id="payslip_details_report"
+ model="hr.payslip"
+ name="paylip.details"
+ rml="hr_payroll/report/report_payslip_details.rml"
+ string="PaySlip Details" />
+
+ <report
+ auto="False"
+ menu="False"
+ id="contribution_register"
+ model="hr.contribution.register"
+ name="contribution.register.lines"
+ rml="hr_payroll/report/report_contribution_register.rml"
+ string="PaySlip Lines By Conribution Register" />
+
+ </data>
+</openerp>
=== added file 'hr_payroll_period/hr_payroll_period_view.xml'
--- hr_payroll_period/hr_payroll_period_view.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/hr_payroll_period_view.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,389 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Submenu for advanced views -->
+ <menuitem name="Advanced"
+ id="menu_payroll_advanced"
+ parent="hr_payroll.menu_hr_root_payroll"
+ sequence="100" groups="base.group_hr_manager,hr_security.group_payroll_manager"/>
+
+ <!-- Move the following menu items from the payroll root menu to the advanced one -->
+ <menuitem action="hr_payroll.action_view_hr_payslip_form"
+ id="hr_payroll.menu_department_tree" parent="menu_payroll_advanced" groups="base.group_hr_manager,hr_security.group_payroll_manager" sequence="10"/>
+ <menuitem action="hr_payroll.action_hr_payslip_run_tree"
+ id="hr_payroll.menu_hr_payslip_run" parent="menu_payroll_advanced" groups="base.group_hr_manager,hr_security.group_payroll_manager" sequence="20"/>
+ <!-- Move 'Payroll Register' from hr_payroll_register -->
+ <menuitem action="hr_payroll_register.open_payroll_registers"
+ id="hr_payroll_register.menu_payroll_register" parent="menu_payroll_advanced" groups="base.group_hr_manager,hr_security.group_payroll_manager" sequence="30"/>
+
+ <!-- Payroll Period -->
+
+ <record id="view_payroll_period_tree" model="ir.ui.view">
+ <field name="name">hr.payroll.period.tree</field>
+ <field name="model">hr.payroll.period</field>
+ <field name="arch" type="xml">
+ <tree string="Payroll Periods">
+ <field name="name"/>
+ <field name="date_start"/>
+ <field name="date_end"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+ <record id="view_payroll_period_form" model="ir.ui.view">
+ <field name="name">hr.payroll.period.form</field>
+ <field name="model">hr.payroll.period</field>
+ <field name="arch" type="xml">
+ <form string="Payroll Period" version="7.0">
+ <header>
+ <button name="%(action_payroll_period_end)d" type="action" states="open,ended,locked,generate,payment" class="oe_highlight" string="End of Pay Period Wizard"/>
+ <field name="state" widget="statusbar"/>
+ </header>
+ <group>
+ <group>
+ <field name="schedule_id"/>
+ <label for="date_start" string="Interval"/>
+ <div>
+ <field name="date_start" nolabel="1" class="oe_inline"/> -
+ <field name="date_end" nolabel="1" class="oe_inline"/>
+ </div>
+ <field name="name"/>
+ <field name="register_id"/>
+ </group>
+ </group>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </field>
+ </record>
+ <record id="open_payroll_period_view" model="ir.actions.act_window">
+ <field name="name">Payroll Periods</field>
+ <field name="res_model">hr.payroll.period</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem action="open_payroll_period_view"
+ id="menu_payroll_period_view"
+ parent="hr_payroll.menu_hr_root_payroll"
+ groups="hr_security.group_payroll_user,base.group_hr_manager"
+ sequence="1"/>
+
+ <!-- End of Pay Period -->
+
+ <record id="action_pay_period_end" model="ir.actions.act_window">
+ <field name="name">End of Pay Period</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">hr.payroll.period</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="domain">[('state','in',['ended','locked','generate','payment'])]</field>
+ <field name="help" type="html">
+ <p>
+ There are no current pay periods that are past their end date.
+ </p>
+ </field>
+ </record>
+ <menuitem action="action_pay_period_end"
+ id="menu_pay_period_end"
+ parent="hr_payroll.menu_hr_root_payroll"
+ groups="hr_security.group_payroll_user,base.group_hr_manager"
+ sequence="2"/>
+
+ <!-- Payroll Period Schedule -->
+
+ <record id="view_payperiod_schedule_tree" model="ir.ui.view">
+ <field name="name">hr.payroll.period.schedule.tree</field>
+ <field name="model">hr.payroll.period.schedule</field>
+ <field name="arch" type="xml">
+ <tree string="Pay Period Schedules">
+ <field name="name"/>
+ <field name="type"/>
+ <field name="tz"/>
+ <field name="ot_week_startday"/>
+ <field name="ot_max_rollover_hours"/>
+ <field name="ot_max_rollover_gap"/>
+ <field name="active"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_payperiod_schedule_form" model="ir.ui.view">
+ <field name="name">hr.payroll.period.schedule.form</field>
+ <field name="model">hr.payroll.period.schedule</field>
+ <field name="arch" type="xml">
+ <form string="Pay Period Schedule" version="7.0">
+ <group>
+ <group>
+ <field name="name"/>
+ <field name="tz"/>
+ <field name="ot_week_startday"/>
+ <field name="ot_max_rollover_hours"/>
+ <field name="ot_max_rollover_gap"/>
+ <field name="paydate_biz_day"/>
+ </group>
+ <group>
+ <field name="type"/>
+ <field name="mo_firstday" attrs="{'invisible': [('type','!=','monthly')]}"/>
+ <field name="mo_paydate" attrs="{'invisible': [('type','!=','monthly')]}"/>
+ <newline/>
+ <field name="initial_period_date" attrs="{'invisible': [('type','!=','monthly')], 'required': [('type','=','monthly')]}"/>
+ <field name="active"/>
+ </group>
+ </group>
+ <group string="Pay Periods" colspan="4" col="1">
+ <button name="add_pay_period" type="object" string="Add"/>
+ <field name="pay_period_ids" nolabel="1"/>
+ </group>
+ <group string="Contracts" colspan="4" col="1">
+ <field name="contract_ids" nolabel="1"/>
+ </group>
+ </form>
+ </field>
+ </record>
+ <record id="open_payroll_period_schedule_view" model="ir.actions.act_window">
+ <field name="name">Payroll Period Schedules</field>
+ <field name="res_model">hr.payroll.period.schedule</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem action="open_payroll_period_schedule_view"
+ id="menu_payroll_period_schedule_view"
+ parent="hr_payroll.payroll_configure"
+ groups="hr_security.group_payroll_user,base.group_hr_manager"
+ sequence="20"/>
+
+ <!-- Payroll Exception -->
+
+ <record id="view_payroll_exception_tree" model="ir.ui.view">
+ <field name="name">hr.payslip.exception.tree</field>
+ <field name="model">hr.payslip.exception</field>
+ <field name="arch" type="xml">
+ <tree string="Payroll Exceptions" colors="red:severity == 'critical';orange:severity == 'high';blue:severity == 'medium';black:severity == 'low';">
+ <field name="name"/>
+ <field name="slip_id"/>
+ <field name="severity"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_payroll_exception_form" model="ir.ui.view">
+ <field name="name">hr.payslip.exception.form</field>
+ <field name="model">hr.payslip.exception</field>
+ <field name="arch" type="xml">
+ <form string="Payroll Exception" version="7.0">
+ <div class="oe_title">
+ <label for="name" class="oe_edit_only"/>
+ <h1><field name="name"/></h1>
+ </div>
+ <group>
+ <group>
+ <field name="rule_id"/>
+ <field name="slip_id"/>
+ <field name="severity"/>
+ </group>
+ </group>
+ </form>
+ </field>
+ </record>
+ <record id="open_payroll_exception_view" model="ir.actions.act_window">
+ <field name="name">Exceptions</field>
+ <field name="res_model">hr.payslip.exception</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem action="open_payroll_exception_view"
+ id="menu_payroll_exception_view"
+ parent="menu_payroll_advanced"
+ groups="hr_security.group_payroll_user,base.group_hr_manager"
+ sequence="40"/>
+
+ <!-- Payroll Exception Rule -->
+
+ <record id="view_exception_rule_tree" model="ir.ui.view">
+ <field name="name">hr.payslip.exception.rule.tree</field>
+ <field name="model">hr.payslip.exception.rule</field>
+ <field name="arch" type="xml">
+ <tree string="Payroll Exception Rules">
+ <field name="name"/>
+ <field name="code"/>
+ <field name="severity"/>
+ <field name="sequence"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_exception_rule_form" model="ir.ui.view">
+ <field name="name">hr.payslip.exception.rule.form</field>
+ <field name="model">hr.payslip.exception.rule</field>
+ <field name="arch" type="xml">
+ <form string="Payroll Exception Rule" version="7.0">
+ <div class="oe_title">
+ <label for="name" class="oe_edit_only"/>
+ <h1><field name="name"/></h1>
+ <label for="code" class="oe_edit_only" string="Code"/>
+ <h2>
+ <field name="code"/>
+ </h2>
+ </div>
+ <group>
+ <group>
+ <field name="severity"/>
+ <field name="condition_select"/>
+ </group>
+ <group>
+ <field name="active"/>
+ <field name="sequence"/>
+ <field name="company_id" widget="selection" groups="base.group_multi_company"/>
+ </group>
+ </group>
+ <group string="Conditions" colspan="4">
+ <field name="condition_python" nolabel="1" colspan="4" attrs="{'invisible':[('condition_select','<>','python')], 'required': [('condition_select','=','python')]}"/>
+ </group>
+ <group string="Note" colspan="4">
+ <field name="note" nolabel="1"/>
+ </group>
+ </form>
+ </field>
+ </record>
+ <record id="open_payroll_exception_rule_view" model="ir.actions.act_window">
+ <field name="name">Payroll Exception Rules</field>
+ <field name="res_model">hr.payslip.exception.rule</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+ <menuitem action="open_payroll_exception_rule_view"
+ id="menu_payroll_exception_rule_view"
+ parent="hr_payroll.payroll_configure"
+ groups="hr_security.group_payroll_user,base.group_hr_manager"
+ sequence="30"/>
+
+ <!-- Employment Contracts -->
+
+ <record id="view_hr_contract_form_inherit" model="ir.ui.view">
+ <field name="name">hr.contract.form.inherit</field>
+ <field name="model">hr.contract</field>
+ <field name="inherit_id" ref="hr_payroll.hr_contract_form_inherit"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//field[@name='struct_id']" position="after">
+ <field name="pps_id"/>
+ </xpath>
+ <xpath expr="//field[@name='schedule_pay']" position="replace"/>
+ </data>
+ </field>
+ </record>
+
+ <!-- Attendance Records -->
+
+ <record id="view_hr_attendance_tree" model="ir.ui.view">
+ <field name="name">hr.attendance.tree.inherit</field>
+ <field name="model">hr.attendance</field>
+ <field name="inherit_id" ref="hr_attendance.view_attendance_tree"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//field[@name='action_desc']" position="after">
+ <field name="state"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ <record id="view_hr_attendance_form" model="ir.ui.view">
+ <field name="name">hr.attendance.form.inherit</field>
+ <field name="model">hr.attendance</field>
+ <field name="inherit_id" ref="hr_attendance.view_attendance_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <xpath expr="//form/sheet" position="before">
+ <header>
+ <field name="state" widget="statusbar"/>
+ </header>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ <!-- Pay Slip Amendment -->
+
+ <record id="view_hr_payslip_amendment_tree" model="ir.ui.view">
+ <field name="name">hr.payslip.amendment.tree.inherit</field>
+ <field name="model">hr.payslip.amendment</field>
+ <field name="inherit_id" ref="hr_payslip_amendment.view_payslip_amendment_tree"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='employee_id']" position="after">
+ <field name="pay_period_id"/>
+ </xpath>
+ </field>
+ </record>
+ <record id="view_hr_payslip_amendment_form" model="ir.ui.view">
+ <field name="name">hr.payslip.amendment.form.inherit</field>
+ <field name="model">hr.payslip.amendment</field>
+ <field name="inherit_id" ref="hr_payslip_amendment.view_payslip_amendment_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='name']" position="after">
+ <field name="pay_period_id" widget="selection"/>
+ </xpath>
+ </field>
+ </record>
+
+ <!-- Leave Types -->
+
+ <record id="view_hr_holidays_status_form" model="ir.ui.view">
+ <field name="name">hr.holidays.status.form.inherit</field>
+ <field name="model">hr.holidays.status</field>
+ <field name="inherit_id" ref="hr_holidays.edit_holiday_status_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='name']" position="after">
+ <field name="code"/>
+ </xpath>
+ </field>
+ </record>
+
+ <!-- Pay Slips -->
+ <record id="view_payslip_form" model="ir.ui.view">
+ <field name="name">hr.payslip.form.exception</field>
+ <field name="model">hr.payslip</field>
+ <field name="inherit_id" ref="hr_payroll.view_hr_payslip_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//group[@name='accounting']" position="after">
+ <group name="exceptions" string="Payroll Exceptions" colspan="4" col="2">
+ <field name="exception_ids" nolabel="1">
+ <tree string="Payroll Exceptions" colors="red:severity == 'critical';orange:severity == 'high';blue:severity == 'medium';black:severity == 'low';">
+ <field name="name"/>
+ <field name="severity"/>
+ </tree>
+ </field>
+ </group>
+ </xpath>
+ </field>
+ </record>
+
+ <!-- Initial Contract Settings -->
+
+ <record id="view_contract_init_tree" model="ir.ui.view">
+ <field name="name">hr.contract.init.tree.payroll_period</field>
+ <field name="model">hr.contract.init</field>
+ <field name="inherit_id" ref="hr_contract_init.view_contract_init_tree"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='struct_id']" position="after">
+ <field name="pay_sched_id"/>
+ </xpath>
+ </field>
+ </record>
+
+ <record id="view_contract_init_form" model="ir.ui.view">
+ <field name="name">hr.contract.init.form.payroll_period</field>
+ <field name="model">hr.contract.init</field>
+ <field name="inherit_id" ref="hr_contract_init.view_contract_init_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='struct_id']" position="after">
+ <field name="pay_sched_id"/>
+ </xpath>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== added file 'hr_payroll_period/hr_payroll_period_workflow.xml'
--- hr_payroll_period/hr_payroll_period_workflow.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/hr_payroll_period_workflow.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- Workflow Definition -->
+ <record id="wkf_payroll_period" model="workflow">
+ <field name="name">hr.payroll.period.basic</field>
+ <field name="osv">hr.payroll.period</field>
+ <field name="on_create">True</field>
+ </record>
+
+ <!-- Workflow Activities (Stages) -->
+
+ <record id="act_open" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_payroll_period"/>
+ <field name="name">open</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'open'})</field>
+ <field name="flow_start">True</field>
+ </record>
+
+ <record id="act_ended" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_payroll_period"/>
+ <field name="name">ended</field>
+ <field name="kind">function</field>
+ <field name="action">set_state_ended()</field>
+ </record>
+
+ <record id="act_period_locked" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_payroll_period"/>
+ <field name="name">locked</field>
+ <field name="kind">function</field>
+ <field name="action">set_state_locked()</field>
+ </record>
+
+ <record id="act_generate" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_payroll_period"/>
+ <field name="name">generate</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'generate'})</field>
+ </record>
+
+ <record id="act_payment" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_payroll_period"/>
+ <field name="name">payment</field>
+ <field name="kind">function</field>
+ <field name="action">write({'state': 'payment'})</field>
+ </record>
+
+ <record id="act_closed" model="workflow.activity">
+ <field name="wkf_id" ref="wkf_payroll_period"/>
+ <field name="name">closed</field>
+ <field name="kind">function</field>
+ <field name="action">set_state_closed()</field>
+ </record>
+
+ <!-- Workflow Transitions -->
+
+ <record id="open2ended" model="workflow.transition">
+ <field name="act_from" ref="act_open"/>
+ <field name="act_to" ref="act_ended"/>
+ <field name="signal">end_period</field>
+ </record>
+
+ <record id="ended2locked" model="workflow.transition">
+ <field name="act_from" ref="act_ended"/>
+ <field name="act_to" ref="act_period_locked"/>
+ <field name="signal">lock_period</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="locked2ended" model="workflow.transition">
+ <field name="act_from" ref="act_period_locked"/>
+ <field name="act_to" ref="act_ended"/>
+ <field name="signal">unlock_period</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="locked2generate" model="workflow.transition">
+ <field name="act_from" ref="act_period_locked"/>
+ <field name="act_to" ref="act_generate"/>
+ <field name="signal">generate_payslips</field>
+ <field name="group_id" ref="hr_security.group_payroll_manager"/>
+ </record>
+
+ <record id="generate2ended" model="workflow.transition">
+ <field name="act_from" ref="act_generate"/>
+ <field name="act_to" ref="act_ended"/>
+ <field name="signal">unlock_period</field>
+ <field name="group_id" ref="base.group_hr_manager"/>
+ </record>
+
+ <record id="generate2payment" model="workflow.transition">
+ <field name="act_from" ref="act_generate"/>
+ <field name="act_to" ref="act_payment"/>
+ <field name="signal">start_payments</field>
+ <field name="group_id" ref="hr_security.group_payroll_manager"/>
+ </record>
+
+ <record id="payment2closed" model="workflow.transition">
+ <field name="act_from" ref="act_payment"/>
+ <field name="act_to" ref="act_closed"/>
+ <field name="signal">close_period</field>
+ <field name="group_id" ref="hr_security.group_payroll_manager"/>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'hr_payroll_period/security'
=== added file 'hr_payroll_period/security/ir.model.access.csv'
--- hr_payroll_period/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/security/ir.model.access.csv 2013-09-27 21:15:53 +0000
@@ -0,0 +1,35 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_hr_payroll_period_user,access_hr_payroll_period,model_hr_payroll_period,hr_security.group_payroll_user,1,1,1,0
+access_hr_payroll_period_manager,access_hr_payroll_period,model_hr_payroll_period,hr_security.group_payroll_manager,1,1,1,1
+access_hr_payroll_period_hrm,access_hr_payroll_period,model_hr_payroll_period,base.group_hr_manager,1,1,0,0
+access_hr_payroll_period_schedule_user,access_hr_payroll_period_schedule,model_hr_payroll_period_schedule,hr_security.group_payroll_user,1,0,0,0
+access_hr_payroll_period_schedule_manager,access_hr_payroll_period_schedule,model_hr_payroll_period_schedule,hr_security.group_payroll_manager,1,1,1,1
+access_hr_payroll_period_schedule_hro,access_hr_payroll_period_schedule,model_hr_payroll_period_schedule,base.group_hr_user,1,0,0,0
+access_hr_payslip_amendment_hruser,access_hr_payslip_amendment,model_hr_payslip_amendment,base.group_hr_user,1,1,1,1
+access_hr_payslip_amendment_user,access_hr_payslip_amendment,model_hr_payslip_amendment,hr_security.group_payroll_user,1,1,1,1
+access_hr_payslip_exception_hrm,access_hr_payslip_exception,model_hr_payslip_exception,base.group_hr_manager,1,0,0,0
+access_hr_payslip_exception_rule_hrm,access_hr_payslip_exception_rule,model_hr_payslip_exception_rule,base.group_hr_manager,1,0,0,0
+access_hr_payslip_exception_pm,access_hr_payslip_exception,model_hr_payslip_exception,hr_security.group_payroll_manager,1,1,1,1
+access_hr_payslip_exception_rule_pm,access_hr_payslip_exception_rule,model_hr_payslip_exception_rule,hr_security.group_payroll_manager,1,1,1,1
+access_hr_contract_pm,access_hr_contract,model_hr_contract,hr_security.group_payroll_manager,1,0,0,0
+access_hr_payroll_structure_pm,hr.payroll.structure,hr_payroll.model_hr_payroll_structure,hr_security.group_payroll_manager,1,1,1,1
+access_hr_contribution_register_pm,hr.contribution.register,hr_payroll.model_hr_contribution_register,hr_security.group_payroll_manager,1,1,1,1
+access_hr_salary_rule_category_pm,hr.salary.rule.category,hr_payroll.model_hr_salary_rule_category,hr_security.group_payroll_manager,1,1,1,1
+access_hr_payslip_pm,hr.payslip,hr_payroll.model_hr_payslip,hr_security.group_payroll_manager,1,1,1,1
+access_hr_payslip_line_pm,hr.payslip.line,hr_payroll.model_hr_payslip_line,hr_security.group_payroll_manager,1,1,1,1
+access_hr_payslip_input_pm,hr.payslip.input,hr_payroll.model_hr_payslip_input,hr_security.group_payroll_manager,1,1,1,1
+access_hr_payslip_worked_days _pm,hr.payslip.worked_days,hr_payroll.model_hr_payslip_worked_days,hr_security.group_payroll_manager,1,1,1,1
+access_hr_payslip_run_pm,hr.payslip.run,hr_payroll.model_hr_payslip_run,hr_security.group_payroll_manager,1,1,1,1
+access_hr_rule_input_pm,hr.rule.input,hr_payroll.model_hr_rule_input,hr_security.group_payroll_manager,1,1,1,1
+access_hr_salary_rule_pm,hr.salary.rule,hr_payroll.model_hr_salary_rule,hr_security.group_payroll_manager,1,1,1,1
+access_hr_schedule_pm,access_hr_schedule,hr_schedule.model_hr_schedule,hr_security.group_payroll_manager,1,0,0,0
+access_hr_schedule_detail_pm,access_hr_schedule_detail,hr_schedule.model_hr_schedule_detail,hr_security.group_payroll_manager,1,0,0,0
+access_hr_schedule_template_pm,access_hr_schedule_template,hr_schedule.model_hr_schedule_template,hr_security.group_payroll_manager,1,0,0,0
+access_hr_schedule_template_worktime_pm,access_hr_schedule_template_worktime,hr_schedule.model_hr_schedule_template_worktime,hr_security.group_payroll_manager,1,0,0,0
+access_hr_schedule_alert_pm,access_hr_schedule_alert,hr_schedule.model_hr_schedule_alert,hr_security.group_payroll_manager,1,0,0,0
+access_hr_schedule_alert_rule_pm,access_hr_schedule_alert_rule,hr_schedule.model_hr_schedule_alert_rule,hr_security.group_payroll_manager,1,0,0,0
+access_hr_payroll_register_pm,access_hr_payroll_register,hr_payroll_register.model_hr_payroll_register,hr_security.group_payroll_manager,1,1,1,1
+access_hr_attendance_pm,access_hr_attendance,hr_attendance.model_hr_attendance,hr_security.group_payroll_manager,1,0,0,0
+access_hr_holidays_pm,access_hr_holidays,hr_holidays.model_hr_holidays,hr_security.group_payroll_manager,1,0,0,0
+access_hr_holidays_status_pm,access_hr_holidays,hr_holidays.model_hr_holidays_status,hr_security.group_payroll_manager,1,0,0,0
+access_hr_employee_children_pm,hr.employee.children_pm,hr_family.model_hr_employee_children,hr_security.group_payroll_manager,1,0,0,0
=== added file 'hr_payroll_period/security/ir_rule.xml'
--- hr_payroll_period/security/ir_rule.xml 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/security/ir_rule.xml 2013-09-27 21:15:53 +0000
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data noupdate="1">
+
+ <!-- Allow Payroll Manager access to all attendance records -->
+ <record id="property_rule_attendace_pm" model="ir.rule">
+ <field name="name">Payroll Manager Attendance</field>
+ <field model="ir.model" name="model_id" ref="model_hr_attendance"/>
+ <field name="domain_force">[(1,'=',1)]</field>
+ <field name="groups" eval="[(4,ref('hr_security.group_payroll_manager'))]"/>
+ </record>
+
+ <!-- Allow Payroll Manager to access holidays/leaves -->
+ <record id="property_rule_holidays_pm" model="ir.rule">
+ <field name="name">Payroll Manager Holidays</field>
+ <field model="ir.model" name="model_id" ref="hr_holidays.model_hr_holidays"/>
+ <field name="domain_force">[(1,'=',1)]</field>
+ <field name="groups" eval="[(4,ref('hr_security.group_payroll_manager'))]"/>
+ </record>
+
+ </data>
+</openerp>
+
=== added directory 'hr_payroll_period/wizard'
=== added file 'hr_payroll_period/wizard/__init__.py'
--- hr_payroll_period/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/wizard/__init__.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,22 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+from . import payroll_period_end
=== added file 'hr_payroll_period/wizard/payroll_period_end.py'
--- hr_payroll_period/wizard/payroll_period_end.py 1970-01-01 00:00:00 +0000
+++ hr_payroll_period/wizard/payroll_period_end.py 2013-09-27 21:15:53 +0000
@@ -0,0 +1,1162 @@
+#-*- coding:utf-8 -*-
+#
+#
+# Copyright (C) 2013 Michael Telahun Makonnen <mmakonnen@xxxxxxxxx>.
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import logging
+import math
+import netsvc
+from datetime import datetime, timedelta
+from dateutil.relativedelta import relativedelta
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as OEDATETIME_FORMAT
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as OEDATE_FORMAT
+from openerp.tools.translate import _
+from osv import fields, osv
+from pytz import timezone
+
+_logger = logging.getLogger(__name__)
+
+
+class payroll_period_end_1(osv.osv_memory):
+
+ _name = 'hr.payroll.period.end.1'
+ _description = 'End of Payroll Period Wizard Step 1'
+
+ _change_res = {
+ 'br100': 0,
+ 'br50': 0,
+ 'br10': 0,
+ 'br5': 0,
+ 'br1': 0,
+ 'cent50': 0,
+ 'cent25': 0,
+ 'cent10': 0,
+ 'cent05': 0,
+ 'cent01': 0,
+ 'done': False,
+ }
+
+ _columns = {
+ 'period_id': fields.integer('Period ID'),
+ 'is_ended': fields.boolean('Past End Day?'),
+ 'public_holiday_ids': fields.many2many('hr.holidays.public.line', 'hr_holidays_pay_period_rel', 'holiday_id', 'period_id', 'Public Holidays', readonly=True),
+ 'alert_critical': fields.integer('Critical Severity', readonly=True),
+ 'alert_high': fields.integer('High Severity', readonly=True),
+ 'alert_medium': fields.integer('Medium Severity', readonly=True),
+ 'alert_low': fields.integer('Low Severity', readonly=True),
+ 'pex_critical': fields.integer('Critical', readonly=True),
+ 'pex_high': fields.integer('High', readonly=True),
+ 'pex_medium': fields.integer('Medium', readonly=True),
+ 'pex_low': fields.integer('Low', readonly=True),
+ 'locked': fields.boolean('Is Period Locked?', readonly=True),
+ 'can_unlock': fields.boolean('Can Unlock Period?', readonly=True),
+ 'payslips': fields.boolean('Have Pay Slips Been Generated?', readonly=True),
+ 'ps_gen