diff --git a/hr_vacation_mngmt/__init__.py b/hr_vacation_mngmt/__init__.py new file mode 100644 index 000000000..c74433564 --- /dev/null +++ b/hr_vacation_mngmt/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +################################################################################### +# A part of OpenHrms Project +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies (). +# Author: Aswani PC () +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) 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 . +# +################################################################################### +from . import models +from . import wizard diff --git a/hr_vacation_mngmt/__manifest__.py b/hr_vacation_mngmt/__manifest__.py new file mode 100644 index 000000000..c14b4d2d3 --- /dev/null +++ b/hr_vacation_mngmt/__manifest__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +################################################################################### +# A part of Open Hrms Project +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies (). +# Author: Aswani PC () +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) 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 . +# +################################################################################### +{ + 'name': "Open HRMS Vacation Management", + 'version': '11.0.1.0.0', + 'summary': """Manage Employee Vacation""", + 'description': """HR Vacation management""", + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'website': 'https://www.openhrms.com', + 'category': 'Generic Modules/Human Resources', + 'depends': ['hr_leave_request_aliasing','project', 'hr_payroll', 'account'], + 'data': [ + 'security/hr_vacation_security.xml', + 'security/ir.model.access.csv', + 'data/hr_payslip_data.xml', + 'views/hr_reminder.xml', + 'data/hr_vacation_data.xml', + 'wizard/reassign_task.xml', + 'views/hr_employee_ticket.xml', + 'views/hr_vacation.xml', + 'views/hr_payslip.xml', + ], + 'images': ['static/description/banner.jpg'], + 'license': 'AGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/hr_vacation_mngmt/data/hr_payslip_data.xml b/hr_vacation_mngmt/data/hr_payslip_data.xml new file mode 100644 index 000000000..68d534755 --- /dev/null +++ b/hr_vacation_mngmt/data/hr_payslip_data.xml @@ -0,0 +1,31 @@ + + + + Leave Salary + + + + + + Leave Salary + LS + + + + none + code + result = categories.BASIC + + + + Leave Salary + LS + + + + none + code + result = categories.BASIC + categories.ALW + + + \ No newline at end of file diff --git a/hr_vacation_mngmt/data/hr_vacation_data.xml b/hr_vacation_mngmt/data/hr_vacation_data.xml new file mode 100644 index 000000000..b0a618611 --- /dev/null +++ b/hr_vacation_mngmt/data/hr_vacation_data.xml @@ -0,0 +1,31 @@ + + + + + Flight ticket status update + + code + model.run_update_ticket_status() + 1 + days + -1 + + + + + HR Leave Reminder + + code + model.send_leave_reminder() + 1 + days + -1 + + + + + Airlines + True + + + \ No newline at end of file diff --git a/hr_vacation_mngmt/doc/RELEASE_NOTES.md b/hr_vacation_mngmt/doc/RELEASE_NOTES.md new file mode 100644 index 000000000..354e022df --- /dev/null +++ b/hr_vacation_mngmt/doc/RELEASE_NOTES.md @@ -0,0 +1,6 @@ +## Module + +#### 25.04.2018 +#### Version 11.0.1.0.0 +##### ADD +- Initial commit for OpenHrms Project diff --git a/hr_vacation_mngmt/doc/RELEASE_NOTES.md~ b/hr_vacation_mngmt/doc/RELEASE_NOTES.md~ new file mode 100644 index 000000000..1a18ce542 --- /dev/null +++ b/hr_vacation_mngmt/doc/RELEASE_NOTES.md~ @@ -0,0 +1,6 @@ +## Module + +#### 30.03.2018 +#### Version 10.0.1.0.0 +##### ADD +- Initial commit for OpenHrms Project diff --git a/hr_vacation_mngmt/models/__init__.py b/hr_vacation_mngmt/models/__init__.py new file mode 100644 index 000000000..827a12580 --- /dev/null +++ b/hr_vacation_mngmt/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import hr_vacation +from . import hr_payslip +from . import hr_employee_ticket diff --git a/hr_vacation_mngmt/models/hr_employee_ticket.py b/hr_vacation_mngmt/models/hr_employee_ticket.py new file mode 100644 index 000000000..46df3c75b --- /dev/null +++ b/hr_vacation_mngmt/models/hr_employee_ticket.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +from datetime import datetime +from odoo import models, fields, api, _ +from odoo.exceptions import UserError, ValidationError + + +class HrFlightTicket(models.Model): + _name = 'hr.flight.ticket' + + name = fields.Char() + employee_id = fields.Many2one('hr.employee', string='Employee', required=True) + ticket_type = fields.Selection([('one', 'One Way'), ('round', 'Round Trip')], string='Ticket Type', default='round') + depart_from = fields.Char(string='Departure', required=True) + destination = fields.Char(string='Destination', required=True) + date_start = fields.Date(string='Start Date', required=True) + date_return = fields.Date(string='Return Date') + ticket_class = fields.Selection([('economy', 'Economy'), + ('premium_economy', 'Premium Economy'), + ('business', 'Business'), + ('first_class', 'First Class')], string='Class') + ticket_fare = fields.Float(string='Ticket Fare') + flight_details = fields.Text(string='Flight Details') + return_flight_details = fields.Text(string='Return Flight Details') + state = fields.Selection([('booked', 'Booked'), ('confirmed', 'Confirmed'), ('started', 'Started'), + ('completed', 'Completed'), ('canceled', 'Canceled')], string='Status', default='booked') + invoice_id = fields.Many2one('account.invoice', string='Invoice') + leave_id = fields.Many2one('hr.holidays', string='Leave') + company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.user.company_id) + + @api.multi + def name_get(self): + res = [] + for ticket in self: + res.append((ticket.id, _("Flight ticket for %s on %s to %s") % ( + ticket.employee_id.name, ticket.date_start, ticket.destination))) + return res + + @api.constrains('date_start', 'date_return') + def check_valid_date(self): + if self.filtered(lambda c: c.date_return and c.date_start > c.date_return): + raise ValidationError(_('Flight travelling start date must be less than flight return date.')) + + def book_ticket(self): + return {'type': 'ir.actions.act_window_close'} + + def confirm_ticket(self): + if self.ticket_fare <= 0: + raise UserError(_('Please add ticket fare.')) + inv_obj = self.env['account.invoice'].sudo() + expense_account = self.env['ir.config_parameter'].sudo().get_param('travel_expense_account') + if not expense_account: + raise UserError(_('Please select expense account for the flight tickets.')) + domain = [ + ('type', '=', 'purchase'), + ('company_id', '=', self.company_id.id), + ] + journal_id = self.env['account.journal'].search(domain, limit=1) + partner = self.env.ref('hr_vacation_mngmt.air_lines_partner') + if not partner.property_payment_term_id: + date_due = fields.Date.context_today(self) + else: + pterm = partner.property_payment_term_id + pterm_list = \ + pterm.with_context(currency_id=self.env.user.company_id.id).compute( + value=1, date_ref=fields.Date.context_today(self))[0] + date_due = max(line[0] for line in pterm_list) + inv_data = { + 'name': '', + 'origin': 'Flight Ticket', + 'type': 'in_invoice', + 'journal_id': journal_id.id, + 'payment_term_id': partner.property_payment_term_id.id, + 'date_due': date_due, + 'reference': False, + 'partner_id': partner.id, + 'account_id': partner.property_account_payable_id.id, + 'invoice_line_ids': [(0, 0, { + 'name': 'Flight Ticket', + 'price_unit': self.ticket_fare, + 'quantity': 1.0, + 'account_id': expense_account, + })], + } + inv_id = inv_obj.create(inv_data) + inv_id.action_invoice_open() + self.write({'state': 'confirmed', 'invoice_id': inv_id.id}) + + def cancel_ticket(self): + if self.state == 'booked': + self.write({'state': 'canceled'}) + elif self.state == 'confirmed': + if self.invoice_id and self.invoice_id.state == 'paid': + self.write({'state': 'canceled'}) + if self.invoice_id and self.invoice_id.state == 'open': + self.invoice_id.action_invoice_cancel() + self.write({'state': 'canceled'}) + + @api.model + def run_update_ticket_status(self): + run_out_tickets = self.search([('state', 'in', ['confirmed', 'started']), + ('date_return', '<=', datetime.now())]) + confirmed_tickets = self.search([('state', '=', 'confirmed'), ('date_start', '<=', datetime.now()), + ('date_return', '>', datetime.now())]) + for ticket in run_out_tickets: + ticket.write({'state': 'completed'}) + for ticket in confirmed_tickets: + ticket.write({'state': 'started'}) + + @api.multi + def action_view_invoice(self): + return { + 'name': _('Flight Ticket Invoice'), + 'view_mode': 'form', + 'view_id': self.env.ref('account.invoice_supplier_form').id, + 'res_model': 'account.invoice', + 'context': "{'type':'in_invoice'}", + 'type': 'ir.actions.act_window', + 'res_id': self.invoice_id.id, + } diff --git a/hr_vacation_mngmt/models/hr_payslip.py b/hr_vacation_mngmt/models/hr_payslip.py new file mode 100644 index 000000000..7c6fb6a32 --- /dev/null +++ b/hr_vacation_mngmt/models/hr_payslip.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api, _ + + +class HrPayslip(models.Model): + _inherit = 'hr.payslip' + + leave_salary = fields.Boolean(string='Leave Salary') + + @api.model + def _get_payslip_lines(self, contract_ids, payslip_id): + def _sum_salary_rule_category(localdict, category, amount): + if category.parent_id: + localdict = _sum_salary_rule_category(localdict, category.parent_id, amount) + localdict['categories'].dict[category.code] = category.code in localdict['categories'].dict and localdict['categories'].dict[category.code] + amount or amount + return localdict + + class BrowsableObject(object): + def __init__(self, employee_id, dict, env): + self.employee_id = employee_id + self.dict = dict + self.env = env + + def __getattr__(self, attr): + return attr in self.dict and self.dict.__getitem__(attr) or 0.0 + + class InputLine(BrowsableObject): + """a class that will be used into the python code, mainly for usability purposes""" + def sum(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute(""" + SELECT sum(amount) as sum + FROM hr_payslip as hp, hr_payslip_input as pi + WHERE hp.employee_id = %s AND hp.state = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""", + (self.employee_id, from_date, to_date, code)) + return self.env.cr.fetchone()[0] or 0.0 + + class WorkedDays(BrowsableObject): + """a class that will be used into the python code, mainly for usability purposes""" + def _sum(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute(""" + SELECT sum(number_of_days) as number_of_days, sum(number_of_hours) as number_of_hours + FROM hr_payslip as hp, hr_payslip_worked_days as pi + WHERE hp.employee_id = %s AND hp.state = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s""", + (self.employee_id, from_date, to_date, code)) + return self.env.cr.fetchone() + + def sum(self, code, from_date, to_date=None): + res = self._sum(code, from_date, to_date) + return res and res[0] or 0.0 + + def sum_hours(self, code, from_date, to_date=None): + res = self._sum(code, from_date, to_date) + return res and res[1] or 0.0 + + class Payslips(BrowsableObject): + """a class that will be used into the python code, mainly for usability purposes""" + + def sum(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute("""SELECT sum(case when hp.credit_note = False then (pl.total) else (-pl.total) end) + FROM hr_payslip as hp, hr_payslip_line as pl + WHERE hp.employee_id = %s AND hp.state = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s""", + (self.employee_id, from_date, to_date, code)) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + #we keep a dict with the result because a value can be overwritten by another rule with the same code + result_dict = {} + rules_dict = {} + worked_days_dict = {} + inputs_dict = {} + blacklist = [] + payslip = self.env['hr.payslip'].browse(payslip_id) + for worked_days_line in payslip.worked_days_line_ids: + worked_days_dict[worked_days_line.code] = worked_days_line + for input_line in payslip.input_line_ids: + inputs_dict[input_line.code] = input_line + + categories = BrowsableObject(payslip.employee_id.id, {}, self.env) + inputs = InputLine(payslip.employee_id.id, inputs_dict, self.env) + worked_days = WorkedDays(payslip.employee_id.id, worked_days_dict, self.env) + payslips = Payslips(payslip.employee_id.id, payslip, self.env) + rules = BrowsableObject(payslip.employee_id.id, rules_dict, self.env) + + baselocaldict = {'categories': categories, 'rules': rules, 'payslip': payslips, 'worked_days': worked_days, + 'inputs': inputs} + # get the ids of the structures on the contracts and their parent id as well + contracts = self.env['hr.contract'].browse(contract_ids) + structure_ids = contracts.get_all_structures() + # get the rules of the structure and thier children + rule_ids = self.env['hr.payroll.structure'].browse(structure_ids).get_all_rules() + # leave salary computation + if payslip.leave_salary: + leave_sal_basic = self.env.ref('hr_vacation_mngmt.hr_salary_rule_leave_salary_basic') + leave_sal_gross = self.env.ref('hr_vacation_mngmt.hr_salary_rule_leave_salary_gross') + default_leave_salary = self.env['ir.config_parameter'].sudo().get_param('default_leave_salary') + if default_leave_salary == '0': + leave_salary = leave_sal_basic + elif default_leave_salary == '1': + leave_salary = leave_sal_gross + else: + leave_salary = leave_sal_basic + rule_ids.append((leave_salary.id, leave_salary.sequence)) + # run the rules by sequence + sorted_rule_ids = [id for id, sequence in sorted(rule_ids, key=lambda x:x[1])] + sorted_rules = self.env['hr.salary.rule'].browse(sorted_rule_ids) + + for contract in contracts: + employee = contract.employee_id + localdict = dict(baselocaldict, employee=employee, contract=contract) + for rule in sorted_rules: + key = rule.code + '-' + str(contract.id) + localdict['result'] = None + localdict['result_qty'] = 1.0 + localdict['result_rate'] = 100 + #check if the rule can be applied + if rule._satisfy_condition(localdict) and rule.id not in blacklist: + #compute the amount of the rule + amount, qty, rate = rule._compute_rule(localdict) + #check if there is already a rule computed with that code + previous_amount = rule.code in localdict and localdict[rule.code] or 0.0 + #set/overwrite the amount computed for this rule in the localdict + tot_rule = amount * qty * rate / 100.0 + localdict[rule.code] = tot_rule + rules_dict[rule.code] = rule + #sum the amount for its salary category + localdict = _sum_salary_rule_category(localdict, rule.category_id, tot_rule - previous_amount) + #create/overwrite the rule in the temporary results + result_dict[key] = { + 'salary_rule_id': rule.id, + 'contract_id': contract.id, + 'name': rule.name, + 'code': rule.code, + 'category_id': rule.category_id.id, + 'sequence': rule.sequence, + 'appears_on_payslip': rule.appears_on_payslip, + 'condition_select': rule.condition_select, + 'condition_python': rule.condition_python, + 'condition_range': rule.condition_range, + 'condition_range_min': rule.condition_range_min, + 'condition_range_max': rule.condition_range_max, + 'amount_select': rule.amount_select, + 'amount_fix': rule.amount_fix, + 'amount_python_compute': rule.amount_python_compute, + 'amount_percentage': rule.amount_percentage, + 'amount_percentage_base': rule.amount_percentage_base, + 'register_id': rule.register_id.id, + 'amount': amount, + 'employee_id': contract.employee_id.id, + 'quantity': qty, + 'rate': rate, + } + else: + #blacklist this rule and its children + blacklist += [id for id, seq in rule._recursive_search_of_rules()] + + return list(result_dict.values()) + + +class HrPayrollConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + default_leave_salary = fields.Selection([('0', 'Basic'), ('1', 'Gross')], string='Leave Salary') + + def get_values(self): + res = super(HrPayrollConfigSettings, self).get_values() + res.update( + default_leave_salary=self.env['ir.config_parameter'].sudo().get_param('default_leave_salary') + ) + return res + + def set_values(self): + super(HrPayrollConfigSettings, self).set_values() + self.env['ir.config_parameter'].sudo().set_param('default_leave_salary', self.default_leave_salary) diff --git a/hr_vacation_mngmt/models/hr_vacation.py b/hr_vacation_mngmt/models/hr_vacation.py new file mode 100644 index 000000000..6175c0789 --- /dev/null +++ b/hr_vacation_mngmt/models/hr_vacation.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- + +from datetime import datetime, timedelta, date +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + + +class HrLeaveRequest(models.Model): + _inherit = 'hr.holidays' + + remaining_leaves = fields.Float(string='Remaining Legal Leaves', related='employee_id.remaining_leaves') + overlapping_leaves = fields.Many2many('hr.holidays', compute='get_overlapping_leaves', string='Overlapping Leaves') + pending_tasks = fields.One2many('pending.task', 'leave_id', string='Pending Tasks') + holiday_managers = fields.Many2many('res.users', compute='get_hr_holiday_managers') + flight_ticket = fields.One2many('hr.flight.ticket', 'leave_id', string='Flight Ticket') + + @api.one + def get_overlapping_leaves(self): + if self.type == 'remove' and self.date_from and self.date_to: + overlap_leaves = [] + from_date = datetime.strptime(self.date_from, '%Y-%m-%d %H:%M:%S').date() + to_date = datetime.strptime(self.date_to, '%Y-%m-%d %H:%M:%S').date() + r = (to_date + timedelta(days=1) - from_date).days + leave_dates = [str(from_date + timedelta(days=i)) for i in range(r)] + leaves = self.env['hr.holidays'].search([('state', '=', 'validate'), ('type', '=', 'remove'), + ('department_id', '=', self.department_id.id)]) + other_leaves = leaves - self + for leave in other_leaves: + frm_dte = datetime.strptime(leave.date_from, '%Y-%m-%d %H:%M:%S').date() + to_dte = datetime.strptime(leave.date_to, '%Y-%m-%d %H:%M:%S').date() + r = (to_dte + timedelta(days=1) - frm_dte).days + leave_dtes = [str(frm_dte + timedelta(days=i)) for i in range(r)] + if set(leave_dtes).intersection(set(leave_dates)): + overlap_leaves.append(leave.id) + self.update({'overlapping_leaves': [(6, 0, overlap_leaves)]}) + + @api.multi + def action_approve(self): + # if double_validation: this method is the first approval approval + # if not double_validation: this method calls action_validate() below + if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): + raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) + + manager = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + for holiday in self: + if holiday.state != 'confirm': + raise UserError(_('Leave request must be confirmed ("To Approve") in order to approve it.')) + + if holiday.pending_tasks: + if holiday.user_id: + ctx = dict(self.env.context or {}) + ctx.update({ + 'default_leave_req_id': self.id, + }) + return { + 'name': _('Re-Assign Task'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'task.reassign', + 'target': 'new', + 'context': ctx, + } + else: + raise UserError(_('Please configure user for the employee %s') % (holiday.employee_id.name,)) + else: + if holiday.double_validation: + return holiday.write({'state': 'validate1', 'manager_id': manager.id if manager else False}) + else: + holiday.action_validate() + + def book_ticket(self): + if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): + raise UserError(_('Only an HR Officer or Manager can book flight tickets.')) + ctx = dict(self.env.context or {}) + ctx.update({ + 'default_employee_id': self.employee_id.id, + 'default_leave_id': self.id, + }) + return { + 'name': _('Book Flight Ticket'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'view_id': self.env.ref('hr_vacation_mngmt.view_hr_book_flight_ticket_form').id, + 'res_model': 'hr.flight.ticket', + 'target': 'new', + 'context': ctx, + } + + @api.one + def get_hr_holiday_managers(self): + self.holiday_managers = self.env.ref('hr_holidays.group_hr_holidays_manager').users + + def view_flight_ticket(self): + return { + 'name': _('Flight Ticket'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'hr.flight.ticket', + 'target': 'current', + 'res_id': self.flight_ticket[0].id, + } + + @api.model + def send_leave_reminder(self): + leave_request = self.env['hr.holidays'].search([('type', '=', 'remove'), ('state', '=', 'validate')]) + leave_reminder = self.env['ir.config_parameter'].sudo().get_param('leave_reminder') + reminder_day_before = int(self.env['ir.config_parameter'].sudo().get_param('reminder_day_before')) + mail_template = self.env.ref('hr_vacation_mngmt.email_template_hr_leave_reminder_mail') + holiday_managers = self.env.ref('hr_holidays.group_hr_holidays_manager').users + today = date.today() + if leave_reminder: + for request in leave_request: + if request.date_from: + from_date = datetime.strptime(request.date_from, '%Y-%m-%d %H:%M:%S').date() + if reminder_day_before == 0: + prev_reminder_day = request.date_from + else: + prev_reminder_day = from_date - timedelta(days=reminder_day_before) + if prev_reminder_day == today: + for manager in holiday_managers: + template = mail_template.sudo().with_context( + email_to=manager.email, + ) + template.send_mail(request.id, force_send=True) + + +class PendingTask(models.Model): + _name = 'pending.task' + + name = fields.Char(string='Task', required=True) + leave_id = fields.Many2one('hr.holidays', string='Leave Request') + dept_id = fields.Many2one('hr.department', string='Department', related='leave_id.department_id') + project_id = fields.Many2one('project.project', string='Project', required=True) + description = fields.Text(string='Description') + assigned_to = fields.Many2one('hr.employee', string='Assigned to', + domain="[('department_id', '=', dept_id)]") + unavailable_employee = fields.Many2many('hr.employee', string='Unavailable Employees', + compute='get_unavailable_employee') + + @api.one + def get_unavailable_employee(self): + unavail_emp = [] + for leave in self.leave_id.overlapping_leaves: + unavail_emp.append(leave.employee_id.id) + self.update({'unavailable_employee': unavail_emp}) + + +class HrVacationConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + leave_reminder = fields.Boolean(string='Leave Reminder Email', help="Send leave remainder emails to hr managers") + reminder_day_before = fields.Integer(string='Reminder Day Before') + default_expense_account = fields.Many2one('account.account', string='Travel Expense Account') + + def get_values(self): + res = super(HrVacationConfigSettings, self).get_values() + res.update( + leave_reminder=self.env['ir.config_parameter'].sudo().get_param('leave_reminder'), + reminder_day_before=int(self.env['ir.config_parameter'].sudo().get_param('reminder_day_before')), + default_expense_account=int(self.env['ir.config_parameter'].sudo().get_param('travel_expense_account')) + ) + return res + + def set_values(self): + super(HrVacationConfigSettings, self).set_values() + self.env['ir.config_parameter'].sudo().set_param('leave_reminder', self.leave_reminder) + self.env['ir.config_parameter'].sudo().set_param('reminder_day_before', self.reminder_day_before) + self.env['ir.config_parameter'].sudo().set_param('travel_expense_account', self.default_expense_account.id) diff --git a/hr_vacation_mngmt/security/hr_vacation_security.xml b/hr_vacation_mngmt/security/hr_vacation_security.xml new file mode 100644 index 000000000..a7bba429f --- /dev/null +++ b/hr_vacation_mngmt/security/hr_vacation_security.xml @@ -0,0 +1,11 @@ + + + + + Hr Flight Ticket Multi Company + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + \ No newline at end of file diff --git a/hr_vacation_mngmt/security/ir.model.access.csv b/hr_vacation_mngmt/security/ir.model.access.csv new file mode 100644 index 000000000..74531a7eb --- /dev/null +++ b/hr_vacation_mngmt/security/ir.model.access.csv @@ -0,0 +1,8 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_flight_ticket_user,hr.flight.ticket.user,model_hr_flight_ticket,hr_holidays.group_hr_holidays_user,1,1,1,1 +access_hr_flight_ticket_employee,hr.flight.ticket.employee,model_hr_flight_ticket,base.group_user,1,1,1,1 +access_pending_task_user,pending.task.user,model_pending_task,hr_holidays.group_hr_holidays_user,1,1,1,1 +access_pending_task_employee,pending.task.employee,model_pending_task,base.group_user,1,1,1,1 +access_account_invoice,account.invoice.hr_manager,account.model_account_invoice,hr_holidays.group_hr_holidays_manager,1,0,0,0 +access_account_invoice_tax,account.invoice.tax.hr_manager,account.model_account_invoice_tax,hr_holidays.group_hr_holidays_manager,1,0,0,0 +access_account_move_line,account.move.line.hr_manager,account.model_account_move_line,hr_holidays.group_hr_holidays_manager,1,0,0,0 \ No newline at end of file diff --git a/hr_vacation_mngmt/static/description/HRMS-BUTTON.png b/hr_vacation_mngmt/static/description/HRMS-BUTTON.png new file mode 100644 index 000000000..0f1b65bea Binary files /dev/null and b/hr_vacation_mngmt/static/description/HRMS-BUTTON.png differ diff --git a/hr_vacation_mngmt/static/description/banner.jpg b/hr_vacation_mngmt/static/description/banner.jpg new file mode 100644 index 000000000..beb870c2b Binary files /dev/null and b/hr_vacation_mngmt/static/description/banner.jpg differ diff --git a/hr_vacation_mngmt/static/description/cybro-service.png b/hr_vacation_mngmt/static/description/cybro-service.png new file mode 100644 index 000000000..252929a86 Binary files /dev/null and b/hr_vacation_mngmt/static/description/cybro-service.png differ diff --git a/hr_vacation_mngmt/static/description/cybro_logo.png b/hr_vacation_mngmt/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/hr_vacation_mngmt/static/description/cybro_logo.png differ diff --git a/hr_vacation_mngmt/static/description/icon.png b/hr_vacation_mngmt/static/description/icon.png new file mode 100644 index 000000000..cf3082e3d Binary files /dev/null and b/hr_vacation_mngmt/static/description/icon.png differ diff --git a/hr_vacation_mngmt/static/description/index.html b/hr_vacation_mngmt/static/description/index.html new file mode 100644 index 000000000..8313a1a30 --- /dev/null +++ b/hr_vacation_mngmt/static/description/index.html @@ -0,0 +1,216 @@ +
+
+

