diff options
Diffstat (limited to 'addons')
32 files changed, 341 insertions, 303 deletions
diff --git a/addons/hr/data/hr_data.xml b/addons/hr/data/hr_data.xml index ab7165fee25..8c70cef3e68 100644 --- a/addons/hr/data/hr_data.xml +++ b/addons/hr/data/hr_data.xml @@ -99,5 +99,10 @@ <field name="user_signature" eval="False"/> </record> + <record model="ir.config_parameter" id="hr_presence_control_login" forcecreate="False"> + <field name="key">hr.hr_presence_control_login</field> + <field name="value">True</field> + </record> + </data> </odoo> diff --git a/addons/hr/models/__init__.py b/addons/hr/models/__init__.py index efe3c1d702c..43857d3d83e 100644 --- a/addons/hr/models/__init__.py +++ b/addons/hr/models/__init__.py @@ -13,3 +13,4 @@ from . import mail_channel from . import res_config_settings from . import res_partner from . import res_users +from . import res_company diff --git a/addons/hr/models/hr_employee_base.py b/addons/hr/models/hr_employee_base.py index fec003ccfc6..191a5cb926d 100644 --- a/addons/hr/models/hr_employee_base.py +++ b/addons/hr/models/hr_employee_base.py @@ -23,3 +23,31 @@ class HrEmployeeBase(models.AbstractModel): user_id = fields.Many2one('res.users') resource_id = fields.Many2one('resource.resource') resource_calendar_id = fields.Many2one('resource.calendar') + hr_presence_state = fields.Selection([ + ('present', 'Present'), + ('absent', 'Absent'), + ('to_define', 'To Define')], compute='_compute_presence_state', default='to_define') + last_activity = fields.Date(compute="_compute_last_activity") + + def _compute_presence_state(self): + """ + This method is overritten in several other modules which add additional + presence criterions. e.g. hr_attendance, hr_holidays + """ + # Check on login + check_login = self.env['ir.config_parameter'].sudo().get_param('hr.hr_presence_control_login') + for employee in self: + state = 'to_define' + if check_login: + if employee.user_id.im_status == 'online': + state = 'present' + elif employee.user_id.im_status == 'offline': + state = 'absent' + employee.hr_presence_state = state + + def _compute_last_activity(self): + employees = self.filtered(lambda e: e.user_id) + presences = self.env['bus.presence'].search([('user_id', 'in', employees.mapped('user_id.id'))]) + + for presence in presences: + presence.user_id.employee_ids.last_activity = presence.last_presence.date() diff --git a/addons/hr/models/res_company.py b/addons/hr/models/res_company.py new file mode 100644 index 00000000000..cd1937d639e --- /dev/null +++ b/addons/hr/models/res_company.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class Company(models.Model): + _inherit = 'res.company' + + hr_presence_control_email_amount = fields.Integer(string="# emails to send") + hr_presence_control_ip_list = fields.Char(string="Valid IP addresses") diff --git a/addons/hr/models/res_config_settings.py b/addons/hr/models/res_config_settings.py index 14494a6318e..a75564bc122 100644 --- a/addons/hr/models/res_config_settings.py +++ b/addons/hr/models/res_config_settings.py @@ -10,9 +10,12 @@ class ResConfigSettings(models.TransientModel): 'resource.calendar', 'Company Working Hours', related='company_id.resource_calendar_id', readonly=False) module_hr_org_chart = fields.Boolean(string="Show Organizational Chart") - module_hr_presence = fields.Boolean(string="Control presence of employees") + module_hr_presence = fields.Boolean(string="Advanced control presence of employees") module_hr_skills = fields.Boolean(string="Employee Skills and Resumé") hr_presence_control_login = fields.Boolean(string="According to the system login (User status on chat)", config_parameter='hr.hr_presence_control_login') - hr_presence_control_email = fields.Boolean(string="According to the amount of sent emails", config_parameter='hr.hr_presence_control_email') - hr_presence_control_ip = fields.Boolean(string="According to the IP address", config_parameter='hr.hr_presence_control_ip') + hr_presence_control_email = fields.Boolean(string="According to the amount of sent emails", config_parameter='hr_presence.hr_presence_control_email') + hr_presence_control_ip = fields.Boolean(string="According to the IP address", config_parameter='hr_presence.hr_presence_control_ip') + module_hr_attendance = fields.Boolean(string="According to the Attendance module.") + hr_presence_control_email_amount = fields.Integer(related="company_id.hr_presence_control_email_amount", readonly=False) + hr_presence_control_ip_list = fields.Char(related="company_id.hr_presence_control_ip_list", readonly=False) hr_employee_self_edit = fields.Boolean(string="Employee Edition", config_parameter='hr.hr_employee_self_edit') diff --git a/addons/hr/models/res_users.py b/addons/hr/models/res_users.py index 0e663a8d62b..c64f678070a 100644 --- a/addons/hr/models/res_users.py +++ b/addons/hr/models/res_users.py @@ -52,6 +52,8 @@ class User(models.Model): study_field = fields.Char(related='employee_id.study_field', readonly=False, related_sudo=False) study_school = fields.Char(related='employee_id.study_school', readonly=False, related_sudo=False) employee_count = fields.Integer(compute='_compute_employee_count') + hr_presence_state = fields.Selection(related='employee_id.hr_presence_state') + last_activity = fields.Date(related='employee_id.last_activity') @api.depends('employee_ids') def _compute_employee_count(self): @@ -69,6 +71,8 @@ class User(models.Model): 'employee_id', 'employee_ids', 'employee_parent_id', + 'hr_presence_state', + 'last_activity', ] hr_writable_fields = [ diff --git a/addons/hr/views/hr_employee_public_views.xml b/addons/hr/views/hr_employee_public_views.xml index fa8582f3015..95b277b30f0 100644 --- a/addons/hr/views/hr_employee_public_views.xml +++ b/addons/hr/views/hr_employee_public_views.xml @@ -95,6 +95,7 @@ <field name="arch" type="xml"> <kanban class="o_hr_employee_kanban"> <field name="id"/> + <field name="hr_presence_state"/> <templates> <t t-name="kanban-box"> <div class="oe_kanban_global_click o_kanban_record_has_image_fill o_hr_kanban_record"> @@ -107,6 +108,15 @@ <div class="o_kanban_record_top"> <div class="o_kanban_record_headings"> <strong class="o_kanban_record_title"> + <div class="float-right" t-if="record.hr_presence_state.raw_value == 'present'"> + <span class="fa fa-circle text-success" role="img" aria-label="Present" title="Present" name="presence_present"></span> + </div> + <div class="float-right" t-if="record.hr_presence_state.raw_value == 'absent'"> + <span class="fa fa-circle text-danger" role="img" aria-label="Absent" title="Absent" name="presence_absent"></span> + </div> + <div class="float-right" t-if="record.hr_presence_state.raw_value == 'to_define'"> + <span class="fa fa-circle text-warning" role="img" aria-label="To define" title="To define" name="presence_to_define"></span> + </div> <field name="name"/> </strong> <span t-if="record.job_id.raw_value" class="o_kanban_record_subtitle"><field name="job_id"/></span> diff --git a/addons/hr/views/hr_employee_views.xml b/addons/hr/views/hr_employee_views.xml index d7e52389246..56a96215194 100644 --- a/addons/hr/views/hr_employee_views.xml +++ b/addons/hr/views/hr_employee_views.xml @@ -40,14 +40,37 @@ <form string="Employee" js_class="hr_employee_form"> <field name="active" invisible="1"/> <field name="user_partner_id" invisible="1"/> + <field name="hr_presence_state" invisible="1"/> <header> <button string="Chat" class="btn btn-primary o_employee_chat_btn" attrs="{'invisible': [('user_id','=', False)]}"/> <button name="%(plan_wizard_action)d" string="Launch Plan" type="action" groups="hr.group_hr_manager"/> </header> <sheet> - <widget name="web_ribbon" text="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/> - <div name="button_box" class="oe_button_box"/> - <field name="image" widget='image' class="oe_avatar" options='{"zoom": true, "preview_image":"image_medium"}'/> + <widget name="web_ribbon" text="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/> + <div name="button_box" class="oe_button_box"> + <button + id="hr_presence_button" + class="oe_stat_button" + disabled="1" + attrs="{'invisible': ['|', ('last_activity', '=', False), ('user_id', '=', False)]}"> + <div role="img" class="fa fa-fw fa-circle text-success o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}" aria-label="Available" title="Available"/> + <div role="img" class="fa fa-fw fa-circle text-warning o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'to_define')]}" aria-label="Away" title="Away"/> + <div role="img" class="fa fa-fw fa-circle text-danger o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'absent')]}" aria-label="Not available" title="Not available"/> + + <div class="o_stat_info" attrs="{'invisible': [('hr_presence_state', '=', 'present')]}"> + <span class="o_stat_value"> + <field name="last_activity"/> + </span> + <span class="o_stat_text"> + Last Activity + </span> + </div> + <div class="o_stat_info" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}"> + <span class="o_stat_text">Connected</span> + </div> + </button> + </div> + <field name="image" widget='image' class="oe_avatar" options='{"zoom": true, "preview_image":"image_medium"}'/> <div class="oe_title"> <label for="name" class="oe_edit_only"/> <h1> @@ -204,6 +227,7 @@ <field name="arch" type="xml"> <kanban class="o_hr_employee_kanban"> <field name="id"/> + <field name="hr_presence_state"/> <templates> <t t-name="kanban-box"> <div class="oe_kanban_global_click o_kanban_record_has_image_fill o_hr_kanban_record"> @@ -216,7 +240,16 @@ <div class="o_kanban_record_top"> <div class="o_kanban_record_headings"> <strong class="o_kanban_record_title"> - <field name="name"/> + <div class="float-right" t-if="record.hr_presence_state.raw_value == 'present'"> + <span class="fa fa-circle text-success" role="img" aria-label="Present" title="Present" name="presence_present"></span> + </div> + <div class="float-right" t-if="record.hr_presence_state.raw_value == 'absent'"> + <span class="fa fa-circle text-danger" role="img" aria-label="Absent" title="Absent" name="presence_absent"></span> + </div> + <div class="float-right" t-if="record.hr_presence_state.raw_value == 'to_define'"> + <span class="fa fa-circle text-warning" role="img" aria-label="To define" title="To define" name="presence_to_define"></span> + </div> + <field name="name" placeholder="Employee's Name"/> </strong> <span t-if="record.job_id.raw_value" class="o_kanban_record_subtitle"><field name="job_id"/></span> </div> diff --git a/addons/hr/views/res_config_settings_views.xml b/addons/hr/views/res_config_settings_views.xml index 5cdcf6f679a..62d4f4179f6 100644 --- a/addons/hr/views/res_config_settings_views.xml +++ b/addons/hr/views/res_config_settings_views.xml @@ -20,6 +20,50 @@ </div> </div> </div> + <div class="col-12 col-lg-6 o_setting_box" title="Presence of employees"> + + <div class="o_setting_right_pane"> + <span class="o_form_label">Control presence of employees</span> + <div class="content-group" name="hr_presence_options"> + <div class="row"> + <field name="module_hr_attendance" class="col-lg-1 ml16"/> + <label for="module_hr_attendance" class="col-lg-10 o_light_label"/> + </div> + <div class="row"> + <field name="hr_presence_control_login" class="col-lg-1 ml16"/> + <label for="hr_presence_control_login" class="col-lg-10 o_light_label"/> + </div> + </div> + </div> + </div> + <div class="col-12 col-lg-6 o_setting_box" title="Advanced presence of employees"> + <div class="o_setting_left_pane"> + <field name="module_hr_presence"/> + </div> + <div class="o_setting_right_pane"> + <label for="module_hr_presence"/> + <div class="text-muted" name="hr_presence_options_advanced"> + Presence reporting screen, email and IP address control. + </div> + <div class="row mt-1" attrs="{'invisible': [('module_hr_presence', '=', False)]}"> + <field name="hr_presence_control_email" class="col-lg-1 ml16"/> + <label for="hr_presence_control_email" class="col-lg-10 o_light_label"/> + </div> + <div class="row ml-4" attrs="{'invisible': ['|', ('module_hr_presence', '=', False), ('hr_presence_control_email', '=', False)]}"> + <span class="mr-2">At least </span> + <field name="hr_presence_control_email_amount"/> + <span> sent emails</span> + </div> + <div class="row" attrs="{'invisible': [('module_hr_presence', '=', False)]}"> + <field name="hr_presence_control_ip" class="col-lg-1 ml16"/> + <label for="hr_presence_control_ip" class="col-lg-10 o_light_label"/> + </div> + <div class="row ml-4" attrs="{'invisible': ['|', ('module_hr_presence', '=', False), ('hr_presence_control_ip', '=', False)]}"> + <span class="mr-2">IP List (comma separated):</span> + <field name="hr_presence_control_ip_list"/> + </div> + </div> + </div> </div> <h2>Work Organization</h2> <div class="row mt16 o_settings_container"> diff --git a/addons/hr/views/res_users.xml b/addons/hr/views/res_users.xml index d417b93341b..1eb980c20f3 100644 --- a/addons/hr/views/res_users.xml +++ b/addons/hr/views/res_users.xml @@ -36,9 +36,31 @@ <field name="arch" type="xml"> <form position="replace"> <form> + <field name="hr_presence_state" invisible="1"/> <header></header> <!-- Used by other modules to add buttons --> <sheet> <div class="oe_button_box" name="button_box"> + <button + id="hr_presence_button" + class="oe_stat_button" + disabled="1" + attrs="{'invisible': [('hr_presence_state', '=', 'absent')]}"> + <div role="img" class="fa fa-fw fa-circle text-success o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}" aria-label="Available" title="Available"/> + <div role="img" class="fa fa-fw fa-circle text-warning o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'to_define')]}" aria-label="Away" title="Away"/> + <div role="img" class="fa fa-fw fa-circle text-danger o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'absent')]}" aria-label="Not available" title="Not available"/> + + <div class="o_stat_info" attrs="{'invisible': [('hr_presence_state', '=', 'present')]}"> + <span class="o_stat_value"> + <field name="last_activity"/> + </span> + <span class="o_stat_text"> + Last Activity + </span> + </div> + <div class="o_stat_info" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}"> + <span class="o_stat_text">Connected</span> + </div> + </button> </div> <field name="image" widget='image' class="oe_avatar" options='{"zoom": true, "preview_image":"image_medium"}'/> <div class="oe_title"> diff --git a/addons/hr_attendance/models/hr_employee.py b/addons/hr_attendance/models/hr_employee.py index 62b7def6d2c..fa5d788c210 100644 --- a/addons/hr_attendance/models/hr_employee.py +++ b/addons/hr_attendance/models/hr_employee.py @@ -17,6 +17,20 @@ class HrEmployeeBase(models.AbstractModel): hours_last_month = fields.Float(compute='_compute_hours_last_month') hours_today = fields.Float(compute='_compute_hours_today') + def _compute_presence_state(self): + """ + Override to include checkin/checkout in the presence state + Attendance has the second highest priority after login + """ + super()._compute_presence_state() + employees = self.filtered(lambda employee: employee.hr_presence_state != 'present') + for employee in employees: + if employee.attendance_state == 'checked_out' and employee.hr_presence_state == 'to_define': + employee.hr_presence_state = 'absent' + for employee in employees: + if employee.attendance_state == 'checked_in': + employee.hr_presence_state = 'present' + def _compute_hours_last_month(self): for employee in self: now = datetime.now() diff --git a/addons/hr_attendance/views/hr_employee_view.xml b/addons/hr_attendance/views/hr_employee_view.xml index f03fddc1730..ad1d073dd60 100644 --- a/addons/hr_attendance/views/hr_employee_view.xml +++ b/addons/hr_attendance/views/hr_employee_view.xml @@ -4,14 +4,24 @@ <field name="name">hr.employee</field> <field name="model">hr.employee</field> <field name="inherit_id" ref="hr.view_employee_form"/> - <field name="priority">10</field> + <field name="priority">20</field> <field name="groups_id" eval="[(4,ref('hr_attendance.group_hr_attendance_user'))]"/> <field name="arch" type="xml"> - <div name="button_box" position="inside"> + <xpath expr="//button[@id='hr_presence_button']" position="attributes"> + <attribute name="attrs"> + {'invisible': ['|', '|', ('user_id', '=', False), ('hr_presence_state', '=', 'absent'), ('attendance_state', '=', 'checked_in')]} + </attribute> + </xpath> + <xpath expr="//div[@name='button_box']" position="inside"> <field name="attendance_state" invisible="1"/> <button name="%(hr_attendance_action_employee)d" + id="hr_attendance_button" class="oe_stat_button" - type="action" attrs="{'invisible': [('attendance_state', '=', False)]}"> + type="action" attrs="{'invisible': [ + '|', ('attendance_state', '=', False), + '&', + ('hr_presence_state', '=', 'present'), + ('attendance_state', '=', 'checked_out')]}"> <div role="img" id="oe_hr_attendance_status" class="fa fa-fw fa-circle o_button_icon oe_hr_attendance_status_green" attrs="{'invisible': [('attendance_state', '=', 'checked_out')]}" aria-label="Available" title="Available"/> <div role="img" id="oe_hr_attendance_status" class="fa fa-fw fa-circle o_button_icon oe_hr_attendance_status_red" attrs="{'invisible': [('attendance_state', '=', 'checked_in')]}" aria-label="Not available" title="Not available"/> <div class="o_stat_info"> @@ -34,7 +44,7 @@ </span> </div> </button> - </div> + </xpath> </field> </record> @@ -43,11 +53,17 @@ <field name="model">res.users</field> <field name="inherit_id" ref="hr.res_users_view_form_profile"/> <field name="arch" type="xml"> + <xpath expr="//button[@id='hr_presence_button']" position="attributes"> + <attribute name="attrs"> + {'invisible': ['|', '|', ('hr_presence_state', '=', 'absent'), ('attendance_state', '=', 'checked_in')]} + </attribute> + </xpath> <xpath expr="//div[@name='button_box']" position="inside"> <field name="attendance_state" invisible="1"/> <button name="%(hr_attendance_action_my_attendances)d" + id="hr_attendance_button" class="oe_stat_button" - type="action" attrs="{'invisible': [('attendance_state', '=', False)]}"> + type="action" attrs="{'invisible': ['|', ('attendance_state', '=', False), '&', ('hr_presence_state', '=', 'present'), ('attendance_state', '=', 'checked_out')]}"> <div role="img" id="oe_hr_attendance_status" class="fa fa-fw fa-circle o_button_icon oe_hr_attendance_status_green" attrs="{'invisible': [('attendance_state', '=', 'checked_out')]}" aria-label="Available" title="Available"/> <div role="img" id="oe_hr_attendance_status" class="fa fa-fw fa-circle o_button_icon oe_hr_attendance_status_red" attrs="{'invisible': [('attendance_state', '=', 'checked_in')]}" aria-label="Not available" title="Not available"/> <div class="o_stat_info"> @@ -131,31 +147,6 @@ </field> </record> - <record id="view_employee_kanban_inherit_hr_attendance" model="ir.ui.view"> - <field name="name">hr.employee</field> - <field name="model">hr.employee</field> - <field name="inherit_id" ref="hr.hr_kanban_view_employees"/> - <field name="priority">1</field> - <field name="arch" type="xml"> - <templates position="before"> - <field name="attendance_state"/> - </templates> - <field name="name" position="replace"> - <div> - <div class="float-right" t-if="record.attendance_state.raw_value == 'checked_in'"> - <span id="oe_hr_attendance_status" class="fa fa-circle oe_hr_attendance_status_green" role="img" aria-label="Available" title="Available"></span> - </div> - <div class="float-right" t-if="record.attendance_state.raw_value == 'checked_out'"> - <span id="oe_hr_attendance_status" class="fa fa-circle oe_hr_attendance_status_red" role="img" aria-label="Not available" title="Not available"></span> - </div> - <strong> - <field name="name" placeholder="Employee's Name"/> - </strong> - </div> - </field> - </field> - </record> - <record id="hr_employee_attendance_action_kanban" model="ir.actions.act_window"> <field name="name">Employees</field> <field name="res_model">hr.employee</field> diff --git a/addons/hr_attendance_presence/__init__.py b/addons/hr_attendance_presence/__init__.py deleted file mode 100644 index 0650744f6bc..00000000000 --- a/addons/hr_attendance_presence/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import models diff --git a/addons/hr_attendance_presence/__manifest__.py b/addons/hr_attendance_presence/__manifest__.py deleted file mode 100644 index da31006ddb7..00000000000 --- a/addons/hr_attendance_presence/__manifest__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -{ - 'name': 'Attendance Presence', - 'version': '1.0', - 'category': 'Human Resources', - 'sequence': 85, - 'summary': 'Bridge Attendance module and Presence module', - 'description': "", - 'website': 'https://www.odoo.com/page/employees', - 'depends': ['hr_attendance', 'hr_presence'], - 'installable': True, - 'auto_install': True, - 'data': [ - 'views/res_config_settings_views.xml', - 'views/hr_employee.xml', - ], -} diff --git a/addons/hr_attendance_presence/models/__init__.py b/addons/hr_attendance_presence/models/__init__.py deleted file mode 100644 index 5d68338dfc7..00000000000 --- a/addons/hr_attendance_presence/models/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import hr_employee -from . import res_config_settings diff --git a/addons/hr_attendance_presence/models/hr_employee.py b/addons/hr_attendance_presence/models/hr_employee.py deleted file mode 100644 index c1dc644cac7..00000000000 --- a/addons/hr_attendance_presence/models/hr_employee.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import logging - -from odoo import models - -_logger = logging.getLogger(__name__) - - -class Employee(models.Model): - _inherit = 'hr.employee' - - def _action_open_presence_view(self): - action = super(Employee, self)._action_open_presence_view() - - if self.env['ir.config_parameter'].sudo().get_param('hr_presence.hr_presence_control_attendance'): - company = self.env.company - employees = self.env['hr.employee'].search([ - ('department_id.company_id', '=', company.id), - ('user_id', '!=', False), - ]) - - employees.filtered( - lambda e: e.attendance_state == 'checked_in' - ).write({'hr_presence_state': 'present'}) - - employees.filtered( - lambda e: e.attendance_state == 'checked_out' - ).write({'hr_presence_state': 'absent'}) - - return action diff --git a/addons/hr_attendance_presence/models/res_config_settings.py b/addons/hr_attendance_presence/models/res_config_settings.py deleted file mode 100644 index a9bbd8f64b3..00000000000 --- a/addons/hr_attendance_presence/models/res_config_settings.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- - -from odoo import fields, models - - -class ResConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' - - hr_presence_control_attendance = fields.Boolean(string="According to the Attendance module", config_parameter='hr_presence.hr_presence_control_attendance') diff --git a/addons/hr_attendance_presence/views/hr_employee.xml b/addons/hr_attendance_presence/views/hr_employee.xml deleted file mode 100644 index a645e594b20..00000000000 --- a/addons/hr_attendance_presence/views/hr_employee.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<odoo> - - <record id="hr_employee_view_kanban" model="ir.ui.view"> - <field name="name">hr.employee</field> - <field name="model">hr.employee</field> - <field name="inherit_id" ref="hr_presence.hr_employee_view_kanban_inherit"/> - <field name="priority">20</field> - <field name="arch" type="xml"> - <xpath expr="//span[@name='presence_absent']" position="attributes"> - <attribute name="invisible">1</attribute> - </xpath> - <xpath expr="//span[@name='presence_present']" position="attributes"> - <attribute name="invisible">1</attribute> - </xpath> - <xpath expr="//span[@name='presence_to_define']" position="attributes"> - <attribute name="invisible">1</attribute> - </xpath> - </field> - </record> -</odoo> diff --git a/addons/hr_attendance_presence/views/res_config_settings_views.xml b/addons/hr_attendance_presence/views/res_config_settings_views.xml deleted file mode 100644 index ef6ab9142e6..00000000000 --- a/addons/hr_attendance_presence/views/res_config_settings_views.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<odoo> - <record id="res_config_settings_view_form" model="ir.ui.view"> - <field name="name">res.config.settings.view.form.inherit.presence.hr</field> - <field name="model">res.config.settings</field> - <field name="priority" eval="70"/> - <field name="inherit_id" ref="hr.res_config_settings_view_form"/> - <field name="arch" type="xml"> - <xpath expr="//div[@name='hr_presence_options']" position="inside"> - <div class="row"> - <field name="hr_presence_control_attendance" class="col-lg-1 ml16"/> - <label for="hr_presence_control_attendance" class="col-lg-10 o_light_label"/> - </div> - </xpath> - </field> - </record> -</odoo> diff --git a/addons/hr_holidays/models/hr_employee.py b/addons/hr_holidays/models/hr_employee.py index c0a47809e04..a79ad7eb307 100644 --- a/addons/hr_holidays/models/hr_employee.py +++ b/addons/hr_holidays/models/hr_employee.py @@ -93,6 +93,11 @@ class HrEmployeeBase(models.AbstractModel): for employee in self: employee.allocation_used_count = employee.allocation_count - employee.remaining_leaves + def _compute_presence_state(self): + super()._compute_presence_state() + employees = self.filtered(lambda employee: employee.hr_presence_state != 'present' and employee.is_absent) + employees.update({'hr_presence_state': 'absent'}) + @api.multi def _compute_leave_status(self): # Used SUPERUSER_ID to forcefully get status of other user's leave, to bypass record rule diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 4685e543a0c..8deda82c3f4 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -793,7 +793,6 @@ class HolidaysRequest(models.Model): # FIXME RLi: This does not make sense, only the parent should be in validation_type both if leaves and leaves[0].validation_type == 'both': leaves.action_validate() - employee_requests = self.filtered(lambda hol: hol.holiday_type == 'employee') employee_requests._validate_leave_request() if not self.env.context.get('leave_fast_create'): diff --git a/addons/hr_holidays/views/hr_views.xml b/addons/hr_holidays/views/hr_views.xml index c7da3c7eea1..48f28f883b8 100644 --- a/addons/hr_holidays/views/hr_views.xml +++ b/addons/hr_holidays/views/hr_views.xml @@ -135,6 +135,11 @@ <field name="model">hr.employee</field> <field name="inherit_id" ref="hr.view_employee_form"/> <field name="arch" type="xml"> + <xpath expr="//button[@id='hr_presence_button']" position="attributes"> + <attribute name="attrs"> + {'invisible': ['|', '|', '|', ('last_activity', '=', False), ('is_absent', '=', True), ('user_id', '=', False), ('id', '=', False)]} + </attribute> + </xpath> <xpath expr="//group[@name='managers']" position="inside"> <field name="leave_manager_id"/> </xpath> @@ -142,10 +147,11 @@ <field name="current_leave_id" invisible="1"/> <field name="show_leaves" invisible="1"/> <field name="is_absent" invisible="1"/> - <button name="%(hr_leave_action_new_request)d" type="action" + <button disabled="1" class="oe_stat_button" + context="{'search_default_employee_id': active_id}" attrs="{'invisible': [('is_absent', '=', False)]}"> - <div role="img" class="fa fa-fw fa-circle o_button_icon text-danger" aria-label="Absent Until" title="Absent Until"/> + <div role="img" class="fa fa-fw fa-plane o_button_icon text-danger" aria-label="Absent Until" title="Absent Until"/> <div class="o_field_widget o_stat_info"> <span class="o_stat_value"> <field name="leave_date_to"/> @@ -160,6 +166,7 @@ class="oe_stat_button" icon="fa-calendar" attrs="{'invisible': [('show_leaves','=', False)]}" + context="{'search_default_employee_id': active_id}" groups="base.group_user" help="Remaining leaves"> <div class="o_field_widget o_stat_info"> @@ -188,10 +195,10 @@ <field name="current_leave_id" invisible="1"/> <field name="show_leaves" invisible="1"/> <field name="is_absent" invisible="1"/> - <button name="%(hr_leave_action_new_request)d" type="action" + <button disabled="1" class="oe_stat_button" attrs="{'invisible': [('is_absent', '=', False)]}"> - <div role="img" class="fa fa-fw fa-circle o_button_icon text-danger" aria-label="Absent Until" title="Absent Until"/> + <div role="img" class="fa fa-fw fa-plane o_button_icon text-danger" aria-label="Absent Until" title="Absent Until"/> <div class="o_field_widget o_stat_info"> <span class="o_stat_value"> <field name="leave_date_to"/> @@ -232,7 +239,7 @@ <button name="%(hr_leave_action_new_request)d" type="action" class="oe_stat_button" attrs="{'invisible': [('is_absent', '=', False)]}"> - <div role="img" class="fa fa-fw fa-circle o_button_icon text-danger" aria-label="Absent Until" title="Absent Until"/> + <div role="img" class="fa fa-fw fa-plane o_button_icon text-danger" aria-label="Absent Until" title="Absent Until"/> <div class="o_field_widget o_stat_info"> <span class="o_stat_value"> <field name="leave_date_to"/> diff --git a/addons/hr_holidays_attendance/__init__.py b/addons/hr_holidays_attendance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/addons/hr_holidays_attendance/__init__.py diff --git a/addons/hr_holidays_attendance/__manifest__.py b/addons/hr_holidays_attendance/__manifest__.py new file mode 100644 index 00000000000..b20f356c401 --- /dev/null +++ b/addons/hr_holidays_attendance/__manifest__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +{ + 'name': "HR Attendance Holidays", + 'summary': """""", + 'category': 'HR', + 'description': """ + Hides attendance presence stat button when an employee is on leave. + """, + 'version': '1.0', + 'depends': ['hr_attendance', 'hr_holidays'], + 'auto_install': True, + 'data': [ + 'views/hr_employee_views.xml', + ], +} diff --git a/addons/hr_holidays_attendance/views/hr_employee_views.xml b/addons/hr_holidays_attendance/views/hr_employee_views.xml new file mode 100644 index 00000000000..53cc6d53e90 --- /dev/null +++ b/addons/hr_holidays_attendance/views/hr_employee_views.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + + <record id="hr_user_view_form" model="ir.ui.view"> + <field name="name">hr.user.preferences.view.form.attendance.inherit</field> + <field name="model">res.users</field> + <field name="inherit_id" ref="hr_attendance.hr_user_view_form"/> + <field name="arch" type="xml"> + <!-- Hide Attendance button --> + <xpath expr="//button[@id='hr_attendance_button']" position="attributes"> + <attribute name="attrs"> + {'invisible': ['|', '|', '&', + ('hr_presence_state', '=', 'present'), + ('attendance_state', '=', 'checked_out'), + ('is_absent', '=', True), + ('id', '=', False), + ]} + </attribute> + </xpath> + <!-- Merge invisible attr of both module --> + <xpath expr="//button[@id='hr_presence_button']" position="attributes"> + <attribute name="attrs"> + {'invisible': ['|', '|', '|', + ('is_absent', '=', True), + ('hr_presence_state', '=', 'absent'), + ('attendance_state', '=', 'checked_in'), + ('id', '=', False), + ]} + </attribute> + </xpath> + </field> + </record> + + <record id="hr_employee_view_form" model="ir.ui.view"> + <field name="name">hr.employee.holidays.attendance.inherit</field> + <field name="model">hr.employee</field> + <field name="inherit_id" ref="hr_attendance.view_employee_form_inherit_hr_attendance"/> + <field name="arch" type="xml"> + <!-- Hide Attendance button --> + <xpath expr="//button[@id='hr_attendance_button']" position="attributes"> + <attribute name="attrs"> + {'invisible': ['|', '|', '&', + ('hr_presence_state', '=', 'present'), + ('attendance_state', '=', 'checked_out'), + ('is_absent', '=', True), + ('id', '=', False), + ]} + </attribute> + </xpath> + <!-- Merge invisible attr of both module --> + <xpath expr="//button[@id='hr_presence_button']" position="attributes"> + <attribute name="attrs"> + {'invisible': ['|', '|', '|', + ('is_absent', '=', True), + ('hr_presence_state', '=', 'absent'), + ('attendance_state', '=', 'checked_in'), + ('id', '=', False), + ]} + </attribute> + </xpath> + </field> + </record> + +</odoo> diff --git a/addons/hr_presence/__manifest__.py b/addons/hr_presence/__manifest__.py index d0fdba48fff..05b27fca5ca 100644 --- a/addons/hr_presence/__manifest__.py +++ b/addons/hr_presence/__manifest__.py @@ -18,7 +18,6 @@ Allows to contact directly the employee in case of unjustified absence. """, 'depends': ['hr', 'hr_holidays', 'sms'], 'data': [ - 'views/res_config_settings_views.xml', 'data/ir_actions_server.xml', 'views/hr_employee_views.xml', 'data/sms_data.xml', diff --git a/addons/hr_presence/models/__init__.py b/addons/hr_presence/models/__init__.py index 996b19c0a45..76aa02054b2 100644 --- a/addons/hr_presence/models/__init__.py +++ b/addons/hr_presence/models/__init__.py @@ -3,5 +3,4 @@ from . import hr_employee from . import res_company -from . import res_config_settings from . import res_users_log diff --git a/addons/hr_presence/models/hr_employee.py b/addons/hr_presence/models/hr_employee.py index 2f0c55a9571..9860fa0ec1b 100644 --- a/addons/hr_presence/models/hr_employee.py +++ b/addons/hr_presence/models/hr_employee.py @@ -10,21 +10,28 @@ from odoo.fields import Datetime _logger = logging.getLogger(__name__) -class ResCompany(models.Model): - _inherit = 'hr.employee' +class Employee(models.AbstractModel): + _inherit = 'hr.employee.base' - hr_presence_state = fields.Selection([ + email_sent = fields.Boolean(default=False) + ip_connected = fields.Boolean(default=False) + manually_set_present = fields.Boolean(default=False) + + # Stored field used in the presence kanban reporting view + # to allow group by state. + hr_presence_state_display = fields.Selection([ ('present', 'Present'), ('absent', 'Absent'), - ('to_define', 'To Define')], groups="hr.group_hr_user", default='to_define') - last_activity = fields.Date(compute="_compute_last_activity") - - def _compute_last_activity(self): - employees = self.filtered(lambda e: e.user_id) - presences = self.env['bus.presence'].search([('user_id', 'in', employees.mapped('user_id.id'))]) + ('to_define', 'To Define')]) - for presence in presences: - presence.user_id.employee_ids.last_activity = presence.last_presence.date() + def _compute_presence_state(self): + super()._compute_presence_state() + employees = self.filtered(lambda employee: employee.hr_presence_state != 'present' and not employee.is_absent) + company = self.env.company + for employee in employees: + if not employee.is_absent and company.hr_presence_last_compute_date and company.hr_presence_last_compute_date.day == Datetime.now().day and \ + (employee.email_sent or employee.ip_connected or employee.manually_set_present): + employee.hr_presence_state = 'present' @api.model def _check_presence(self): @@ -32,28 +39,16 @@ class ResCompany(models.Model): if not company.hr_presence_last_compute_date or \ company.hr_presence_last_compute_date.day != Datetime.now().day: self.env['hr.employee'].search([ - ('department_id.company_id', '=', company.id) - ]).write({'hr_presence_state': 'to_define'}) - - employees = self.env['hr.employee'].search([ - ('department_id.company_id', '=', company.id), - ('user_id', '!=', False), - ('hr_presence_state', '=', 'to_define')]) - - # Remove employees on holidays - leaves = self.env['hr.leave'].search([ - ('state', '=', 'validate'), - ('date_from', '<=', Datetime.to_string(Datetime.now())), - ('date_to', '>=', Datetime.to_string(Datetime.now()))]) - employees_on_holiday = leaves.mapped('employee_id') - employees_on_holiday.write({'hr_presence_state': 'absent'}) - employees = employees - employees_on_holiday - - # Check on system login - if self.env['ir.config_parameter'].sudo().get_param('hr_presence.hr_presence_control_login'): - online_employees = employees.filtered(lambda employee: employee.user_id.im_status in ['away', 'online']) - online_employees.write({'hr_presence_state': 'present'}) - employees = employees - online_employees + ('company_id', '=', company.id) + ]).write({ + 'email_sent': False, + 'ip_connected': False, + 'manually_set_present': False + }) + + employees = self.env['hr.employee'].search([('company_id', '=', company.id)]) + all_employees = employees + # Check on IP if self.env['ir.config_parameter'].sudo().get_param('hr_presence.hr_presence_control_ip'): @@ -68,7 +63,7 @@ class ResCompany(models.Model): ).mapped('ip') if any([ip in ip_list for ip in employee_ips]): ip_employees |= employee - ip_employees.write({'hr_presence_state': 'present'}) + ip_employees.write({'ip_connected': True}) employees = employees - ip_employees # Check on sent emails @@ -82,10 +77,13 @@ class ResCompany(models.Model): ('date', '<=', Datetime.to_string(Datetime.now()))]) if sent_emails >= threshold: email_employees |= employee - email_employees.write({'hr_presence_state': 'present'}) + email_employees.write({'email_sent': True}) employees = employees - email_employees - company.hr_presence_last_compute_date = Datetime.now() + company.sudo().hr_presence_last_compute_date = Datetime.now() + + for employee in all_employees: + employee.hr_presence_state_display = employee.hr_presence_state @api.model def _action_open_presence_view(self): @@ -110,7 +108,12 @@ class ResCompany(models.Model): def action_set_present(self): if not self.env.user.has_group('hr.group_hr_manager'): raise UserError(_("You don't have the right to do this. Please contact an Administrator.")) - self.write({'hr_presence_state': 'present'}) + self.write({'manually_set_present': True}) + + def write(self, vals): + if vals.get('hr_presence_state_display') == 'present': + vals['manually_set_present'] = True + return super().write(vals) def action_open_leave_request(self): self.ensure_one() diff --git a/addons/hr_presence/models/res_company.py b/addons/hr_presence/models/res_company.py index 7de70604a92..834fd26cf21 100644 --- a/addons/hr_presence/models/res_company.py +++ b/addons/hr_presence/models/res_company.py @@ -7,7 +7,4 @@ from odoo import fields, models class ResCompany(models.Model): _inherit = 'res.company' - - hr_presence_control_email_amount = fields.Integer(string="# emails to send") - hr_presence_control_ip_list = fields.Char(string="Valid IP addresses") hr_presence_last_compute_date = fields.Datetime() diff --git a/addons/hr_presence/models/res_config_settings.py b/addons/hr_presence/models/res_config_settings.py deleted file mode 100644 index e831d7506bb..00000000000 --- a/addons/hr_presence/models/res_config_settings.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -from odoo import fields, models - - -class ResConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' - - hr_presence_control_login = fields.Boolean(string="According to the system login (User status on chat)", config_parameter='hr_presence.hr_presence_control_login') - hr_presence_control_email = fields.Boolean(string="According to the amount of sent emails", config_parameter='hr_presence.hr_presence_control_email') - hr_presence_control_ip = fields.Boolean(string="According to the IP address", config_parameter='hr_presence.hr_presence_control_ip') - hr_presence_control_email_amount = fields.Integer(related="company_id.hr_presence_control_email_amount", readonly=False) - hr_presence_control_ip_list = fields.Char(related="company_id.hr_presence_control_ip_list", readonly=False) diff --git a/addons/hr_presence/views/hr_employee_views.xml b/addons/hr_presence/views/hr_employee_views.xml index 6f226b3dc71..61cb26184f8 100644 --- a/addons/hr_presence/views/hr_employee_views.xml +++ b/addons/hr_presence/views/hr_employee_views.xml @@ -7,42 +7,11 @@ <field name="groups_id" eval="[(4, ref('hr.group_hr_manager'))]"/> <field name="arch" type="xml"> <filter name="group_manager" position="after"> - <filter name="group_hr_presence_state" string="Presence" domain="[]" context="{'group_by':'hr_presence_state'}"/> + <filter name="group_hr_presence_state" string="Presence" domain="[]" context="{'group_by':'hr_presence_state_display'}"/> </filter> </field> </record> - <record id="hr_employee_view_form" model="ir.ui.view"> - <field name="name">hr.employee.presence.form.inherit</field> - <field name="model">hr.employee</field> - <field name="inherit_id" ref="hr.view_employee_form"/> - <field name="groups_id" eval="[(4, ref('hr.group_hr_manager'))]"/> - <field name="arch" type="xml"> - <div name="button_box" position="inside"> - <field name="hr_presence_state" invisible="1"/> - <button name="%(ir_actions_server_action_open_presence_view)d" - class="oe_stat_button" - type="action" - attrs="{'invisible': ['|', ('hr_presence_state', '=', 'absent'), ('user_id', '=', False)]}"> - <div role="img" class="fa fa-fw fa-circle text-success o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}" aria-label="Available" title="Available"/> - <div role="img" class="fa fa-fw fa-circle text-warning o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'to_define')]}" aria-label="Not available" title="Not available"/> - - <div class="o_stat_info" attrs="{'invisible': [('hr_presence_state', '!=', 'to_define')]}"> - <span class="o_stat_value"> - <field name="last_activity"/> - </span> - <span class="o_stat_text"> - Last Activity - </span> - </div> - <div class="o_stat_info" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}"> - <span class="o_stat_text">Connected</span> - </div> - </button> - </div> - </field> - </record> - <record id="hr_employee_view_kanban" model="ir.ui.view"> <field name="name">hr.employee.view.kanban</field> <field name="model">hr.employee</field> @@ -54,7 +23,7 @@ <div class="o_kanban_record_bottom"> <div class="oe_kanban_bottom_left"> <field name="hr_presence_state" invisible="1"/> - <button name="action_set_present" type="object" class="btn btn-primary btn-sm mt8 fa fa-check" title="Set as present" attrs="{'invisible': [('hr_presence_state', '!=', 'to_define')]}"> </button> + <button name="action_set_present" type="object" class="btn btn-primary btn-sm mt8 fa fa-check" title="Set as present" attrs="{'invisible': [('hr_presence_state', '=', 'present')]}"> </button> <button name="action_open_leave_request" type="object" class="btn btn-primary btn-sm mt8">Time Off</button> <button name="action_send_sms" type="object" class="btn btn-primary btn-sm mt8">SMS</button> <button name="action_send_mail" type="object" class="btn btn-primary btn-sm mt8">Mail</button> @@ -64,31 +33,4 @@ </field> </record> - <record id="hr_employee_view_kanban_inherit" model="ir.ui.view"> - <field name="name">hr.employee</field> - <field name="model">hr.employee</field> - <field name="inherit_id" ref="hr.hr_kanban_view_employees"/> - <field name="priority">1</field> - <field name="arch" type="xml"> - <templates position="before"> - <field name="hr_presence_state"/> - </templates> - <field name="name" position="replace"> - <div> - <div class="float-right" t-if="record.hr_presence_state.raw_value == 'present'"> - <span class="fa fa-circle text-success" role="img" aria-label="Present" title="Present" name="presence_present"></span> - </div> - <div class="float-right" t-if="record.hr_presence_state.raw_value == 'absent'"> - <span class="fa fa-circle text-danger" role="img" aria-label="Absent" title="Absent" name="presence_absent"></span> - </div> - <div class="float-right" t-if="record.hr_presence_state.raw_value == 'to_define'"> - <span class="fa fa-circle text-warning" role="img" aria-label="To define" title="To define" name="presence_to_define"></span> - </div> - <strong> - <field name="name" placeholder="Employee's Name"/> - </strong> - </div> - </field> - </field> - </record> </odoo> diff --git a/addons/hr_presence/views/res_config_settings_views.xml b/addons/hr_presence/views/res_config_settings_views.xml deleted file mode 100644 index 9f447753925..00000000000 --- a/addons/hr_presence/views/res_config_settings_views.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<odoo> - <record id="res_config_settings_view_form" model="ir.ui.view"> - <field name="name">res.config.settings.view.form.inherit.hr</field> - <field name="model">res.config.settings</field> - <field name="priority" eval="70"/> - <field name="inherit_id" ref="hr.res_config_settings_view_form"/> - <field name="arch" type="xml"> - <xpath expr="//div[@data-key='hr']//div[hasclass('o_settings_container')]" position="inside"> - <div class="col-12 col-lg-6 o_setting_box" title="Control presence of employees"> - <div class="o_setting_left_pane"> - <field name="module_hr_presence"/> - </div> - <div class="o_setting_right_pane"> - <label for="module_hr_presence"/> - <div class="content-group" name="hr_presence_options"> - <div class="row"> - <field name="hr_presence_control_login" class="col-lg-1 ml16"/> - <label for="hr_presence_control_login" class="col-lg-10 o_light_label"/> - </div> - <div class="row"> - <field name="hr_presence_control_email" class="col-lg-1 ml16"/> - <label for="hr_presence_control_email" class="col-lg-10 o_light_label"/> - </div> - <div attrs="{'invisible': [('hr_presence_control_email', '=', False)]}"> - <span>At least </span> - <field name="hr_presence_control_email_amount"/> - <span> sent emails</span> - </div> - <div class="row"> - <field name="hr_presence_control_ip" class="col-lg-1 ml16"/> - <label for="hr_presence_control_ip" class="col-lg-10 o_light_label"/> - </div> - <div attrs="{'invisible': [('hr_presence_control_ip', '=', False)]}"> - <span>IP List (comma separated):</span> - <field name="hr_presence_control_ip_list"/> - </div> - </div> - </div> - </div> - </xpath> - </field> - </record> -</odoo> |
