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
+
+
+
+
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.
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
Our Odoo Services
+
+
+

+
+
+
+
+
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
+
+
+
+
+
+ Leave salary calculation
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
+
+
+
+
+
+
+
+
+
+
+ Send leave remainder emails to holiday managers
+
+
+
+
+
+
+
+
+
+
\ 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
+
+
+
+
+
+
\ No newline at end of file