Open HRMS

+

Most advanced open source HR management software

+
+
+
+
+
+
+ + + +
+
+
+
+
+
+

Open HRMS Vacation Management

+

Manage Employee Vacation

+

Cybrosys Technologies

+
+
+

Features:

+
+ Remaining legal leaves in leave request to approval.
+ Overlapping leaves.
+ Leave notification.
+ Leave salary.
+ Pending task update and re-assign task.
+ Flight ticket booking.
+
+
+
+ +
+
+
+
+

Overview

+

+ This module extend Odoo default HR Holiday Management with extra features adaptable for managing employees vacation.. +

+
+
+
+
+ +
+
+
+

Remaining & Overlapping Leaves

+

+ +
+
+ +
+
+
+

Pending Tasks Update & Task Re-assign

+ +

+
+

+ Employees can able to update their pending tasks on leave request +

+ +
+ +
+
+
+ +
+

+ On leave request approval it opens a wizard to re-assign tasks to available employees. +

+ +
+ +
+
+
+
+
+ +
+
+
+

Flight Ticket Booking

+

+
+

+ HR managers can able to book the flight ticket for the employee. +

+ +
+ +
+
+
+ + + +
+

+ On confirming the flight ticket generate corresponding supplier invoice. +

+ +
+ +
+
+
+
+
+ +
+
+
+

