diff --git a/hr_employee_transfer/README.rst b/hr_employee_transfer/README.rst new file mode 100644 index 00000000000..4f237865cab --- /dev/null +++ b/hr_employee_transfer/README.rst @@ -0,0 +1,82 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================= +Employee Transfer +================= + +This module allows us to transfer employees between jobs and departments + + +Installation +============ + +Install this module as usual by going to the install addon screen + + +Configuration +============= + +No Additional Configuration Requirement is necessary + + +Usage +===== + +To use this module, you need to: + +* Go to Human Resources > Employee Transfer and create a transfer document + +#. Go to ... + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/116/8.0 + + +Known issues / Roadmap +====================== + + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed `feedback +`_. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Michael Telahun Makonnen ' +* Salton Massally + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/hr_employee_transfer/__init__.py b/hr_employee_transfer/__init__.py new file mode 100644 index 00000000000..f119cbcd759 --- /dev/null +++ b/hr_employee_transfer/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# © 2013 Michael Telahun Makonnen . + +from . import models diff --git a/hr_employee_transfer/__openerp__.py b/hr_employee_transfer/__openerp__.py new file mode 100644 index 00000000000..66f8faed874 --- /dev/null +++ b/hr_employee_transfer/__openerp__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# © 2013 Michael Telahun Makonnen . + +{ + 'name': 'Employee Transfer', + 'version': '8.0.1.0.0', + 'license': 'AGPL-3', + 'category': 'Human Resources', + 'summary': "Transfer Employees between Jobs and Departments", + 'author': 'Michael Telahun Makonnen, ' + 'Odoo Community Association (OCA)', + 'website': 'http://miketelahun.wordpress.com', + 'depends': [ + 'hr_contract', + ], + 'data': [ + 'security/ir.model.access.csv', + 'data/hr_transfer_cron.xml', + 'data/hr_transfer_data.xml', + 'views/hr_transfer_view.xml', + 'data/hr_transfer_workflow.xml', + ], + + 'installable': True, +} diff --git a/hr_employee_transfer/data/hr_transfer_cron.xml b/hr_employee_transfer/data/hr_transfer_cron.xml new file mode 100755 index 00000000000..d0a2e022ba7 --- /dev/null +++ b/hr_employee_transfer/data/hr_transfer_cron.xml @@ -0,0 +1,18 @@ + + + + + + Departmental Transfers + 1 + days + -1 + + + + + + + + + diff --git a/hr_employee_transfer/data/hr_transfer_data.xml b/hr_employee_transfer/data/hr_transfer_data.xml new file mode 100755 index 00000000000..e0f9a3b842d --- /dev/null +++ b/hr_employee_transfer/data/hr_transfer_data.xml @@ -0,0 +1,26 @@ + + + + + + + + Department Transfer - Confirmed + hr.department.transfer + Transfer submitted + + + + Department Transfer - Pending + hr.department.transfer + Transfer pending effective date + + + + Department Transfer - Completed + hr.department.transfer + Transfer complete + + + + diff --git a/hr_employee_transfer/data/hr_transfer_workflow.xml b/hr_employee_transfer/data/hr_transfer_workflow.xml new file mode 100755 index 00000000000..631e75655ff --- /dev/null +++ b/hr_employee_transfer/data/hr_transfer_workflow.xml @@ -0,0 +1,100 @@ + + + + + + + hr.department.transfer.basic + hr.department.transfer + True + + + + + + + draft + function + write({'state': 'draft'}) + True + + + + + confirm + function + state_confirm() + + + + + pending + function + write({'state': 'pending'}) + + + + + done + function + state_done() + True + + + + + cancel + function + write({'state': 'cancel'}) + True + + + + + + + + signal_confirm + + + + + + + effective_date_in_future() + signal_pending + + + + + + + not effective_date_in_future() + signal_pending + + + + + + + signal_cancel + + + + + + + not effective_date_in_future() + signal_done + + + + + + + signal_cancel + + + + + diff --git a/hr_employee_transfer/models/__init__.py b/hr_employee_transfer/models/__init__.py new file mode 100644 index 00000000000..fa3bbbfc501 --- /dev/null +++ b/hr_employee_transfer/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# © 2013 Michael Telahun Makonnen . + +from . import hr_department_transfer diff --git a/hr_employee_transfer/models/hr_department_transfer.py b/hr_employee_transfer/models/hr_department_transfer.py new file mode 100644 index 00000000000..ee24754ca46 --- /dev/null +++ b/hr_employee_transfer/models/hr_department_transfer.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- +# © 2013 Michael Telahun Makonnen . + +from dateutil.relativedelta import relativedelta + +from openerp import fields, models, api +from openerp.exceptions import Warning as UserError + + +_tracked_states = { + 'hr_transfer.mt_alert_xfer_confirmed': + lambda self, cr, uid, obj, ctx = None: obj['state'] == 'confirm', + 'hr_transfer.mt_alert_xfer_pending': + lambda self, cr, uid, obj, ctx = None: obj['state'] == 'pending', + 'hr_transfer.mt_alert_xfer_done': + lambda self, cr, uid, obj, ctx = None: obj['state'] == 'done', +} + + +class HrDepartmentTransfer(models.Model): + _name = 'hr.department.transfer' + _description = 'Departmental Transfer' + _order = "id DESC" + _inherit = ['mail.thread', 'ir.needaction_mixin'] + + employee_id = fields.Many2one( + 'hr.employee', + 'Employee', + required=True, + readonly=True, + states={'draft': [('readonly', False)]} + ) + src_id = fields.Many2one( + 'hr.job', + 'Job (FROM)', + required=True, + readonly=True, + states={'draft': [('readonly', False)]} + ) + dst_id = fields.Many2one( + 'hr.job', + 'Job (TO)', + required=True, + readonly=True, + states={'draft': [('readonly', False)]} + ) + src_department_id = fields.Many2one( + 'hr.department', + 'Department (FROM)', + relation='src_id.department_id', + store=True, + readonly=True + ) + dst_department_id = fields.Many2one( + 'hr.department', + 'Department (TO)', + relation='dst_id.department_id', + store=True, + readonly=True + ) + src_contract_id = fields.Many2one( + 'hr.contract', + 'Contract (FROM)', + readonly=True, + states={'draft': [('readonly', False)]} + ) + dst_contract_id = fields.Many2one( + 'hr.contract', + 'Contract (TO)', + readonly=True + ) + date = fields.Date( + 'Effective Date', + required=True, + readonly=True, + states={'draft': [('readonly', False)]} + ) + note = fields.Text('Reason') + state = fields.Selection( + [ + ('draft', 'Draft'), + ('confirm', 'Confirmed'), + ('pending', 'Pending'), + ('done', 'Done'), + ('cancel', 'Cancelled'), + ], + 'State', + readonly=True, + default='draft' + ) + + _rec_name = 'date' + + _defaults = { + 'state': 'draft', + } + + _track = { + 'state': _tracked_states, + } + + @api.model + def _needaction_domain_get(self): + + users_obj = self.env['res.users'] + domain = [] + if users_obj.has_group('base.group_hr_manager'): + domain = [('state', '=', 'confirm')] + return domain + + return False + + @api.multi + def unlink(self, cr, uid, ids, context=None): + transfers = self.filtered(lambda r: r.state != 'draft') + if transfers: + raise UserError('Unable to Delete Transfer! \n' + 'Transfer has been initiated. Either cancel the ' + 'transfer or create another transfer to undo it') + + return super(HrDepartmentTransfer, self).unlink() + + @api.onchange('employee_id') + @api.one + def onchange_employee(self): + if self.employee_id: + employee = self.employee_id + self.src_department_id = employee.department_id and \ + employee.department_id.id or \ + False + self.src_id = employee.contract_id and \ + (employee.contract_id.job_id and \ + employee.contract_id_job_id.id or \ + False) or False + self.src_contract_id = employee.contract_id and \ + self.employee_id.contract_id.id or False + + @api.model + def _check_state(self, contract, effective_date): + if contract.date_end and effective_date >= contract.date_end: + raise UserError('The contract end date is on or before the ' + 'effective date of the transfer.') + return True + + @api.multi + def effective_date_in_future(self): + today = fields.Date.today() + for xfer in self: + if xfer.date <= today: + return False + return True + + @api.multi + def transfer_contract(self, contract, job_id, effective_date): + self.ensure_one() + # Copy the contract and adjust start/end dates, + # job id, etc. accordingly. + default = { + 'job_id': job_id, + 'date_start': effective_date, + 'name': '/', + 'message_ids': False, + 'trial_date_start': False, + 'trial_date_end': False, + } + data = contract.copy_data(default=default) + if isinstance(data, list): + data = data[0] + + # end the current contract + contract.date_end = fields.Date.to_string( + fields.Date.from_string(effective_date) + relativedelta(days=-1)) + + dst_contract = self.env['hr.contract'].create(data) + # Link to the new contract + self.write({'dst_contract_id': dst_contract.id}) + + @api.multi + def state_confirm(self): + for xfer in self: + self._check_state(xfer.src_contract_id, xfer.date) + self.write({'state': 'confirm'}) + return True + + @api.multi + def state_done(self): + + for xfer in self: + if xfer.date <= fields.Date.today(): + self._check_state(xfer.src_contract_id, xfer.date) + xfer.employee_id.write( + {'department_id': xfer.dst_department_id.id}) + if xfer.src_contract_id: + xfer.transfer_contract(xfer.src_contract_id, + xfer.dst_id.id, xfer.date) + xfer.write({'state': 'done'}) + else: + return False + return True + + @api.model + def try_pending_department_transfers(self): + """Completes pending departmental transfers. + Called from the scheduler.""" + pending_transfers = self.search( + [ + ('state', '=', 'pending'), + ('date', '<=', fields.Date.today()), + ] + ) + pending_transfers.signal_workflow('signal_done') + return True diff --git a/hr_employee_transfer/security/ir.model.access.csv b/hr_employee_transfer/security/ir.model.access.csv new file mode 100755 index 00000000000..5417816caab --- /dev/null +++ b/hr_employee_transfer/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_transfer_user,access_hr_transfer,model_hr_department_transfer,base.group_hr_user,1,1,1,1 +access_hr_transfer_manager,access_hr_transfer,model_hr_department_transfer,base.group_hr_manager,1,1,1,1 diff --git a/hr_employee_transfer/static/description/icon.png b/hr_employee_transfer/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/hr_employee_transfer/static/description/icon.png differ diff --git a/hr_employee_transfer/tests/__init__.py b/hr_employee_transfer/tests/__init__.py new file mode 100644 index 00000000000..3570ce633d1 --- /dev/null +++ b/hr_employee_transfer/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# © 2015 Salton Massally (). + +from . import test_employee_transfer diff --git a/hr_employee_transfer/tests/test_employee_transfer.py b/hr_employee_transfer/tests/test_employee_transfer.py new file mode 100644 index 00000000000..9c4a1aa1336 --- /dev/null +++ b/hr_employee_transfer/tests/test_employee_transfer.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# © 2015 Salton Massally (). + +from openerp.tests import common +from openerp import fields + + +class TestEmployeeTransfer(common.TransactionCase): + + def setUp(self): + super(TestEmployeeTransfer, self).setUp() + self.employee_model = self.env['hr.employee'] + self.transfer_model = self.env['hr.department.transfer'] + self.job_model = self.env['hr.job'] + + # Create an employees + self.job = self.job_model.create({'name': 'Job 1'}) + self.job_2 = self.job_model.create({'name': 'Job 2'}) + self.department = self.env['hr.department'].create( + {'name': 'Department 1'}) + self.employee = self.employee_model.create({ + 'name': 'Employee 1', + 'job_id': self.job.id, + 'department_id': self.department.id + }) + self.contract = self.env["hr.contract"].create({ + 'name': 'Contract 1', + 'date_start': '2010-10-01', + 'employee_id': self.employee.id, + 'wage': 1000, + 'job_id': self.job.id, + }) + + # create transfer + self.transfer = self.transfer_model.create( + { + 'employee_id': self.employee.id, + 'src_id': self.job.id, + 'dst_id': self.job_2.id, + 'src_contract_id': self.contract.id, + 'date': fields.Date.today() + } + ) + + self.transfer.signal_workflow('signal_confirm') + self.transfer.signal_workflow('signal_pending') + + def test_transfer(self): + self.assertTrue(self.transfer.dst_contract_id) + self.assertTrue(self.transfer.src_contract_id) + self.assertEqual(self.employee.job_id, self.job_2) diff --git a/hr_employee_transfer/views/hr_transfer_view.xml b/hr_employee_transfer/views/hr_transfer_view.xml new file mode 100755 index 00000000000..687e45f6561 --- /dev/null +++ b/hr_employee_transfer/views/hr_transfer_view.xml @@ -0,0 +1,96 @@ + + + + + + hr.department.transfer.tree + hr.department.transfer + + + + + + + + + + + + + Job/Departmental Transfers + hr.department.transfer + + + + + + + + + + + + + + + + + hr.department.transfer.form + hr.department.transfer + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+ + + + Employee Transfers + hr.department.transfer + form + tree,form + {"search_default_inprogress":1} + + + + + +
+