Initial commit
|
@ -0,0 +1,4 @@
|
||||||
|
Employee Time Clock
|
||||||
|
======================
|
||||||
|
|
||||||
|
Find all informations here: <https://apps.openerp.com/apps/modules/8.0/hr_employee_time_clock>
|
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
import controllers
|
||||||
|
import models
|
||||||
|
import wizard
|
||||||
|
import report
|
|
@ -0,0 +1,43 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': "Employee time clock",
|
||||||
|
'author': "Bytebrand GmbH",
|
||||||
|
'summary': 'Track over- and under-time, generate timesheets, upload public holidays',
|
||||||
|
'website': "http://www.bytebrand.net",
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'version': '8.0.3.0.0',
|
||||||
|
'depends': ['hr_timesheet_sheet', 'hr_attendance', 'hr_contract', 'hr_holidays'], #,'hr_attendance_analysis'
|
||||||
|
'images': ['images/overundertime.png'],
|
||||||
|
'installable': True,
|
||||||
|
'data': [
|
||||||
|
'security/ir_rule.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/views.xml',
|
||||||
|
# Report
|
||||||
|
'report/report_attendance_analysis_view.xml',
|
||||||
|
# View file for the wizard
|
||||||
|
'wizard/create_timesheet_with_tag_view.xml',
|
||||||
|
'wizard/import_leave_requests_view.xml',
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import main
|
|
@ -0,0 +1,42 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
from openerp import http
|
||||||
|
|
||||||
|
# class HrTimesheetOvertime(http.Controller):
|
||||||
|
# @http.route('/hr_timesheet_overtime/hr_timesheet_overtime/', auth='public')
|
||||||
|
# def index(self, **kw):
|
||||||
|
# return "Hello, world"
|
||||||
|
|
||||||
|
# @http.route('/hr_timesheet_overtime/hr_timesheet_overtime/objects/', auth='public')
|
||||||
|
# def list(self, **kw):
|
||||||
|
# return http.request.render('hr_timesheet_overtime.listing', {
|
||||||
|
# 'root': '/hr_timesheet_overtime/hr_timesheet_overtime',
|
||||||
|
# 'objects': http.request.env['hr_timesheet_overtime.hr_timesheet_overtime'].search([]),
|
||||||
|
# })
|
||||||
|
|
||||||
|
# @http.route('/hr_timesheet_overtime/hr_timesheet_overtime/objects/<model("hr_timesheet_overtime.hr_timesheet_overtime"):obj>/', auth='public')
|
||||||
|
# def object(self, obj, **kw):
|
||||||
|
# return http.request.render('hr_timesheet_overtime.object', {
|
||||||
|
# 'object': obj
|
||||||
|
# })
|
|
@ -0,0 +1,160 @@
|
||||||
|
# German translation for hr_employee_time_clock
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:hr_timesheet_sheet.sheet,calculate_diff_hours:0
|
||||||
|
#: model:ir.model,name:hr_employee_time_clock.model_hr_timesheet_sheet_sheet
|
||||||
|
#: view:hr_timesheet_sheet.sheet:hr_employee_time_clock.timesheet_overtime
|
||||||
|
msgid "Total balance"
|
||||||
|
msgstr "Total Saldo"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:hr_timesheet_sheet.sheet,total_duty_hours:0
|
||||||
|
#: model:ir.model,name:hr_employee_time_clock.model_hr_timesheet_sheet_sheet
|
||||||
|
#: view:hr_timesheet_sheet.sheet:hr_employee_time_clock.timesheet_overtime
|
||||||
|
msgid "Remaining this timesheet"
|
||||||
|
msgstr "Verbleibend diese Zeiterfassung"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:hr_timesheet_sheet.sheet,prev_timesheet_diff:0
|
||||||
|
#: model:ir.model,name:hr_employee_time_clock.model_hr_timesheet_sheet_sheet
|
||||||
|
#: view:hr_timesheet_sheet.sheet:hr_employee_time_clock.timesheet_overtime
|
||||||
|
msgid "Last timesheets"
|
||||||
|
msgstr "Letzte Zeiterfassung"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:hr_timesheet_sheet.sheet,total_attendance:0
|
||||||
|
#: model:ir.model,name:hr_employee_time_clock.model_hr_timesheet_sheet_sheet
|
||||||
|
#: view:hr_timesheet_sheet.sheet:hr_employee_time_clock.timesheet_overtime
|
||||||
|
msgid "Attendance this timesheet"
|
||||||
|
msgstr "Anwesenheit diese Zeiterfassung"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:hr_timesheet_sheet.sheet,analysis:0
|
||||||
|
msgid "Attendance Analysis"
|
||||||
|
msgstr "Anwesenheit Analyse"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: view:hr_timesheet_sheet.sheet:hr_employee_time_clock.timesheet_overtime
|
||||||
|
msgid "Overtime Analysis"
|
||||||
|
msgstr "Überzeit Analyse"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: view:hr.timesheet.current.open:hr_employee_time_clock.view_hr_timesheet_current_open_inherit
|
||||||
|
msgid "Generate timesheets for employees having the following tag:"
|
||||||
|
msgstr "Generiere Zeiterfassungen für Mitarbeiter mit folgendem Tag:"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:hr.timesheet.current.open,category_id:0
|
||||||
|
#: model:ir.model,name:hr.model_hr_employee_category
|
||||||
|
msgid "Employee Tag"
|
||||||
|
msgstr "Mitarbeiter Tag"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:hr.timesheet.current.open,date_from:0
|
||||||
|
msgid "Start Date"
|
||||||
|
msgstr "Startdatum"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:hr.timesheet.current.open,date_to:0
|
||||||
|
msgid "End Date"
|
||||||
|
msgstr "Enddatum"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/wizard/create_timesheet_with_tag.py:44
|
||||||
|
#, python-format
|
||||||
|
msgid "Timesheet already exists for %s."
|
||||||
|
msgstr "Zeiterfassung existiert bereits für %s."
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/wizard/import_leave_requests.py:31
|
||||||
|
#, python-format
|
||||||
|
msgid "Data Error!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/wizard/import_leave_requests.py:31
|
||||||
|
#, python-format
|
||||||
|
msgid "Date format in your .csv file does not match with database date format."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: view:import.leave.requests:hr_employee_time_clock.view_import_leave_requests
|
||||||
|
msgid "Generate timesheets for employees having the following tag:"
|
||||||
|
msgstr "Generiere Zeiterfassungen für Mitarbeiter mit folgendem Tag:"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:import.leave.requests,leave_dates:0
|
||||||
|
msgid "Select *.csv"
|
||||||
|
msgstr "Wähle *.csv"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:import.leave.requests,leave_type_id:0
|
||||||
|
#: model:ir.model,name:hr_holidays.model_hr_holidays_status
|
||||||
|
#: view:import.leave.requests:hr_employee_time_clock.view_import_leave_requests
|
||||||
|
msgid "Leave Type"
|
||||||
|
msgstr "Abwesenheitstyp"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: field:import.leave.requests,employee_tag_id:0
|
||||||
|
#: model:ir.model,name:hr.model_hr_employee_category
|
||||||
|
#: view:import.leave.requests:hr_employee_time_clock.view_import_leave_requests
|
||||||
|
msgid "Employee Tag"
|
||||||
|
msgstr "Mitarbeiter Tag"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: view:import.leave.requests:hr_employee_time_clock.view_import_leave_requests
|
||||||
|
#: model:ir.actions.act_window,name:hr_employee_time_clock.action_import_leave_requests
|
||||||
|
#: model:ir.ui.menu,name:hr_employee_time_clock.menu_import_leave_requests
|
||||||
|
msgid "Import Leave Requests"
|
||||||
|
msgstr "Importiere Abwesenheiten"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: view:hr.timesheet.current.open:hr_employee_time_clock.view_hr_timesheet_current_open_inherit
|
||||||
|
#: model:ir.ui.menu,name:hr_employee_time_clock.menu_act_hr_timesheet_sheet_form_open_current
|
||||||
|
msgid "Generate Timesheets"
|
||||||
|
msgstr "Generiere Zeiterfassungen"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/models.py:104
|
||||||
|
#, python-format
|
||||||
|
msgid "Previous Timesheet:"
|
||||||
|
msgstr "Letzte Zeiterfassung:"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/models.py:124
|
||||||
|
#, python-format
|
||||||
|
msgid "Total:"
|
||||||
|
msgstr "Total:"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/models.py:227
|
||||||
|
#, python-format
|
||||||
|
msgid "Date"
|
||||||
|
msgstr "Datum"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/models.py:228
|
||||||
|
#, python-format
|
||||||
|
msgid "Duty Hours"
|
||||||
|
msgstr "Pflichtstunden"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/models.py:229
|
||||||
|
#, python-format
|
||||||
|
msgid "Worked Hours"
|
||||||
|
msgstr "Gearbeitete Stunden"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/models.py:230
|
||||||
|
#, python-format
|
||||||
|
msgid "Difference"
|
||||||
|
msgstr "Differenz"
|
||||||
|
|
||||||
|
#. module: hr_employee_time_clock
|
||||||
|
#: code:addons/hr_employee_time_clock/models.py:231
|
||||||
|
#, python-format
|
||||||
|
msgid "Running"
|
||||||
|
msgstr "Laufend"
|
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from . import hr_employee
|
||||||
|
from . import hr_attendance_analysis
|
||||||
|
from . import time_clock_resource_calendar
|
||||||
|
from . import hr_timesheet_dh
|
||||||
|
from . import resource_calendar
|
||||||
|
from . import hr_timesheet_sheet
|
||||||
|
from . import res_users
|
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import math
|
||||||
|
from openerp import models, api, _
|
||||||
|
from datetime import datetime
|
||||||
|
from openerp.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class HrAttendance(models.Model):
|
||||||
|
_inherit = "hr.attendance"
|
||||||
|
|
||||||
|
# ref: https://bugs.launchpad.net/openobject-client/+bug/887612
|
||||||
|
# test: 0.9853 - 0.0085
|
||||||
|
|
||||||
|
def float_time_convert(self, float_val):
|
||||||
|
hours = math.floor(abs(float_val))
|
||||||
|
mins = abs(float_val) - hours
|
||||||
|
mins = round(mins * 60)
|
||||||
|
if mins >= 60.0:
|
||||||
|
hours += 1
|
||||||
|
mins = 0.0
|
||||||
|
float_time = '%02d:%02d' % (hours, mins)
|
||||||
|
return float_time
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, values):
|
||||||
|
if values.get('name'):
|
||||||
|
times = datetime.strptime(values.get('name'), "%Y-%m-%d %H:%M:%S")
|
||||||
|
if datetime.now() < times:
|
||||||
|
raise ValidationError(
|
||||||
|
_('You can not set time of Sing In (resp. Sing Out) which '
|
||||||
|
'is later than a current time'))
|
||||||
|
return super(HrAttendance, self).create(values)
|
|
@ -0,0 +1,111 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
from openerp import api, fields, models, _
|
||||||
|
from openerp.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class HrEmployee(models.Model):
|
||||||
|
_inherit = "hr.employee"
|
||||||
|
_description = "Employee"
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def attendance_action_change(self):
|
||||||
|
hr_timesheet_sheet_sheet_pool = self.env['hr_timesheet_sheet.sheet']
|
||||||
|
hr_timesheet_ids = hr_timesheet_sheet_sheet_pool.search(
|
||||||
|
[('employee_id', '=', self.id),
|
||||||
|
('date_from', '<=', date.today()),
|
||||||
|
('date_to', '>=', date.today())])
|
||||||
|
if not hr_timesheet_ids:
|
||||||
|
raise ValidationError(
|
||||||
|
_('Please contact your manager to create timesheet for you.'))
|
||||||
|
return super(HrEmployee, self).attendance_action_change()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def check_in_out_action(self, values):
|
||||||
|
employee = self.sudo().browse(values.get('employee_id')).exists()
|
||||||
|
# employee.attendance_action_change()
|
||||||
|
if not employee:
|
||||||
|
return [
|
||||||
|
{'error': _(
|
||||||
|
'Please contact your manager to create '
|
||||||
|
'employee for you and change QR-code.')}]
|
||||||
|
|
||||||
|
hr_timesheet_sheet_sheet_pool = self.env['hr_timesheet_sheet.sheet']
|
||||||
|
hr_timesheet_ids = hr_timesheet_sheet_sheet_pool.search(
|
||||||
|
[('employee_id', '=', employee.id),
|
||||||
|
('date_from', '<=', date.today()),
|
||||||
|
('date_to', '>=', date.today())])
|
||||||
|
if not hr_timesheet_ids:
|
||||||
|
return [
|
||||||
|
{'error': _(
|
||||||
|
'Please contact your manager to create '
|
||||||
|
'timesheet for you.')}]
|
||||||
|
|
||||||
|
""" Check In/Check Out action
|
||||||
|
Check In: create a new attendance record
|
||||||
|
Check Out: modify check_out field of appropriate attendance record
|
||||||
|
"""
|
||||||
|
if len(self) > 1:
|
||||||
|
raise ValidationError(
|
||||||
|
_('Cannot perform check in or '
|
||||||
|
'check out on multiple employees.'))
|
||||||
|
action_date = fields.Datetime.now()
|
||||||
|
if employee.state != 'absent':
|
||||||
|
vals = {'name': action_date,
|
||||||
|
'action': 'sign_out',
|
||||||
|
'employee_id': employee.id, }
|
||||||
|
log = 'checked_out'
|
||||||
|
else:
|
||||||
|
vals = {'name': action_date,
|
||||||
|
'action': 'sign_in',
|
||||||
|
'employee_id': employee.id, }
|
||||||
|
log = 'checked_in'
|
||||||
|
self.env['hr.attendance'].sudo().create(vals)
|
||||||
|
employee = self.sudo().browse(employee.id)
|
||||||
|
ctx = self.env.context.copy()
|
||||||
|
ctx.update(online_analysis=True)
|
||||||
|
res = hr_timesheet_ids.with_context(
|
||||||
|
ctx).attendance_analysis(hr_timesheet_ids.id)
|
||||||
|
running = 0
|
||||||
|
date_line = values.get('date').split(' ')[0]
|
||||||
|
dddd = (fields.Datetime.from_string(date_line + ' 00:00:00'))
|
||||||
|
date_format, time_format = \
|
||||||
|
hr_timesheet_sheet_sheet_pool._get_user_datetime_format()
|
||||||
|
date_line = dddd.strftime("{} {}".format(date_format,
|
||||||
|
time_format)).split(' ')[0]
|
||||||
|
for d in res.get('hours'):
|
||||||
|
if d.get('name') == date_line:
|
||||||
|
running = d.get('running')
|
||||||
|
re = {'log': log,
|
||||||
|
'name': employee.name,
|
||||||
|
'image': employee.image_medium or '',
|
||||||
|
'running': running,
|
||||||
|
'user_id': employee.user_id.id}
|
||||||
|
print('\n re >>>>>> %s' % re)
|
||||||
|
return [{'log': log,
|
||||||
|
'name': employee.name,
|
||||||
|
'image': employee.image_medium or '',
|
||||||
|
'running': running,
|
||||||
|
'user_id': employee.user_id.id}]
|
|
@ -0,0 +1,324 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import datetime as dtime
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from openerp import api, fields, models, _
|
||||||
|
from dateutil import rrule, parser
|
||||||
|
from openerp.tools.translate import _
|
||||||
|
|
||||||
|
|
||||||
|
class HrTimesheetDh(models.Model):
|
||||||
|
"""
|
||||||
|
Addition plugin for HR timesheet for work with duty hours
|
||||||
|
"""
|
||||||
|
_inherit = 'hr_timesheet_sheet.sheet'
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _duty_hours(self):
|
||||||
|
for sheet in self:
|
||||||
|
sheet['total_duty_hours'] = 0.0
|
||||||
|
if sheet.state == 'done':
|
||||||
|
sheet['total_duty_hours'] = sheet.total_duty_hours_done
|
||||||
|
else:
|
||||||
|
dates = list(rrule.rrule(rrule.DAILY,
|
||||||
|
dtstart=parser.parse(sheet.date_from),
|
||||||
|
until=parser.parse(sheet.date_to)))
|
||||||
|
period = {'date_from': sheet.date_from,
|
||||||
|
'date_to': sheet.date_to}
|
||||||
|
for date_line in dates:
|
||||||
|
duty_hours = sheet.calculate_duty_hours(date_from=date_line,
|
||||||
|
period=period,
|
||||||
|
)
|
||||||
|
sheet['total_duty_hours'] += duty_hours
|
||||||
|
sheet['total_duty_hours'] = (sheet.total_duty_hours -
|
||||||
|
sheet.total_attendance)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def count_leaves(self, date_from, employee_id, period):
|
||||||
|
holiday_obj = self.env['hr.holidays']
|
||||||
|
start_leave_period = end_leave_period = False
|
||||||
|
if period.get('date_from') and period.get('date_to'):
|
||||||
|
start_leave_period = period.get('date_from')
|
||||||
|
end_leave_period = period.get('date_to')
|
||||||
|
holiday_ids = holiday_obj.search(
|
||||||
|
['|', '&',
|
||||||
|
('date_from', '>=', start_leave_period),
|
||||||
|
('date_from', '<=', end_leave_period),
|
||||||
|
'&', ('date_to', '<=', end_leave_period),
|
||||||
|
('date_to', '>=', start_leave_period),
|
||||||
|
('employee_id', '=', employee_id),
|
||||||
|
('state', '=', 'validate'),
|
||||||
|
('type', '=', 'remove')])
|
||||||
|
leaves = []
|
||||||
|
for leave in holiday_ids:
|
||||||
|
leave_date_from = datetime.strptime(leave.date_from,
|
||||||
|
'%Y-%m-%d %H:%M:%S')
|
||||||
|
leave_date_to = datetime.strptime(leave.date_to,
|
||||||
|
'%Y-%m-%d %H:%M:%S')
|
||||||
|
leave_dates = list(rrule.rrule(rrule.DAILY,
|
||||||
|
dtstart=parser.parse(
|
||||||
|
leave.date_from),
|
||||||
|
until=parser.parse(leave.date_to)))
|
||||||
|
for date in leave_dates:
|
||||||
|
if date.strftime('%Y-%m-%d') == date_from.strftime('%Y-%m-%d'):
|
||||||
|
leaves.append(
|
||||||
|
(leave_date_from, leave_date_to, leave.number_of_days))
|
||||||
|
break
|
||||||
|
return leaves
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def get_overtime(self, start_date):
|
||||||
|
for sheet in self:
|
||||||
|
if sheet.state == 'done':
|
||||||
|
return sheet.total_duty_hours_done * -1
|
||||||
|
return self.calculate_diff(start_date)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _overtime_diff(self):
|
||||||
|
for sheet in self:
|
||||||
|
old_timesheet_start_from = parser.parse(
|
||||||
|
sheet.date_from) - timedelta(days=1)
|
||||||
|
prev_timesheet_diff = \
|
||||||
|
self.get_previous_month_diff(
|
||||||
|
sheet.employee_id.id,
|
||||||
|
old_timesheet_start_from.strftime('%Y-%m-%d')
|
||||||
|
)
|
||||||
|
sheet['calculate_diff_hours'] = (
|
||||||
|
self.get_overtime(datetime.today().strftime('%Y-%m-%d'), ) +
|
||||||
|
prev_timesheet_diff)
|
||||||
|
sheet['prev_timesheet_diff'] = prev_timesheet_diff
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _get_analysis(self):
|
||||||
|
res = {}
|
||||||
|
for sheet in self:
|
||||||
|
function_call = True
|
||||||
|
data = self.attendance_analysis(sheet.id, function_call)
|
||||||
|
values = []
|
||||||
|
output = [
|
||||||
|
'<style>.attendanceTable td,.attendanceTable th {padding: 3px; border: 1px solid #C0C0C0; border-collapse: collapse; text-align: right;} </style><table class="attendanceTable" >']
|
||||||
|
for val in data.values():
|
||||||
|
if isinstance(val, (int, float)):
|
||||||
|
output.append('<tr>')
|
||||||
|
prev_ts = _('Previous Timesheet:')
|
||||||
|
output.append('<th colspan="2">' + prev_ts + ' </th>')
|
||||||
|
output.append('<td colspan="3">' + str(val) + '</td>')
|
||||||
|
output.append('</tr>')
|
||||||
|
for k, v in data.items():
|
||||||
|
if isinstance(v, list):
|
||||||
|
output.append('<tr>')
|
||||||
|
for th in v[0].keys():
|
||||||
|
output.append('<th>' + th + '</th>')
|
||||||
|
output.append('</tr>')
|
||||||
|
for res in v:
|
||||||
|
values.append(res.values())
|
||||||
|
for tr in values:
|
||||||
|
output.append('<tr>')
|
||||||
|
for td in tr:
|
||||||
|
output.append('<td>' + td + '</td>')
|
||||||
|
output.append('</tr>')
|
||||||
|
|
||||||
|
if isinstance(v, dict):
|
||||||
|
output.append('<tr>')
|
||||||
|
total_ts = _('Total:')
|
||||||
|
output.append('<th>' + total_ts + ' </th>')
|
||||||
|
for td in v.values():
|
||||||
|
output.append('<td>' + '%s' % round(td, 4) + '</td>')
|
||||||
|
output.append('</tr>')
|
||||||
|
output.append('</table>')
|
||||||
|
sheet['analysis'] = '\n'.join(output)
|
||||||
|
|
||||||
|
total_duty_hours = fields.Float(compute='_duty_hours',
|
||||||
|
string='Total Duty Hours',
|
||||||
|
multi="_duty_hours")
|
||||||
|
total_duty_hours_done = fields.Float(string='Total Duty Hours',
|
||||||
|
readonly=True,
|
||||||
|
default=0.0)
|
||||||
|
total_diff_hours = fields.Float(string='Total Diff Hours',
|
||||||
|
readonly=True,
|
||||||
|
default=0.0)
|
||||||
|
calculate_diff_hours = fields.Char(compute='_overtime_diff',
|
||||||
|
string="Diff (worked-duty)",
|
||||||
|
multi="_diff")
|
||||||
|
prev_timesheet_diff = fields.Char(compute='_overtime_diff',
|
||||||
|
method=True,
|
||||||
|
string="Diff from old",
|
||||||
|
multi="_diff")
|
||||||
|
analysis = fields.Text(compute='_get_analysis',
|
||||||
|
type="text",
|
||||||
|
string="Attendance Analysis")
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def calculate_duty_hours(self, date_from, period):
|
||||||
|
contract_obj = self.env['hr.contract']
|
||||||
|
calendar_obj = self.env['resource.calendar']
|
||||||
|
duty_hours = 0.0
|
||||||
|
contract_ids = contract_obj.search(
|
||||||
|
[
|
||||||
|
('employee_id', '=', self.employee_id.id),
|
||||||
|
('date_start', '<=', date_from),
|
||||||
|
'|',
|
||||||
|
('date_end', '>=', date_from),
|
||||||
|
('date_end', '=', None)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for contract in contract_ids:
|
||||||
|
ctx = dict(self.env.context).copy()
|
||||||
|
ctx.update(period)
|
||||||
|
dh = calendar_obj.get_working_hours_of_date(
|
||||||
|
cr=self._cr,
|
||||||
|
uid=self.env.user.id,
|
||||||
|
ids=contract.working_hours.id,
|
||||||
|
start_dt=date_from,
|
||||||
|
resource_id=self.employee_id.id,
|
||||||
|
context=ctx)
|
||||||
|
leaves = self.count_leaves(date_from, self.employee_id.id, period)
|
||||||
|
if not leaves:
|
||||||
|
if not dh:
|
||||||
|
dh = 0.00
|
||||||
|
duty_hours += dh
|
||||||
|
else:
|
||||||
|
if leaves[-1] and leaves[-1][-1]:
|
||||||
|
if float(leaves[-1][-1]) == (-0.5):
|
||||||
|
duty_hours += dh / 2
|
||||||
|
|
||||||
|
return duty_hours
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def get_previous_month_diff(self, employee_id, prev_timesheet_date_from):
|
||||||
|
total_diff = 0.0
|
||||||
|
timesheet_ids = self.search(
|
||||||
|
[('employee_id', '=', employee_id),
|
||||||
|
('date_from', '<', prev_timesheet_date_from)
|
||||||
|
])
|
||||||
|
for timesheet in timesheet_ids:
|
||||||
|
total_diff += timesheet.get_overtime(
|
||||||
|
start_date=prev_timesheet_date_from)
|
||||||
|
return total_diff
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _get_user_datetime_format(self):
|
||||||
|
""" Get user's language & fetch date/time formats of
|
||||||
|
that language """
|
||||||
|
lang_obj = self.env['res.lang']
|
||||||
|
language = self.env.user.lang
|
||||||
|
lang_ids = lang_obj.search([('code', '=', language)])
|
||||||
|
date_format = _('%Y-%m-%d')
|
||||||
|
time_format = _('%H:%M:%S')
|
||||||
|
for lang in lang_ids:
|
||||||
|
date_format = lang.date_format
|
||||||
|
time_format = lang.time_format
|
||||||
|
return date_format, time_format
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def attendance_analysis(self, timesheet_id=None, function_call=False):
|
||||||
|
attendance_obj = self.env['hr.attendance']
|
||||||
|
date_format, time_format = self._get_user_datetime_format()
|
||||||
|
|
||||||
|
for sheet in self:
|
||||||
|
if sheet.id == timesheet_id:
|
||||||
|
|
||||||
|
employee_id = sheet.employee_id.id
|
||||||
|
start_date = sheet.date_from
|
||||||
|
end_date = sheet.date_to
|
||||||
|
previous_month_diff = self.get_previous_month_diff(
|
||||||
|
employee_id, start_date)
|
||||||
|
current_month_diff = previous_month_diff
|
||||||
|
res = {
|
||||||
|
'previous_month_diff': previous_month_diff,
|
||||||
|
'hours': []
|
||||||
|
}
|
||||||
|
|
||||||
|
period = {'date_from': start_date,
|
||||||
|
'date_to': end_date
|
||||||
|
}
|
||||||
|
dates = list(rrule.rrule(rrule.DAILY,
|
||||||
|
dtstart=parser.parse(start_date),
|
||||||
|
until=parser.parse(
|
||||||
|
end_date)))
|
||||||
|
work_current_month_diff = 0.0
|
||||||
|
total = {'worked_hours': 0.0, 'duty_hours': 0.0,
|
||||||
|
'diff':
|
||||||
|
current_month_diff, 'work_current_month_diff': ''}
|
||||||
|
for date_line in dates:
|
||||||
|
|
||||||
|
dh = sheet.calculate_duty_hours(date_from=date_line,
|
||||||
|
period=period,
|
||||||
|
)
|
||||||
|
worked_hours = 0.0
|
||||||
|
for att in sheet.period_ids:
|
||||||
|
if att.name == date_line.strftime('%Y-%m-%d'):
|
||||||
|
worked_hours = att.total_attendance
|
||||||
|
|
||||||
|
diff = worked_hours - dh
|
||||||
|
current_month_diff += diff
|
||||||
|
work_current_month_diff += diff
|
||||||
|
if function_call:
|
||||||
|
res['hours'].append({
|
||||||
|
_('Date'): date_line.strftime(date_format),
|
||||||
|
_('Duty Hours'):
|
||||||
|
attendance_obj.float_time_convert(dh),
|
||||||
|
_('Worked Hours'):
|
||||||
|
attendance_obj.float_time_convert(worked_hours),
|
||||||
|
_('Difference'): self.sign_float_time_convert(diff),
|
||||||
|
_('Running'): self.sign_float_time_convert(
|
||||||
|
current_month_diff)})
|
||||||
|
else:
|
||||||
|
res['hours'].append({
|
||||||
|
'name': date_line.strftime(date_format),
|
||||||
|
'dh': attendance_obj.float_time_convert(dh),
|
||||||
|
'worked_hours': attendance_obj.float_time_convert(
|
||||||
|
worked_hours),
|
||||||
|
'diff': self.sign_float_time_convert(diff),
|
||||||
|
'running': self.sign_float_time_convert(
|
||||||
|
current_month_diff)
|
||||||
|
})
|
||||||
|
total['duty_hours'] += dh
|
||||||
|
total['worked_hours'] += worked_hours
|
||||||
|
total['diff'] += diff
|
||||||
|
total['work_current_month_diff'] = work_current_month_diff
|
||||||
|
res['total'] = total
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def sign_float_time_convert(self, float_time):
|
||||||
|
sign = '-' if float_time < 0 else ''
|
||||||
|
attendance_obj = self.pool.get('hr.attendance')
|
||||||
|
return sign + attendance_obj.float_time_convert(float_time)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def write(self, vals):
|
||||||
|
if 'state' in vals and vals['state'] == 'done':
|
||||||
|
vals['total_diff_hours'] = self.calculate_diff(None)
|
||||||
|
for sheet in self:
|
||||||
|
vals['total_duty_hours_done'] = sheet.total_duty_hours
|
||||||
|
elif 'state' in vals and vals['state'] == 'draft':
|
||||||
|
vals['total_diff_hours'] = 0.0
|
||||||
|
res = super(HrTimesheetDh, self).write(vals)
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def calculate_diff(self, end_date=None):
|
||||||
|
for sheet in self:
|
||||||
|
return sheet.total_duty_hours * (-1)
|
|
@ -0,0 +1,45 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
from openerp import api, fields, models, _
|
||||||
|
from openerp.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class HrTimesheetSheet(models.Model):
|
||||||
|
_inherit = "hr_timesheet_sheet.sheet"
|
||||||
|
|
||||||
|
@api.onchange('date_from', 'date_to')
|
||||||
|
@api.multi
|
||||||
|
def change_date(self):
|
||||||
|
if self.date_to and self.date_from and self.date_from > self.date_to:
|
||||||
|
raise ValidationError(
|
||||||
|
_('You added wrong date period.'))
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, values):
|
||||||
|
if values.get('date_to') and values.get('date_from') \
|
||||||
|
and values.get('date_from') > values.get('date_to'):
|
||||||
|
raise ValidationError(
|
||||||
|
_('You added wrong date period.'))
|
||||||
|
return super(HrTimesheetSheet, self).create(values)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# Copyright (C) 2016 - now Bytebrand Outsourcing AG (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
from openerp import fields, models, SUPERUSER_ID, api
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ResUsers(models.Model):
|
||||||
|
_inherit = 'res.users'
|
||||||
|
|
||||||
|
def authenticate(cls, db, login, password, user_agent_env):
|
||||||
|
uid = cls._login(db, login, password)
|
||||||
|
if uid == SUPERUSER_ID:
|
||||||
|
# Successfully logged in as admin!
|
||||||
|
# Attempt to guess the web base url...
|
||||||
|
if user_agent_env and user_agent_env.get('base_location'):
|
||||||
|
try:
|
||||||
|
with cls.pool.cursor() as cr:
|
||||||
|
base = user_agent_env['base_location']
|
||||||
|
ICP = api.Environment(cr, uid, {})[
|
||||||
|
'ir.config_parameter']
|
||||||
|
if not ICP.get_param('web.base.url.freeze'):
|
||||||
|
ICP.set_param('web.base.url', base)
|
||||||
|
except Exception:
|
||||||
|
_logger.exception(
|
||||||
|
"Failed to update web.base.url configuration parameter")
|
||||||
|
if user_agent_env:
|
||||||
|
return uid
|
||||||
|
else:
|
||||||
|
with cls.pool.cursor() as cr:
|
||||||
|
module = api.Environment(
|
||||||
|
cr, uid, {})['ir.module.module'].sudo().search(
|
||||||
|
[('name', '=', 'hr_employee_time_clock')])
|
||||||
|
return {'uid': uid, 'version': module.latest_version}
|
|
@ -0,0 +1,123 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import datetime as dtime
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from openerp import api, fields, models, _
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceCalendar(models.Model):
|
||||||
|
_inherit = 'resource.calendar'
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def get_working_hours_of_date(self, cr, uid, ids, start_dt=None,
|
||||||
|
end_dt=None, leaves=None,
|
||||||
|
compute_leaves=False, resource_id=None,
|
||||||
|
default_interval=None, context=None):
|
||||||
|
""" Get the working hours of the day based on calendar. This method uses
|
||||||
|
get_working_intervals_of_day to have the work intervals of the day. It
|
||||||
|
then calculates the number of hours contained in those intervals. """
|
||||||
|
res = dtime.timedelta()
|
||||||
|
intervals = self.get_working_intervals_of_day(
|
||||||
|
cr, uid, ids,
|
||||||
|
start_dt, end_dt, leaves,
|
||||||
|
compute_leaves, resource_id,
|
||||||
|
default_interval, context)
|
||||||
|
for interval in intervals:
|
||||||
|
res += interval[1] - interval[0]
|
||||||
|
return seconds(res) / 3600.0
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def get_working_intervals_of_day(self, cr, uid, ids, start_dt=None,
|
||||||
|
end_dt=None, leaves=None,
|
||||||
|
compute_leaves=False, resource_id=None,
|
||||||
|
default_interval=None, context=None):
|
||||||
|
|
||||||
|
if isinstance(ids, (list, tuple)):
|
||||||
|
ids = ids[0]
|
||||||
|
work_limits = []
|
||||||
|
if start_dt is None and end_dt is not None:
|
||||||
|
start_dt = end_dt.replace(hour=0, minute=0, second=0)
|
||||||
|
elif start_dt is None:
|
||||||
|
start_dt = datetime.now().replace(hour=0, minute=0, second=0)
|
||||||
|
else:
|
||||||
|
work_limits.append((start_dt.replace(
|
||||||
|
hour=0, minute=0, second=0), start_dt))
|
||||||
|
if end_dt is None:
|
||||||
|
end_dt = start_dt.replace(hour=23, minute=59, second=59)
|
||||||
|
else:
|
||||||
|
work_limits.append((end_dt, end_dt.replace(
|
||||||
|
hour=23, minute=59, second=59)))
|
||||||
|
assert start_dt.date() == end_dt.date(), \
|
||||||
|
'get_working_intervals_of_day is restricted to one day'
|
||||||
|
intervals = []
|
||||||
|
work_dt = start_dt.replace(hour=0, minute=0, second=0)
|
||||||
|
|
||||||
|
# no calendar: try to use the default_interval, then return directly
|
||||||
|
if ids is None:
|
||||||
|
working_interval = []
|
||||||
|
if default_interval:
|
||||||
|
working_interval = (
|
||||||
|
start_dt.replace(hour=default_interval[0],
|
||||||
|
minute=0, second=0),
|
||||||
|
start_dt.replace(hour=default_interval[1],
|
||||||
|
minute=0, second=0))
|
||||||
|
intervals = self.interval_remove_leaves(working_interval,
|
||||||
|
work_limits)
|
||||||
|
return intervals
|
||||||
|
|
||||||
|
working_intervals = []
|
||||||
|
for calendar_working_day in self.get_attendances_for_weekdays(
|
||||||
|
ids, [start_dt.weekday()]):
|
||||||
|
working_interval = (
|
||||||
|
work_dt.replace(hour=int(calendar_working_day.hour_from)),
|
||||||
|
work_dt.replace(hour=int(calendar_working_day.hour_to))
|
||||||
|
)
|
||||||
|
working_intervals += self.interval_remove_leaves(working_interval,
|
||||||
|
work_limits)
|
||||||
|
# find leave intervals
|
||||||
|
if leaves is None and compute_leaves:
|
||||||
|
leaves = self.get_leave_intervals(cr, uid, ids,
|
||||||
|
resource_id=resource_id,
|
||||||
|
context=context)
|
||||||
|
|
||||||
|
# filter according to leaves
|
||||||
|
for interval in working_intervals:
|
||||||
|
work_intervals = self.interval_remove_leaves(interval, leaves)
|
||||||
|
intervals += work_intervals
|
||||||
|
return intervals
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def get_attendances_for_weekdays(self, ids, weekdays):
|
||||||
|
""" Given a list of weekdays, return matching
|
||||||
|
resource.calendar.attendance"""
|
||||||
|
calendar = self.browse(ids)
|
||||||
|
return [att for att in calendar.attendance_ids
|
||||||
|
if int(att.dayofweek) in weekdays]
|
||||||
|
|
||||||
|
|
||||||
|
def seconds(td):
|
||||||
|
assert isinstance(td, dtime.timedelta)
|
||||||
|
|
||||||
|
return (td.microseconds + (
|
||||||
|
td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10. ** 6
|
|
@ -0,0 +1,100 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import math
|
||||||
|
from openerp import fields, models, api
|
||||||
|
|
||||||
|
|
||||||
|
class TimeClockResourceCalendar(models.Model):
|
||||||
|
_inherit = "resource.calendar"
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def get_working_intervals_of_day(self, start_dt=None, end_dt=None,
|
||||||
|
leaves=None, compute_leaves=False,
|
||||||
|
resource_id=None, default_interval=None):
|
||||||
|
""" To resolve issue of 0.5h on duty hours,
|
||||||
|
this method has to be overriden here."""
|
||||||
|
|
||||||
|
""" Computes start_dt, end_dt (with default values if not set) +
|
||||||
|
off-interval work limits """
|
||||||
|
work_limits = []
|
||||||
|
if start_dt is None and end_dt is not None:
|
||||||
|
start_dt = end_dt.replace(hour=0, minute=0, second=0)
|
||||||
|
elif start_dt is None:
|
||||||
|
start_dt = datetime.datetime.now().replace(hour=0, minute=0,
|
||||||
|
second=0)
|
||||||
|
else:
|
||||||
|
work_limits.append(
|
||||||
|
(start_dt.replace(hour=0, minute=0, second=0), start_dt))
|
||||||
|
if end_dt is None:
|
||||||
|
end_dt = start_dt.replace(hour=23, minute=59, second=59)
|
||||||
|
else:
|
||||||
|
work_limits.append(
|
||||||
|
(end_dt, end_dt.replace(hour=23, minute=59, second=59)))
|
||||||
|
assert start_dt.date() == end_dt.date(), \
|
||||||
|
'get_working_intervals_of_day is restricted to one day'
|
||||||
|
|
||||||
|
intervals = []
|
||||||
|
work_dt = start_dt.replace(hour=0, minute=0, second=0)
|
||||||
|
|
||||||
|
# no calendar: try to use the default_interval, then return directly
|
||||||
|
if self.id is None:
|
||||||
|
if default_interval:
|
||||||
|
working_interval = (
|
||||||
|
start_dt.replace(
|
||||||
|
hour=default_interval[0], minute=0, second=0),
|
||||||
|
start_dt.replace(
|
||||||
|
hour=default_interval[1], minute=0, second=0))
|
||||||
|
intervals = self.interval_remove_leaves(working_interval,
|
||||||
|
work_limits)
|
||||||
|
if intervals:
|
||||||
|
return intervals
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
working_intervals = []
|
||||||
|
for calendar_working_day in self.get_attendances_for_weekdays(
|
||||||
|
[start_dt.weekday()])[0]:
|
||||||
|
|
||||||
|
# FIXED by Addition IT Solutions: Counting
|
||||||
|
# minutes to get result when 0.5h are added to calendar
|
||||||
|
minutes_from = math.modf(calendar_working_day.hour_from)[0] * 60
|
||||||
|
minutes_to = math.modf(calendar_working_day.hour_to)[0] * 60
|
||||||
|
working_interval = (
|
||||||
|
work_dt.replace(hour=int(calendar_working_day.hour_from),
|
||||||
|
minute=int(minutes_from)),
|
||||||
|
work_dt.replace(hour=int(calendar_working_day.hour_to),
|
||||||
|
minute=int(minutes_to))
|
||||||
|
)
|
||||||
|
working_intervals += self.interval_remove_leaves(working_interval,
|
||||||
|
work_limits)
|
||||||
|
# find leave intervals
|
||||||
|
if leaves is None and compute_leaves:
|
||||||
|
leaves = self.get_leave_intervals(resource_id=resource_id)
|
||||||
|
|
||||||
|
# filter according to leaves
|
||||||
|
for interval in working_intervals:
|
||||||
|
work_intervals = self.interval_remove_leaves(interval, leaves)
|
||||||
|
intervals += work_intervals
|
||||||
|
|
||||||
|
return intervals
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||||
|
#
|
||||||
|
# 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 report_attendance_analysis
|
|
@ -0,0 +1,75 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||||
|
#
|
||||||
|
# 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 tools
|
||||||
|
from openerp.osv import fields, osv
|
||||||
|
|
||||||
|
|
||||||
|
class HrAttendanceAnalysisReport(osv.osv):
|
||||||
|
_name = "hr.attendance.analysis.report"
|
||||||
|
_description = "Attendance Analysis based on Timesheet"
|
||||||
|
_auto = False
|
||||||
|
_columns = {
|
||||||
|
'name': fields.many2one('hr.employee',
|
||||||
|
'Employee'),
|
||||||
|
'department_id': fields.many2one('hr.department',
|
||||||
|
'Department'),
|
||||||
|
'timesheet_id': fields.many2one('hr_timesheet_sheet.sheet',
|
||||||
|
'Timesheet'),
|
||||||
|
'total_duty_hours_running': fields.float('Running Hours'),
|
||||||
|
'total_duty_hours_done': fields.float('Duty Hours'),
|
||||||
|
'user_id': fields.many2one('res.users',
|
||||||
|
'User of Employee'),
|
||||||
|
'parent_user_id': fields.many2one('res.users',
|
||||||
|
'User of Manager'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def init(self, cr):
|
||||||
|
tools.drop_view_if_exists(cr, 'hr_attendance_analysis_report')
|
||||||
|
cr.execute("""
|
||||||
|
CREATE or REPLACE view hr_attendance_analysis_report as (
|
||||||
|
select
|
||||||
|
min(sheet.id) as id,
|
||||||
|
sheet.id as timesheet_id,
|
||||||
|
sheet.employee_id as name,
|
||||||
|
emp.department_id as department_id,
|
||||||
|
res.user_id as user_id,
|
||||||
|
(select r.user_id
|
||||||
|
from resource_resource r, hr_employee e
|
||||||
|
where r.id = e.resource_id and e.id=emp.parent_id) as parent_user_id,
|
||||||
|
sheet.total_diff_hours as total_duty_hours_running,
|
||||||
|
sheet.total_duty_hours_done as total_duty_hours_done
|
||||||
|
from
|
||||||
|
hr_timesheet_sheet_sheet sheet,
|
||||||
|
hr_employee emp,
|
||||||
|
resource_resource res,
|
||||||
|
hr_department dp
|
||||||
|
where
|
||||||
|
sheet.employee_id=emp.id AND
|
||||||
|
emp.resource_id=res.id AND
|
||||||
|
emp.department_id=dp.id
|
||||||
|
group by
|
||||||
|
sheet.id, emp.department_id, res.user_id, emp.parent_id
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record id="view_hr_attendance_analysis_report_tree" model="ir.ui.view">
|
||||||
|
<field name="name">hr.attendance.analysis.report.tree</field>
|
||||||
|
<field name="model">hr.attendance.analysis.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Analysis by Timesheet">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="department_id"/>
|
||||||
|
<field name="timesheet_id"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_hr_attendance_analysis_report_graph" model="ir.ui.view">
|
||||||
|
<field name="name">hr.attendance.analysis.report.graph</field>
|
||||||
|
<field name="model">hr.attendance.analysis.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<graph string="Analysis by Timesheet" type="pivot" stacked="True">
|
||||||
|
<field name="department_id" type="row"/>
|
||||||
|
<field name="name" type="row"/>
|
||||||
|
<field name="total_duty_hours_running" type="measure"/>
|
||||||
|
<field name="total_duty_hours_done" type="measure"/>
|
||||||
|
<field name="timesheet_id" type="col"/>
|
||||||
|
</graph>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_hr_attendance_analysis_report" model="ir.actions.act_window">
|
||||||
|
<field name="name">Analysis by Timesheet</field>
|
||||||
|
<field name="res_model">hr.attendance.analysis.report</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">graph</field>
|
||||||
|
<field name="context">{'group_by_no_leaf':1,'group_by':[]}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem action="action_hr_attendance_analysis_report" id="menu_hr_attendance_analysis_report"
|
||||||
|
parent="hr.menu_hr_reporting" groups="base.group_hr_user" sequence="3"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -0,0 +1,5 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_hr_contract_user,hr.contract.employee,hr_contract.model_hr_contract,base.group_user,1,0,0,0
|
||||||
|
access_resource_calendar_user,resource.calendar.employee,resource.model_resource_calendar,base.group_user,1,0,0,0
|
||||||
|
access_resource_calendar_attendance_user,resource.calendar.attendance.employee,resource.model_resource_calendar_attendance,base.group_user,1,0,0,0
|
||||||
|
access_hr_attendance_analysis_report_user,hr.attendance.analysis.report.officer,model_hr_attendance_analysis_report,base.group_hr_user,1,1,1,0
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data noupdate="1">
|
||||||
|
|
||||||
|
<record id="employee_see_own_contract" model="ir.rule">
|
||||||
|
<field name="model_id" ref="hr_contract.model_hr_contract"/>
|
||||||
|
<field name="name">Personal Employee Contracts</field>
|
||||||
|
<field name="domain_force">['|',('employee_id.user_id','=',user.id),('employee_id.parent_id.user_id','child_of',user.id)]</field>
|
||||||
|
<field name="perm_read" eval="1"/>
|
||||||
|
<field name="perm_write" eval="0"/>
|
||||||
|
<field name="perm_create" eval="0"/>
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
<field name="groups" eval="[(4,ref('base.group_user'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!--<record model="ir.rule" id="manager_timesheet_rule">-->
|
||||||
|
<!--<field name="name">Timesheet Rule for Manager</field>-->
|
||||||
|
<!--<field name="model_id" ref="hr_timesheet_sheet.model_hr_timesheet_sheet_sheet"/>-->
|
||||||
|
<!--<field name="domain_force">['|',('employee_id.user_id','=',user.id), ('employee_id.parent_id.user_id','child_of',user.id)]</field> -->
|
||||||
|
<!--</record>-->
|
||||||
|
|
||||||
|
<!--<record model="ir.rule" id="manager_timesheet_report_rule">-->
|
||||||
|
<!--<field name="name">Timesheet Report Rule for Manager</field>-->
|
||||||
|
<!--<field name="model_id" ref="hr_employee_time_clock.model_hr_attendance_analysis_report"/>-->
|
||||||
|
<!--<field name="domain_force">['|',('user_id','=',user.id), ('parent_user_id','child_of',user.id)]</field>-->
|
||||||
|
<!--</record>-->
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,201 @@
|
||||||
|
<section class="oe_container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h2 class="oe_slogan">Employee Time Clock</h2>
|
||||||
|
<h3 class="oe_slogan">Tracking employees' over- and undertime with ease</h3>
|
||||||
|
<h4 class="oe_slogan" style="margin-bottom: 16px; margin-top: 16px;">by Bytebrand Outsourcing AG</h4>
|
||||||
|
<div class="oe_row oe_centeralign">
|
||||||
|
<a href="mailto:odoo@bytebrand.net" class="btn btn-primary btn-lg mt8" style="color: white;"><span class="fa fa-envelope fa-1x"></span> Help Mail </a>
|
||||||
|
<a href="https://github.com/marcok/odoo_modules/issues" class="btn btn-primary btn-lg mt8" style="color: white;"><span class="fa fa-bug fa-1x"></span> Bugs </a>
|
||||||
|
<a href="https://github.com/marcok/odoo_modules/issues" class="btn btn-primary btn-lg mt8" style="color: white;"><span class="fa fa-wrench fa-1x"></span> Request new Features </a>
|
||||||
|
</div>
|
||||||
|
<div class="oe_mt32">
|
||||||
|
Employee Time Clock calculates over- and undertime for each employee and allows employees to sign in or sign out with a mobile app or with a QR code at company's entrance.
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Once you install the module, you'll see addional tab on the emloyees' timesheet pages called <i>Overtime Analysis</i>. On this tab you can observe:
|
||||||
|
<ul>
|
||||||
|
<li>Employee's <i>"duty hours"</i> which represent how much time one should spend working per day
|
||||||
|
<li>Duty hours are assigned to employee based on his contract/schedule, which should be defined in Odoo</li>
|
||||||
|
</li>
|
||||||
|
<li>Time spent in the office (calcucalted based on the check in/out records)</li>
|
||||||
|
<li>Saldo time per day</li>
|
||||||
|
<li>Running over/under-time</li>
|
||||||
|
</ul>
|
||||||
|
<img class="oe_picture oe_screenshot" src="screenshot7.png" alt="">
|
||||||
|
</p>
|
||||||
|
<p>Includes <a href="#iOSApps"><strong>iPhone/iPad apps</strong></a> for signing in and leave requests!</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h4 class="oe_slogan">Features</h4>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li><i class="fa fa-check-square-o text-primary"></i> Calculates "duty hours" based on individual employee contracts</li>
|
||||||
|
<li><i class="fa fa-check-square-o text-primary"></i> Allows to import public holidays from a CSV/XLS files</li>
|
||||||
|
<li><i class="fa fa-check-square-o text-primary"></i> Includes two <b>iOS apps</b>:
|
||||||
|
<ul>
|
||||||
|
<li>one for employees to sign in/out, track their over- and undertimes, request leaves</li>
|
||||||
|
<li>and another for you to use as a sign in terminal at your company's entrance</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="oe_container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h3>How it works</h3>
|
||||||
|
<p>The module adds a new data type called <i>duty hours</i>, which is the baseline for all calcualtions. Duty hours are calculated based on the time schedule assigned to an employee/contract and therefore can be configured on a daily basis. Whenever an employee gets an approved leave, duty hours for those days are set to 0.</p>
|
||||||
|
<p>Once you have a time schedule set up for an employee, his working hours are tracked using sign in/out timestamps. Each employee can use one of these methods to sign in/out:
|
||||||
|
<ul>
|
||||||
|
<li>Sign in/out to your Odoo instance</li>
|
||||||
|
<li>Sign in/out from our iPhone app</li>
|
||||||
|
<li>Sign in/out at the company's entrance using our QR code scanning iPhone/iPad app</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h2 class="oe_slogan" id="iOSApps">Odoo Time Clock (iPad & iPhone)</h2>
|
||||||
|
<h3 class="oe_slogan">Allows employees to sign in to Odoo using a simple QR code, printed on a card</h3>
|
||||||
|
<div class="oe_centered">
|
||||||
|
<img class="oe_screenshot" src="odoo-time-clock-prev.jpg">
|
||||||
|
</div>
|
||||||
|
<h4 class="oe_slogan">Features</h4>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li><i class="fa fa-check-square-o text-primary"></i> Allows to sign in to Odoo using personal QR code</li>
|
||||||
|
<li><i class="fa fa-check-square-o text-primary"></i> Displays company-wide announcement upon sign in if defined</li>
|
||||||
|
<li><i class="fa fa-check-square-o text-primary"></i> Allows to order lunch (if <i>Lunch</i> module installed) upon sign in</li>
|
||||||
|
<li><i class="fa fa-check-square-o text-primary"></i> Free for up to 3 users</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="oe_container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h2>How to make a QR code</h2>
|
||||||
|
<p>To allow employees use your sign in system you'd need to:
|
||||||
|
<ul>
|
||||||
|
<li>export employee IDs;</li>
|
||||||
|
<li>convert IDs into QR codes;</li>
|
||||||
|
<li>print out QR codes and hand them out to your employees.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<h5>Export employee IDs</h5>
|
||||||
|
<p>Follow below steps to export each employees' ID.
|
||||||
|
<ul>
|
||||||
|
<li>Open Odoo's Employees page and switch to the list view.
|
||||||
|
<img class="oe_picture oe_screenshot" src="screenshot1.png" alt="">
|
||||||
|
</li>
|
||||||
|
<li>Select all employees and press <i>Action</i> -> <i>Export</i>
|
||||||
|
<img class="oe_picture oe_screenshot" src="screenshot2.png" alt="">
|
||||||
|
</li>
|
||||||
|
<li>Select Export all data and pick info fields that you want to put on the sticker with QR code
|
||||||
|
<img class="oe_picture oe_screenshot" src="screenshot3.png" alt="">
|
||||||
|
<li>Important: make sure you also picked ID field, because it is used to generate QR codes</li>
|
||||||
|
</li>
|
||||||
|
<li>Select desired export format (doesn't make big difference), press Export and save the file on your PC</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<h5>Convert IDs into QR codes</h5>
|
||||||
|
<p><a href="http://www.herma.de/en/service/free-software/herma-labelassistant-online.html">Online HERMA tool</a> will allow you to import employees' information that we exported in the previous step and generate a printable PDF with everything, including QR codes. We made a quick video tutorial for you, check it out!</p>
|
||||||
|
<p>
|
||||||
|
Let's go through creating stickers with QR codes step-by-step.
|
||||||
|
<ul>
|
||||||
|
<li>Go to HERMA tool using the link above</li>
|
||||||
|
<li>Select any preferrable design template (we will go with the <i>Address labels, design 10</i>)</li>
|
||||||
|
<li>Remove unneeded labels by selecting them and hitting Delete</li>
|
||||||
|
<li>Click on the <i>Mail merge</i> button and upload previously exported file
|
||||||
|
<img class="oe_picture oe_screenshot" src="screenshot4.png" alt="">
|
||||||
|
</li>
|
||||||
|
<li>Now select needed fields and press <i>Add fields</i>
|
||||||
|
<img class="oe_picture oe_screenshot" src="screenshot5.png" alt=""></li>
|
||||||
|
<li>Now select "Set up barcode/QR code" checkbox and press the little QR code button next to the <b>ID</b> field to add a QR code
|
||||||
|
<img class="oe_picture oe_screenshot" src="screenshot6.png" alt="">
|
||||||
|
</li>
|
||||||
|
<li>That's it, press <i>Finish up and continue</i> and <i>Print preview</i></li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<p>After above steps HERMA will generate same stickers per each employee in your CSV/XLS file automatically and you don't need to repeat the same procedure for each employee. You will be offered to download the generated PDF which later can be printed on the sticker paper.</p>
|
||||||
|
<p>If you wish, you can use any other online tool for making QR codes, like <a href="https://www.the-qrcode-generator.com/">The QR Code Generator</a>. But note, that QR code should have an employee ID encoded in a text format!</p>
|
||||||
|
<h5>Setting up Odoo to work with QR codes</h5>
|
||||||
|
<p>Before you give the QR codes to your employees make sure you have created a Timesheet for each employee for the upcoming days. Once the timesheets are created, you will have to connect Odoo Time Clock iOS app to your Odoo server.</p>
|
||||||
|
<p>Open upu the app and enter your Odoo server information, press Connect and select needed database from the list. That's it, if timesheets were created and QR codes generated correctly, you will be able to sign in with QR code and see the greetings mesasge.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h4>Lunch ordering</h4>
|
||||||
|
<div class="oe_row_img oe_centered">
|
||||||
|
<img class="oe_picture oe_screenshot" src="lunch.png">
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
If odoo module <i>Lunch</i> is installed and lunch items are defined, they are shown after each sign in and a user is allowed to choose his preference. The app can operate in "Lunch order" only mode. App settings:
|
||||||
|
<ul>
|
||||||
|
<li>Define which categories have to be shown</li>
|
||||||
|
<li><i>Lunch only</i> mode: no sign in/out will be recorded. This is useful e.g. for coffee consummation book keeping.</li>
|
||||||
|
<li>You can define if the lunch screen only has to be shown for one order per day or of after each sign in.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<h4>Company info message</h4>
|
||||||
|
<div class="oe_row_img oe_centered">
|
||||||
|
<img class="oe_picture oe_screenshot" src="message.png">
|
||||||
|
</div>
|
||||||
|
<p>A company wide information message can be defined as a calender entry, tagged with "Station". The message will be shown for the entry period. The calender entry has to be created with the same user which is used for signing in to the app.</p>
|
||||||
|
<p>The app checks your calendar <strong>every 5 minutes</strong>, thus you might need to wait a little bit until next employee sees it upon signing in. Your company logo, if available, is displayed as well.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="oe_container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h2 class="oe_slogan">Odoo HR (iPhone)</h2>
|
||||||
|
<h3 class="oe_slogan">App for signing in/out, tracking personal over- and undertime, requesting leaves</h3>
|
||||||
|
<div class="oe_centered">
|
||||||
|
<img class="oe_screenshot" src="odoo-hr-prev.jpg">
|
||||||
|
</div>
|
||||||
|
<h4 class="oe_slogan">Features</h4>
|
||||||
|
<div class="oe_row oe_spaced outer">
|
||||||
|
<div class="oe_span6 oe_centeralign">
|
||||||
|
<p class="oe_mt32">
|
||||||
|
Sign in/out
|
||||||
|
</p>
|
||||||
|
<div class="oe_pic_ctr">
|
||||||
|
<img class="oe_demo oe_picture oe_screenshot oe_centered" src="signin.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_span6 oe_centeralign">
|
||||||
|
<p class="oe_mt32">
|
||||||
|
Sign in/out history
|
||||||
|
</p>
|
||||||
|
<div class="oe_pic_ctr">
|
||||||
|
<img class="oe_demo oe_picture oe_screenshot oe_centered" src="attendancehistory.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<div class="oe_span6 oe_centeralign">
|
||||||
|
<p class="oe_mt32">
|
||||||
|
Attendance analysis
|
||||||
|
</p>
|
||||||
|
<div class="oe_pic_ctr">
|
||||||
|
<img class="oe_demo oe_picture oe_screenshot oe_centered" src="attendanceana.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_span6 text-center">
|
||||||
|
<p class="oe_mt32">
|
||||||
|
Leave requests
|
||||||
|
</p>
|
||||||
|
<div class="oe_pic_ctr">
|
||||||
|
<img class="oe_demo oe_picture oe_screenshot oe_centered" src="leave.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</section>
|
||||||
|
<section class="oe_container">
|
||||||
|
<div class="oe_row oe_spaced oe_centeralign">
|
||||||
|
<h3>Bytebrand Outsourcing AG</h3>
|
||||||
|
<a class="btn" href="https://bytebrand.net" style="color:#31AD56;" target="_blank" ><span class="fa fa-internet-explorer fa-1x"></span> Bytebrand.net</a>
|
||||||
|
<a class="btn" href="https://www.facebook.com/bytebrand/" style="color:#31AD56;" target="_blank"><span class="fa fa-facebook fa-1x"></span> Facebook</a>
|
||||||
|
<a class="btn" href="https://www.linkedin.com/company/10154565/" style="color:#31AD56;" target="_blank"><span class="fa fa-linkedin fa-1x"></span> LinkedIn</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import xmlrpclib
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
url = "http://localhost"
|
||||||
|
db = "db"
|
||||||
|
username = "admin"
|
||||||
|
password = 'admin'
|
||||||
|
|
||||||
|
common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
|
||||||
|
print common.version()
|
||||||
|
|
||||||
|
uid = common.authenticate(db, username, password, {})
|
||||||
|
print uid
|
||||||
|
|
||||||
|
models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url), verbose=True)
|
||||||
|
print models.execute_kw(db, uid, password,
|
||||||
|
'hr_timesheet_sheet.sheet', 'attendance_analysis', [], dict(timesheet_id=1))
|
|
@ -0,0 +1,44 @@
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<record id="action_hr_timesheet_overtime" model="ir.actions.act_window">
|
||||||
|
<field name="name">Academy teachers</field>
|
||||||
|
<field name="res_model">hr_timesheet_sheet.sheet</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_id">
|
||||||
|
<field name="name">view.name</field>
|
||||||
|
<field name="model">hr_timesheet_sheet.sheet</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Idea list">
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="timesheet_overtime" model="ir.ui.view">
|
||||||
|
<field name="name">hr_timesheet_sheet.overtime.form</field>
|
||||||
|
<field name="model">hr_timesheet_sheet.sheet</field>
|
||||||
|
<field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="/form/sheet/group/group[2]" position="replace">
|
||||||
|
<group>
|
||||||
|
<field name="prev_timesheet_diff" widget="float_time" string="Last timesheets"/>
|
||||||
|
<field name="total_timesheet" widget="float_time"/>
|
||||||
|
<field name="total_attendance" widget="float_time" string="Attendance this timesheet"/>
|
||||||
|
<field name="total_duty_hours" widget="float_time" string="Remaining this timesheet"/>
|
||||||
|
<field name="calculate_diff_hours" widget="float_time" string="Total balance"/>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
<notebook position="inside">
|
||||||
|
<page string="Overtime Analysis">
|
||||||
|
<field name="analysis" widget="html" nolabel="1"/>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from . import create_timesheet_with_tag
|
||||||
|
from . import import_leave_requests
|
||||||
|
|
||||||
|
# END
|
|
@ -0,0 +1,102 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
import time
|
||||||
|
from openerp import fields, models, api, _
|
||||||
|
from openerp.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class CreateTimesheetWithTag(models.TransientModel):
|
||||||
|
_inherit = 'hr.timesheet.current.open'
|
||||||
|
_description = 'Create Timesheet With Employee Tag'
|
||||||
|
|
||||||
|
# Added below fields on the wizard
|
||||||
|
category_id = fields.Many2one('hr.employee.category',
|
||||||
|
string="Employee Tag",
|
||||||
|
required=True,
|
||||||
|
help='Category of Employee')
|
||||||
|
date_from = fields.Date(string='Start Date')
|
||||||
|
date_to = fields.Date(string='End Date')
|
||||||
|
|
||||||
|
@api.onchange('date_from', 'date_to')
|
||||||
|
@api.multi
|
||||||
|
def change_date(self, date_from, date_to):
|
||||||
|
if date_to and date_from and date_from > date_to:
|
||||||
|
raise ValidationError(
|
||||||
|
_('You added wrong date period.'))
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, values):
|
||||||
|
if values.get('date_to') and values.get('date_from') \
|
||||||
|
and values.get('date_from') > values.get('date_to'):
|
||||||
|
raise ValidationError(
|
||||||
|
_('You added wrong date period.'))
|
||||||
|
return super(CreateTimesheetWithTag, self).create(values)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def open_timesheet(self):
|
||||||
|
employee_obj = self.env['hr.employee']
|
||||||
|
ts = self.env['hr_timesheet_sheet.sheet']
|
||||||
|
value = super(CreateTimesheetWithTag, self).open_timesheet()
|
||||||
|
# First: Search all employees of selected Tag
|
||||||
|
if not self.category_id:
|
||||||
|
return value
|
||||||
|
category_id = self.category_id.id
|
||||||
|
employee_objects = employee_obj.search([
|
||||||
|
('category_ids', 'in', [category_id])])
|
||||||
|
user_ids = []
|
||||||
|
ts_ids = []
|
||||||
|
date_from = self.date_from or time.strftime('%Y-%m-%d')
|
||||||
|
date_to = self.date_to or time.strftime('%Y-%m-%d')
|
||||||
|
# Second: Create/Open Timesheets for all fetched employees.
|
||||||
|
for emp in employee_objects:
|
||||||
|
|
||||||
|
if emp.user_id:
|
||||||
|
user_ids.append(emp.user_id.id)
|
||||||
|
ts_id = ts.search([
|
||||||
|
('user_id', '=', emp.user_id.id),
|
||||||
|
('state', 'in', ('draft', 'new')),
|
||||||
|
('date_from', '<=', date_from),
|
||||||
|
('date_to', '>=', date_to)
|
||||||
|
])
|
||||||
|
if ts_id:
|
||||||
|
raise ValidationError(
|
||||||
|
_('Timesheet already exists for {name}.'.format(
|
||||||
|
name=emp.name)))
|
||||||
|
if not ts_id:
|
||||||
|
values = {'employee_id': emp.id}
|
||||||
|
if self.date_from and self.date_to:
|
||||||
|
values.update({
|
||||||
|
'date_from': date_from,
|
||||||
|
'date_to': date_to})
|
||||||
|
ts_id = ts.create(values)
|
||||||
|
|
||||||
|
ts_ids.append(ts_id.id)
|
||||||
|
|
||||||
|
# Third: Add it to dictionary to be returned
|
||||||
|
domain = "[('id','in',%s),('user_id', 'in', %s)]" % (ts_ids, user_ids)
|
||||||
|
value.update(domain=domain)
|
||||||
|
value.update(view_mode='tree,form')
|
||||||
|
return value
|
||||||
|
|
||||||
|
# END
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!--Done BY Addition IT Solutions: BEGIN -->
|
||||||
|
<!-- Override 'My Current Timesheet' form and menu here -->
|
||||||
|
<record id="view_hr_timesheet_current_open_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr_timesheet_current_open.form.inherit</field>
|
||||||
|
<field name="model">hr.timesheet.current.open</field>
|
||||||
|
<field name="inherit_id" ref="hr_timesheet_sheet.view_hr_timesheet_current_open"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//form/group/separator" position="attributes">
|
||||||
|
<attribute name="string">Generate timesheets for employees having the following tag:</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//form/group" position="after">
|
||||||
|
<group>
|
||||||
|
<field name="category_id"/>
|
||||||
|
<group colspan="2">
|
||||||
|
<field name="date_from" on_change="change_date(date_from, date_to)"/>
|
||||||
|
<field name="date_to" on_change="change_date(date_from, date_to)"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem name="Generate Timesheets"
|
||||||
|
id="menu_act_hr_timesheet_sheet_form_open_current"
|
||||||
|
parent="hr_attendance.menu_hr_time_tracking"
|
||||||
|
action="hr_timesheet_sheet.action_hr_timesheet_current_open"
|
||||||
|
groups="base.group_hr_manager"
|
||||||
|
sequence="10"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
||||||
|
|
||||||
|
<!--Done BY Addition IT Solutions: END -->
|
|
@ -0,0 +1,97 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Clear Groups for Odoo
|
||||||
|
# Copyright (C) 2016 Bytebrand GmbH (<http://www.bytebrand.net>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser 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 Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
from openerp.osv import fields, osv
|
||||||
|
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
|
from openerp.tools.translate import _
|
||||||
|
|
||||||
|
|
||||||
|
class import_leave_requests(osv.osv_memory):
|
||||||
|
_name = 'import.leave.requests'
|
||||||
|
_description = 'Import Leave Requests With Employee Tag'
|
||||||
|
_columns = {
|
||||||
|
'leave_dates': fields.binary('Select *.csv',
|
||||||
|
required=True,
|
||||||
|
help="Select csv file having "
|
||||||
|
"holiday dates."),
|
||||||
|
'leave_type_id': fields.many2one('hr.holidays.status',
|
||||||
|
'Leave Type',
|
||||||
|
required=True),
|
||||||
|
'employee_tag_id': fields.many2one('hr.employee.category',
|
||||||
|
"Employee Tag", required=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
def convert_to_user_timezone(self, user_tz, dt):
|
||||||
|
input_tz = pytz.timezone(user_tz)
|
||||||
|
converted_date = input_tz.localize(dt, is_dst=False)
|
||||||
|
converted_date = converted_date.astimezone(pytz.UTC).strftime(
|
||||||
|
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
return converted_date
|
||||||
|
|
||||||
|
def import_leave_data(self, cr, uid, ids, context=None):
|
||||||
|
holiday_obj = self.pool.get('hr.holidays')
|
||||||
|
employee_obj = self.pool.get('hr.employee')
|
||||||
|
timesheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
|
||||||
|
converter = self.pool.get('ir.fields.converter')
|
||||||
|
for data in self.browse(cr, uid, ids, context):
|
||||||
|
leaves = (data.leave_dates.decode('base64')).split('\n')
|
||||||
|
category_id = data.employee_tag_id.id
|
||||||
|
employee_ids = employee_obj.search(cr, uid, [
|
||||||
|
('category_ids', 'in', [category_id])], context=context)
|
||||||
|
for employee_id in employee_ids:
|
||||||
|
employee = employee_obj.browse(cr, uid, employee_id, context)
|
||||||
|
for leave in leaves[:-1]:
|
||||||
|
dt_fmt, tm_fmt = \
|
||||||
|
(timesheet_obj._get_user_datetime_format(
|
||||||
|
cr,
|
||||||
|
uid,
|
||||||
|
context=context))
|
||||||
|
try:
|
||||||
|
datetime.strptime(leave, dt_fmt)
|
||||||
|
except ValueError:
|
||||||
|
raise osv.except_osv(_('Data Error!'), _(
|
||||||
|
"Date format in your .csv file does not "
|
||||||
|
"match with database date format."))
|
||||||
|
dt1 = datetime.strptime(leave, dt_fmt)
|
||||||
|
dt2 = (datetime.strptime(leave, dt_fmt) +
|
||||||
|
timedelta(hours=23,
|
||||||
|
minutes=59,
|
||||||
|
seconds=59))
|
||||||
|
user_tz = employee.user_id and employee.user_id.tz or 'utc'
|
||||||
|
leave_date = self.convert_to_user_timezone(user_tz, dt1)
|
||||||
|
leave_date_to = self.convert_to_user_timezone(user_tz, dt2)
|
||||||
|
holiday_id = holiday_obj.create(cr, uid, {
|
||||||
|
'name': data.leave_type_id.name,
|
||||||
|
'date_from': leave_date,
|
||||||
|
'date_to': leave_date_to,
|
||||||
|
'holiday_status_id': data.leave_type_id.id,
|
||||||
|
'employee_id': employee_id,
|
||||||
|
'number_of_days_temp': 1.0,
|
||||||
|
'type': 'remove'
|
||||||
|
})
|
||||||
|
holiday_obj.holidays_validate(cr, uid, [holiday_id],
|
||||||
|
context=context)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# END
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--Done BY Addition IT Solutions: BEGIN -->
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record id="view_import_leave_requests" model="ir.ui.view">
|
||||||
|
<field name="name">import.leave.requests.form</field>
|
||||||
|
<field name="model">import.leave.requests</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Import Leave Requests">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<separator string="Generate leave requests for employees having the following tag:"/>
|
||||||
|
</group>
|
||||||
|
<group colspan="2">
|
||||||
|
<field name="leave_dates"/>
|
||||||
|
<field name="leave_type_id"/>
|
||||||
|
<field name="employee_tag_id"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button name="import_leave_data" string="Import" type="object" class="oe_highlight"/>
|
||||||
|
or
|
||||||
|
<button string="Cancel" class="oe_link" special="cancel" />
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_import_leave_requests" model="ir.actions.act_window">
|
||||||
|
<field name="name">Import Leave Requests</field>
|
||||||
|
<field name="res_model">import.leave.requests</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="view_id" ref="view_import_leave_requests"/>
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem name="Import Leave Requests" id="menu_import_leave_requests"
|
||||||
|
parent="hr_holidays.menu_open_ask_holidays" action="action_import_leave_requests"
|
||||||
|
groups="base.group_hr_manager" sequence="15"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
||||||
|
|
||||||
|
<!--Done BY Addition IT Solutions: END -->
|