diff --git a/addons/hr_contract/__manifest__.py b/addons/hr_contract/__manifest__.py
index d229c773b53..f05b650c937 100644
--- a/addons/hr_contract/__manifest__.py
+++ b/addons/hr_contract/__manifest__.py
@@ -23,7 +23,7 @@ You can assign several contracts per employee.
'security/ir.model.access.csv',
'data/hr_contract_data.xml',
'views/hr_contract_views.xml',
- 'views/hr_contract_templates.xml',
+ 'views/assets.xml',
],
'demo': ['data/hr_contract_demo.xml'],
'installable': True,
diff --git a/addons/hr_contract/data/hr_contract_demo.xml b/addons/hr_contract/data/hr_contract_demo.xml
index c7092205f57..591137f2f88 100644
--- a/addons/hr_contract/data/hr_contract_demo.xml
+++ b/addons/hr_contract/data/hr_contract_demo.xml
@@ -11,6 +11,7 @@
eval="obj().env.ref('hr.employee_admin').department_id.id"/>
draft
+ normal
@@ -23,6 +24,7 @@
eval="obj().env.ref('hr.employee_al').department_id.id"/>
open
+ normal
@@ -35,6 +37,7 @@
eval="obj().env.ref('hr.employee_mit').department_id.id"/>
open
+ normal
@@ -48,6 +51,7 @@
eval="obj().env.ref('hr.employee_stw').department_id.id"/>
open
+ normal
@@ -60,6 +64,7 @@
eval="obj().env.ref('hr.employee_qdp').department_id.id"/>
draft
+ normal
@@ -72,6 +77,7 @@
eval="obj().env.ref('hr.employee_han').department_id.id"/>
open
+ normal
@@ -85,6 +91,7 @@
eval="obj().env.ref('hr.employee_niv').department_id.id"/>
draft
+ normal
@@ -98,6 +105,7 @@
eval="obj().env.ref('hr.employee_jth').department_id.id"/>
draft
+ normal
@@ -110,6 +118,7 @@
eval="obj().env.ref('hr.employee_chs').department_id.id"/>
cancel
+ normal
@@ -122,6 +131,7 @@
eval="obj().env.ref('hr.employee_jve').department_id.id"/>
cancel
+ normal
@@ -134,7 +144,8 @@
- pending
+ open
+ blocked
@@ -148,6 +159,7 @@
eval="obj().env.ref('hr.employee_fpi').department_id.id"/>
close
+ normal
@@ -161,6 +173,7 @@
eval="obj().env.ref('hr.employee_vad').department_id.id"/>
close
+ normal
\ No newline at end of file
diff --git a/addons/hr_contract/models/hr_contract.py b/addons/hr_contract/models/hr_contract.py
index 544ec534715..343479f3bdb 100644
--- a/addons/hr_contract/models/hr_contract.py
+++ b/addons/hr_contract/models/hr_contract.py
@@ -6,8 +6,8 @@ from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
-from odoo.osv import expression
+from odoo.osv import expression
class Contract(models.Model):
_name = 'hr.contract'
@@ -33,14 +33,23 @@ class Contract(models.Model):
notes = fields.Text('Notes')
state = fields.Selection([
('draft', 'New'),
- ('incoming', 'Incoming'),
('open', 'Running'),
- ('pending', 'To Renew'),
('close', 'Expired'),
('cancel', 'Cancelled')
], string='Status', group_expand='_expand_states',
tracking=True, help='Status of the contract', default='draft')
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
+ """
+ kanban_state:
+ * draft + green = "Incoming" state (will be set as Open once the contract has started)
+ * open + red = "Pending" state (will be set as Closed once the contract has ended)
+ * red = Shows a warning on the employees kanban view
+ """
+ kanban_state = fields.Selection([
+ ('normal', 'Grey'),
+ ('done', 'Green'),
+ ('blocked', 'Red')
+ ], string='Kanban State', default='normal', tracking=True)
currency_id = fields.Many2one(string="Currency", related='company_id.currency_id', readonly=True)
permit_no = fields.Char('Work Permit No', related="employee_id.permit_no", readonly=False)
visa_no = fields.Char('Visa No', related="employee_id.visa_no", readonly=False)
@@ -57,6 +66,7 @@ class Contract(models.Model):
def _expand_states(self, states, domain, order):
return [key for key, val in type(self).state.selection]
+
@api.onchange('employee_id')
def _onchange_employee_id(self):
if self.employee_id:
@@ -64,14 +74,18 @@ class Contract(models.Model):
self.department_id = self.employee_id.department_id
self.resource_calendar_id = self.employee_id.resource_calendar_id
- @api.constrains('employee_id', 'state', 'date_start', 'date_end')
+ @api.constrains('employee_id', 'state', 'kanban_state', 'date_start', 'date_end')
def _check_current_contract(self):
- """ Two contracts in state [incoming | pending | open] cannot overlap """
- for contract in self.filtered(lambda c: c.state not in ['draft', 'cancel', 'close']):
+ """ Two contracts in state [incoming | open | close] cannot overlap """
+ for contract in self.filtered(lambda c: c.state not in ['draft', 'cancel'] or c.state == 'draft' and c.kanban_state == 'done'):
domain = [
('id', '!=', contract.id),
('employee_id', '=', contract.employee_id.id),
- ('state', 'in', ['incoming', 'pending', 'open']),
+ '|',
+ ('state', 'in', ['open', 'close']),
+ '&',
+ ('state', '=', 'draft'),
+ ('kanban_state', '=', 'done') # replaces incoming
]
if not contract.date_end:
@@ -102,11 +116,11 @@ class Contract(models.Model):
('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=60))),
('visa_expire', '>=', fields.Date.to_string(date.today() + relativedelta(days=1))),
]).write({
- 'state': 'pending'
+ 'kanban_state': 'blocked'
})
self.search([
- ('state', 'in', ('open', 'pending')),
+ ('state', '=', 'open'),
'|',
('date_end', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))),
('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))),
@@ -114,38 +128,45 @@ class Contract(models.Model):
'state': 'close'
})
- self.search([('state', '=', 'incoming'), ('date_start', '<=', fields.Date.to_string(date.today())),]).write({
+ self.search([('state', '=', 'draft'), ('kanban_state', '=', 'done'), ('date_start', '<=', fields.Date.to_string(date.today())),]).write({
'state': 'open'
})
return True
+ def _assign_open_contract(self):
+ for contract in self:
+ contract.employee_id.sudo().write({'contract_id': contract.id})
+
+ @api.multi
+ def write(self, vals):
+ res = super(Contract, self).write(vals)
+ if vals.get('state') == 'open':
+ self._assign_open_contract()
+
+ calendar = vals.get('resource_calendar_id')
+ if calendar and (self.state == 'open' or (self.state == 'draft' and self.kanban_state == 'done')):
+ self.mapped('employee_id').write({'resource_calendar_id': calendar})
+
+ if 'state' in vals and 'kanban_state' not in vals:
+ self.write({'kanban_state': 'normal'})
+
+ return res
+
@api.model
- def create(self, vals_list):
- contracts = super().create(vals_list)
- open_contracts = contracts.filtered(lambda c: c.state in ['open', 'pending'])
- # sync contract -> employee
- for contract in open_contracts:
- contract.employee_id.contract_id = contract
+ def create(self, vals):
+ contracts = super(Contract, self).create(vals)
+ if vals.get('state') == 'open':
+ contracts._assign_open_contract()
+ open_contracts = contracts.filtered(lambda c: c.state == 'open' or c.state == 'draft' and c.kanban_state == 'done')
# sync contract calendar -> calendar employee
for contract in open_contracts.filtered(lambda c: c.resource_calendar_id):
contract.employee_id.resource_calendar_id = contract.resource_calendar_id
return contracts
- @api.multi
- def write(self, vals):
- res = super(Contract, self).write(vals)
- if vals.get('state') == 'open':
- for contract in self:
- contract.employee_id.sudo().write({'contract_id': contract.id})
- calendar = vals.get('resource_calendar_id')
- if calendar and self.state in ['open', 'pending']:
- self.mapped('employee_id').write({'resource_calendar_id': calendar})
- return res
-
@api.multi
def _track_subtype(self, init_values):
self.ensure_one()
- if 'state' in init_values and self.state == 'pending':
+ if 'state' in init_values and self.state == 'open' and 'kanban_state' in init_values and self.kanban_state == 'blocked':
return self.env.ref('hr_contract.mt_contract_pending')
elif 'state' in init_values and self.state == 'close':
return self.env.ref('hr_contract.mt_contract_close')
diff --git a/addons/hr_contract/models/hr_employee.py b/addons/hr_contract/models/hr_employee.py
index 4d596f046e0..a1f36d333b7 100644
--- a/addons/hr_contract/models/hr_employee.py
+++ b/addons/hr_contract/models/hr_employee.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
+from odoo.osv import expression
class Employee(models.Model):
@@ -14,6 +15,12 @@ class Employee(models.Model):
groups="hr.group_hr_user", help='Current contract of the employee')
calendar_mismatch = fields.Boolean(related='contract_id.calendar_mismatch')
contracts_count = fields.Integer(compute='_compute_contracts_count', string='Contract Count')
+ contract_warning = fields.Boolean(string='Contract Warning', store=True, compute='_compute_contract_warning', groups="hr.group_hr_user")
+
+ @api.depends('contract_id', 'contract_id.state', 'contract_id.kanban_state')
+ def _compute_contract_warning(self):
+ for employee in self:
+ employee.contract_warning = not employee.contract_id or employee.contract_id.kanban_state == 'blocked' or employee.contract_id.state != 'open'
def _compute_contracts_count(self):
# read_group as sudo, since contract count is displayed on form view
@@ -22,20 +29,27 @@ class Employee(models.Model):
for employee in self:
employee.contracts_count = result.get(employee.id, 0)
- def _get_contracts(self, date_from, date_to, states=['open', 'pending']):
+ def _get_contracts(self, date_from, date_to, states=['open'], kanban_state=False):
"""
Returns the contracts of the employee between date_from and date_to
"""
- return self.env['hr.contract'].search([
- '&', '&', '&',
- ('employee_id', 'in', self.ids),
- ('state', 'in', states),
- ('date_start', '<=', date_to),
- '|', ('date_end', '=', False), ('date_end', '>=', date_from)
- ])
+ state_domain = [('state', 'in', states)]
+ if kanban_state:
+ state_domain = expression.AND([state_domain, [('kanban_state', 'in', kanban_state)]])
+
+ return self.env['hr.contract'].search(
+ expression.AND([[('employee_id', 'in', self.ids)],
+ state_domain,
+ [('date_start', '<=', date_to),
+ '|',
+ ('date_end', '=', False),
+ ('date_end', '>=', date_from)]]))
+
+ def _get_incoming_contracts(self, date_from, date_to):
+ return self._get_contracts(date_from, date_to, states=['draft'], kanban_state=['done'])
@api.model
- def _get_all_contracts(self, date_from, date_to, states=['open', 'pending']):
+ def _get_all_contracts(self, date_from, date_to, states=['open']):
"""
Returns the contracts of all employees between date_from and date_to
"""
diff --git a/addons/hr_contract/static/src/scss/state_selection.scss b/addons/hr_contract/static/src/scss/state_selection.scss
new file mode 100644
index 00000000000..329cfd28feb
--- /dev/null
+++ b/addons/hr_contract/static/src/scss/state_selection.scss
@@ -0,0 +1,9 @@
+.o_hr_contract_state {
+ .o_status {
+ vertical-align: unset !important;
+ }
+}
+
+.o_hr_contract_job_id {
+ min-height: 1.5rem;
+}
\ No newline at end of file
diff --git a/addons/hr_contract/tests/test_auto_status.py b/addons/hr_contract/tests/test_auto_status.py
index c342d05dd0f..fcff8621bb9 100644
--- a/addons/hr_contract/tests/test_auto_status.py
+++ b/addons/hr_contract/tests/test_auto_status.py
@@ -12,6 +12,9 @@ class TestHrContracts(TestContractBase):
self.contracts = self.env['hr.contract'].with_context(tracking_disable=True)
self.test_contract = dict(name='Test', wage=1, employee_id=self.employee.id, state='open')
+ def test_employee_contractwarning(self):
+ self.assertEquals(self.employee.contract_warning, True)
+
def apply_cron(self):
self.env.ref('hr_contract.ir_cron_data_contract_update_state').method_direct_trigger()
@@ -20,16 +23,20 @@ class TestHrContracts(TestContractBase):
self.contract = self.contracts.create(self.test_contract)
self.apply_cron()
self.assertEquals(self.contract.state, 'open')
+ self.assertEquals(self.contract.kanban_state, 'normal')
+ self.assertEquals(self.employee.contract_warning, False)
self.test_contract.update(dict(date_end=datetime.now() + relativedelta(days=5)))
self.contract.write(self.test_contract)
self.apply_cron()
- self.assertEquals(self.contract.state, 'pending')
+ self.assertEquals(self.contract.state, 'open')
+ self.assertEquals(self.contract.kanban_state, 'blocked')
self.test_contract.update({
'date_start': datetime.now() + relativedelta(days=-50),
'date_end': datetime.now() + relativedelta(days=-1),
- 'state': 'pending',
+ 'state': 'open',
+ 'kanban_state': 'blocked',
})
self.contract.write(self.test_contract)
self.apply_cron()
@@ -40,19 +47,21 @@ class TestHrContracts(TestContractBase):
self.test_contract.update(dict(date_end=False))
self.contract = self.contracts.create(self.test_contract)
self.apply_cron()
- self.assertEquals(self.contract.state, 'pending')
+ self.assertEquals(self.contract.state, 'open')
+ self.assertEquals(self.contract.kanban_state, 'blocked')
self.employee.visa_expire = date.today() + relativedelta(days=-5)
self.test_contract.update({
'date_start': datetime.now() + relativedelta(days=-50),
- 'state': 'pending',
+ 'state': 'open',
+ 'kanban_state': 'blocked',
})
self.contract.write(self.test_contract)
self.apply_cron()
self.assertEquals(self.contract.state, 'close')
def test_contract_start_date(self):
- self.test_contract.update(dict(date_start=datetime.now(), state='incoming'))
+ self.test_contract.update(dict(date_start=datetime.now(), state='draft', kanban_state='done'))
self.contract = self.contracts.create(self.test_contract)
self.apply_cron()
self.assertEquals(self.contract.state, 'open')
diff --git a/addons/hr_contract/tests/test_contract.py b/addons/hr_contract/tests/test_contract.py
index ca05600929c..3d339c4c101 100644
--- a/addons/hr_contract/tests/test_contract.py
+++ b/addons/hr_contract/tests/test_contract.py
@@ -12,11 +12,12 @@ class TestHrContracts(TestContractBase):
super(TestHrContracts, self).setUp()
self.contracts = self.env['hr.contract'].with_context(tracking_disable=True)
- def create_contract(self, state, start, end=None):
+ def create_contract(self, state, kanban_state, start, end=None):
return self.env['hr.contract'].create({
'name': 'Contract',
'employee_id': self.employee.id,
'state': state,
+ 'kanban_state': kanban_state,
'wage': 1,
'date_start': start,
'date_end': end,
@@ -25,65 +26,65 @@ class TestHrContracts(TestContractBase):
def test_incoming_overlapping_contract(self):
start = datetime.strptime('2015-11-01', '%Y-%m-%d').date()
end = datetime.strptime('2015-11-30', '%Y-%m-%d').date()
- self.create_contract('open', start, end)
+ self.create_contract('open', 'normal', start, end)
# Incoming contract
with self.assertRaises(ValidationError, msg="It should not create two contract in state open or incoming"):
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
- self.create_contract('incoming', start, end)
+ self.create_contract('draft', 'done', start, end)
def test_pending_overlapping_contract(self):
start = datetime.strptime('2015-11-01', '%Y-%m-%d').date()
end = datetime.strptime('2015-11-30', '%Y-%m-%d').date()
- self.create_contract('open', start, end)
+ self.create_contract('open', 'normal', start, end)
# Pending contract
with self.assertRaises(ValidationError, msg="It should not create two contract in state open or pending"):
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
- self.create_contract('pending', start, end)
+ self.create_contract('open', 'blocked', start, end)
# Draft contract -> should not raise
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
- self.create_contract('draft', start, end)
+ self.create_contract('draft', 'normal', start, end)
def test_draft_overlapping_contract(self):
start = datetime.strptime('2015-11-01', '%Y-%m-%d').date()
end = datetime.strptime('2015-11-30', '%Y-%m-%d').date()
- self.create_contract('open', start, end)
+ self.create_contract('open', 'normal', start, end)
# Draft contract -> should not raise even if overlapping
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
- self.create_contract('draft', start, end)
+ self.create_contract('draft', 'normal', start, end)
def test_overlapping_contract_no_end(self):
# No end date
- self.create_contract('open', datetime.strptime('2015-11-01', '%Y-%m-%d').date())
+ self.create_contract('open', 'normal', datetime.strptime('2015-11-01', '%Y-%m-%d').date())
with self.assertRaises(ValidationError):
start = datetime.strptime('2015-11-15', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
- self.create_contract('incoming', start, end)
+ self.create_contract('draft', 'done', start, end)
def test_overlapping_contract_no_end2(self):
start = datetime.strptime('2015-11-01', '%Y-%m-%d').date()
end = datetime.strptime('2015-12-30', '%Y-%m-%d').date()
- self.create_contract('open', start, end)
+ self.create_contract('open', 'normal', start, end)
with self.assertRaises(ValidationError):
# No end
- self.create_contract('incoming', datetime.strptime('2015-01-01', '%Y-%m-%d').date())
+ self.create_contract('draft', 'done', datetime.strptime('2015-01-01', '%Y-%m-%d').date())
def test_set_employee_contract_create(self):
- contract = self.create_contract('open', date(2018, 1, 1), date(2018, 1, 2))
+ contract = self.create_contract('open', 'normal', date(2018, 1, 1), date(2018, 1, 2))
self.assertEqual(self.employee.contract_id, contract)
def test_set_employee_contract_write(self):
- contract = self.create_contract('draft', date(2018, 1, 1), date(2018, 1, 2))
+ contract = self.create_contract('draft', 'normal', date(2018, 1, 1), date(2018, 1, 2))
contract.state = 'open'
self.assertEqual(self.employee.contract_id, contract)
diff --git a/addons/hr_contract/views/assets.xml b/addons/hr_contract/views/assets.xml
new file mode 100644
index 00000000000..f482256b4ae
--- /dev/null
+++ b/addons/hr_contract/views/assets.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/hr_contract/views/hr_contract_views.xml b/addons/hr_contract/views/hr_contract_views.xml
index a1c9c3cd843..ecf835d094c 100644
--- a/addons/hr_contract/views/hr_contract_views.xml
+++ b/addons/hr_contract/views/hr_contract_views.xml
@@ -53,6 +53,37 @@
+
+ hr.employee.view.kanban
+ hr.employee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hr.employee.view.search
+ hr.employee
+
+
+
+
+
+
+
+
+
+
hr.user.preferences.view.form.contract.inherit
res.users
@@ -83,7 +114,7 @@
-
+
@@ -114,8 +145,16 @@
@@ -143,7 +182,7 @@
-
@@ -196,6 +235,7 @@
+
@@ -207,9 +247,10 @@
hr.contract.kanban
hr.contract
-
+
+
@@ -224,10 +265,13 @@