Leave Salary

+

+ +
+
+ +
+
+
+

Configurations

+

+ + +
+
+ +
+
+

Our Odoo Services

+
+
+ +
+
+ +
+

Need Any Help?

+ +
+ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_01.png b/hr_vacation_mngmt/static/description/ohrms_vacation_01.png new file mode 100644 index 000000000..e0c3b2065 Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_01.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_02.png b/hr_vacation_mngmt/static/description/ohrms_vacation_02.png new file mode 100644 index 000000000..f5fd72e74 Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_02.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_03.png b/hr_vacation_mngmt/static/description/ohrms_vacation_03.png new file mode 100644 index 000000000..b70b7530c Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_03.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_04.png b/hr_vacation_mngmt/static/description/ohrms_vacation_04.png new file mode 100644 index 000000000..ee4a82ced Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_04.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_05.png b/hr_vacation_mngmt/static/description/ohrms_vacation_05.png new file mode 100644 index 000000000..74ece7ff9 Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_05.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_06.png b/hr_vacation_mngmt/static/description/ohrms_vacation_06.png new file mode 100644 index 000000000..96660682b Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_06.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_07.png b/hr_vacation_mngmt/static/description/ohrms_vacation_07.png new file mode 100644 index 000000000..b43c12347 Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_07.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_08.png b/hr_vacation_mngmt/static/description/ohrms_vacation_08.png new file mode 100644 index 000000000..c190c4950 Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_08.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_09.png b/hr_vacation_mngmt/static/description/ohrms_vacation_09.png new file mode 100644 index 000000000..84221ab4b Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_09.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_10.png b/hr_vacation_mngmt/static/description/ohrms_vacation_10.png new file mode 100644 index 000000000..29079dca1 Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_10.png differ diff --git a/hr_vacation_mngmt/static/description/ohrms_vacation_11.png b/hr_vacation_mngmt/static/description/ohrms_vacation_11.png new file mode 100644 index 000000000..ad6004e62 Binary files /dev/null and b/hr_vacation_mngmt/static/description/ohrms_vacation_11.png differ diff --git a/hr_vacation_mngmt/views/hr_employee_ticket.xml b/hr_vacation_mngmt/views/hr_employee_ticket.xml new file mode 100644 index 000000000..b845df735 --- /dev/null +++ b/hr_vacation_mngmt/views/hr_employee_ticket.xml @@ -0,0 +1,121 @@ + + + + + hr.flight.ticket.form + hr.flight.ticket + + +
+
+
+ +
+ + +
+
+

+
+ + + + + + + + + + + + + + + + + + +
+
+
+
+ + + hr.flight.ticket.form + hr.flight.ticket + + + + + + + + + + + + + + hr.flight.ticket.form + hr.flight.ticket + + +
+
+

+
+ + + + + + + + + + + + + + +
+
+
+
+
+ + + Flight Tickets + hr.flight.ticket + tree,form + form + +

+ There is no previously booked flight ticket details available. +

+
+
+ + +
+
\ No newline at end of file diff --git a/hr_vacation_mngmt/views/hr_payslip.xml b/hr_vacation_mngmt/views/hr_payslip.xml new file mode 100644 index 000000000..a29bc263e --- /dev/null +++ b/hr_vacation_mngmt/views/hr_payslip.xml @@ -0,0 +1,39 @@ + + + + + hr.payslip.form + hr.payslip + + + + + + + + + + Configure Payroll + res.config.settings + + + +

Leaves

+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/hr_vacation_mngmt/views/hr_reminder.xml b/hr_vacation_mngmt/views/hr_reminder.xml new file mode 100644 index 000000000..eeb7614e2 --- /dev/null +++ b/hr_vacation_mngmt/views/hr_reminder.xml @@ -0,0 +1,20 @@ + + + + + Leave : Reminder + + + ${object.employee_id.company_id.email} + Reminder: ${object.display_name} + Hello ,

+

The employee ${object.employee_id.name} has taken ${object.no_of_days_temp} days leave starting from ${object.date_from} to ${object.date_to}.

+ +

Kindly do the needful.

+ +

Thank you!

+]]>
+
+
+
\ No newline at end of file diff --git a/hr_vacation_mngmt/views/hr_vacation.xml b/hr_vacation_mngmt/views/hr_vacation.xml new file mode 100644 index 000000000..9e53fad57 --- /dev/null +++ b/hr_vacation_mngmt/views/hr_vacation.xml @@ -0,0 +1,114 @@ + + + + + Leave Request + hr.holidays + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + pending.task.form + pending.task + +
+ + + + + + + + + + + + + + + +
+
+
+ + + Configure Leave + res.config.settings + + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/hr_vacation_mngmt/wizard/__init__.py b/hr_vacation_mngmt/wizard/__init__.py new file mode 100644 index 000000000..14c06bcd4 --- /dev/null +++ b/hr_vacation_mngmt/wizard/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import reassign_task diff --git a/hr_vacation_mngmt/wizard/reassign_task.py b/hr_vacation_mngmt/wizard/reassign_task.py new file mode 100644 index 000000000..f3f70e003 --- /dev/null +++ b/hr_vacation_mngmt/wizard/reassign_task.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + + +class ReAssignTask(models.TransientModel): + _name = 'task.reassign' + + pending_tasks = fields.One2many('pending.task', related='leave_req_id.pending_tasks', string='Pending Tasks') + leave_req_id = fields.Many2one('hr.holidays', string='Leave Request') + + @api.multi + def action_approve(self): + task_pending = False + e_unavail = False + emp_unavail = [] + for task in self.pending_tasks: + if not task.assigned_to: + task_pending = True + if task_pending: + raise UserError(_('Please assign pending task to employees.')) + else: + for task in self.pending_tasks: + if task.assigned_to in task.unavailable_employee: + emp_unavail.append(task.assigned_to.name) + e_unavail = True + emp_unavail = set(emp_unavail) + emp_unavail_count = len(emp_unavail) + if e_unavail: + if emp_unavail_count == 1: + raise UserError(_('Selected employee %s is not available') % (', '.join(emp_unavail),)) + else: + raise UserError(_('Selected employees %s are not available') % (', '.join(emp_unavail),)) + + else: + manager = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + holiday = self.leave_req_id + tasks = self.env['project.task'] + for task in self.pending_tasks: + if not task.assigned_to.user_id: + raise UserError(_('Please configure user for the employee %s') % (task.assigned_to.name,)) + vals = { + 'name': task.name, + 'user_id': task.assigned_to.user_id.id, + 'project_id': task.project_id.id, + 'description': task.description, + } + tasks.sudo().create(vals) + if holiday.double_validation: + return holiday.write({'state': 'validate1', 'manager_id': manager.id if manager else False}) + else: + holiday.action_validate() + + @api.multi + def cancel(self): + for task in self.pending_tasks: + task.update({'assigned_to': False}) + return {'type': 'ir.actions.act_window_close'} diff --git a/hr_vacation_mngmt/wizard/reassign_task.xml b/hr_vacation_mngmt/wizard/reassign_task.xml new file mode 100644 index 000000000..8625127c5 --- /dev/null +++ b/hr_vacation_mngmt/wizard/reassign_task.xml @@ -0,0 +1,31 @@ + + + + + Re-Assign Task + task.reassign + +
+

Confirm leave request and reassign pending works of the employee.

+ + + + + + + + + + + + + + +
+
+
+
+
\ No newline at end of file