Merge remote-tracking branch 'origin/feature/share_module' into feature/share_module
# Conflicts: # addons_extensions/grace_period/models/__init__.py
This commit is contained in:
commit
5c6341d8b7
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
'name': 'Grace period Attendance',
|
||||
'version': '18.0.1.0.0',
|
||||
'category': 'Human Resources',
|
||||
'summary': 'Attendances grace Period',
|
||||
'author': 'Srivyn Platforms',
|
||||
'license': 'LGPL-3',
|
||||
|
||||
'depends': [
|
||||
'hr',
|
||||
'hr_attendance',
|
||||
'hr_holidays',
|
||||
'resource',
|
||||
'hr_attendance_extended',
|
||||
],
|
||||
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/resource_calendar_weekday_data.xml',
|
||||
'data/sequence.xml',
|
||||
'views/resource_calendar_period.xml',
|
||||
'views/attendance_data.xml',
|
||||
'views/late_coming_request.xml',
|
||||
'views/hr_employee_inherit.xml',
|
||||
'views/late_coming_mail_template.xml',
|
||||
# 'security/record_rules.xml',
|
||||
],
|
||||
|
||||
'installable': True,
|
||||
'application': False,
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="weekday_monday"
|
||||
model="resource.calendar.weekday">
|
||||
<field name="name">Monday</field>
|
||||
<field name="code">0</field>
|
||||
</record>
|
||||
|
||||
<record id="weekday_tuesday"
|
||||
model="resource.calendar.weekday">
|
||||
<field name="name">Tuesday</field>
|
||||
<field name="code">1</field>
|
||||
</record>
|
||||
|
||||
<record id="weekday_wednesday"
|
||||
model="resource.calendar.weekday">
|
||||
<field name="name">Wednesday</field>
|
||||
<field name="code">2</field>
|
||||
</record>
|
||||
|
||||
<record id="weekday_thursday"
|
||||
model="resource.calendar.weekday">
|
||||
<field name="name">Thursday</field>
|
||||
<field name="code">3</field>
|
||||
</record>
|
||||
|
||||
<record id="weekday_friday"
|
||||
model="resource.calendar.weekday">
|
||||
<field name="name">Friday</field>
|
||||
<field name="code">4</field>
|
||||
</record>
|
||||
|
||||
<record id="weekday_saturday"
|
||||
model="resource.calendar.weekday">
|
||||
<field name="name">Saturday</field>
|
||||
<field name="code">5</field>
|
||||
</record>
|
||||
|
||||
<record id="weekday_sunday"
|
||||
model="resource.calendar.weekday">
|
||||
<field name="name">Sunday</field>
|
||||
<field name="code">6</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<odoo>
|
||||
|
||||
<record id="seq_late_coming_request"
|
||||
model="ir.sequence">
|
||||
|
||||
<field name="name">
|
||||
Late Coming Request
|
||||
</field>
|
||||
|
||||
<field name="code">
|
||||
late.coming.request
|
||||
</field>
|
||||
|
||||
<field name="prefix">
|
||||
LCR/
|
||||
</field>
|
||||
|
||||
<field name="padding">
|
||||
5
|
||||
</field>
|
||||
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,34 @@
|
|||
from odoo import fields, models
|
||||
|
||||
|
||||
class HREmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
attendance_analytics_count = fields.Integer(
|
||||
string="Attendance Count",
|
||||
compute="_compute_attendance_analytics_count"
|
||||
)
|
||||
|
||||
def _compute_attendance_analytics_count(self):
|
||||
|
||||
analytics = self.env['attendance.analytics']
|
||||
|
||||
for rec in self:
|
||||
rec.attendance_analytics_count = analytics.search_count([
|
||||
('employee_id', '=', rec.id)
|
||||
])
|
||||
|
||||
def action_open_attendance_analytics(self):
|
||||
|
||||
self.ensure_one()
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Attendance Sheet',
|
||||
'res_model': 'attendance.analytics',
|
||||
'view_mode': 'list,pivot,graph',
|
||||
'domain': [('employee_id', '=', self.id)],
|
||||
'context': {
|
||||
'search_default_employee_id': self.id
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
from odoo import fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class LateComingRequest(models.Model):
|
||||
_name = 'late.coming.request'
|
||||
_description = 'Late Coming Request'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_rec_name = 'employee_id'
|
||||
|
||||
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee',
|
||||
string="Employee",
|
||||
required=True
|
||||
)
|
||||
|
||||
manager_id = fields.Many2one(
|
||||
'hr.employee',
|
||||
related='employee_id.parent_id',
|
||||
store=True,
|
||||
string="Manager"
|
||||
)
|
||||
|
||||
department_id = fields.Many2one(
|
||||
'hr.department',
|
||||
related='employee_id.department_id',
|
||||
store=True,
|
||||
string="Department"
|
||||
)
|
||||
|
||||
attendance_date = fields.Date(
|
||||
string="Attendance Date"
|
||||
)
|
||||
|
||||
check_in = fields.Datetime(
|
||||
string="Check In"
|
||||
)
|
||||
|
||||
worked_hours = fields.Float(
|
||||
string="Worked Hours"
|
||||
)
|
||||
|
||||
expected_check_in = fields.Float(
|
||||
string="Expected Check In"
|
||||
)
|
||||
|
||||
late_minutes = fields.Float(
|
||||
string="Late Minutes"
|
||||
)
|
||||
|
||||
department_grace_period = fields.Integer(
|
||||
string="Department Grace Period"
|
||||
)
|
||||
|
||||
required_checkout_time = fields.Float(
|
||||
string="Required Checkout Time"
|
||||
)
|
||||
|
||||
compensation_pending = fields.Boolean(
|
||||
string="Compensation Pending"
|
||||
)
|
||||
|
||||
compensation_minutes = fields.Float(
|
||||
string="Compensation Minutes"
|
||||
)
|
||||
|
||||
reason = fields.Text(
|
||||
string="Reason"
|
||||
)
|
||||
|
||||
manager_comment = fields.Text(
|
||||
string="Manager Comment"
|
||||
)
|
||||
|
||||
status_message = fields.Char(
|
||||
string="Status"
|
||||
)
|
||||
|
||||
state = fields.Selection([
|
||||
|
||||
('draft', 'Draft'),
|
||||
|
||||
('submitted', 'Submitted'),
|
||||
|
||||
('approved', 'Approved'),
|
||||
|
||||
('rejected', 'Rejected')
|
||||
|
||||
],
|
||||
string="Status",
|
||||
default='draft'
|
||||
)
|
||||
|
||||
def action_submit(self):
|
||||
|
||||
for rec in self:
|
||||
|
||||
if not rec.reason:
|
||||
raise UserError(
|
||||
_("Please enter reason.")
|
||||
)
|
||||
|
||||
if not rec.manager_id:
|
||||
raise UserError(
|
||||
_("Manager not configured.")
|
||||
)
|
||||
|
||||
if not rec.manager_id.work_email:
|
||||
raise UserError(
|
||||
_("Manager work email not configured.")
|
||||
)
|
||||
|
||||
rec.state = 'submitted'
|
||||
|
||||
template = self.env.ref(
|
||||
'grace_period.email_template_late_coming_request'
|
||||
)
|
||||
|
||||
template.send_mail(
|
||||
rec.id,
|
||||
force_send=True
|
||||
)
|
||||
rec.message_post(
|
||||
body=_("Request Submitted.")
|
||||
)
|
||||
|
||||
def action_approve(self):
|
||||
|
||||
for rec in self:
|
||||
|
||||
if (
|
||||
rec.manager_id.user_id != self.env.user
|
||||
and not self.env.user.has_group(
|
||||
'hr.group_hr_manager'
|
||||
)
|
||||
):
|
||||
|
||||
raise UserError(
|
||||
_("Only Manager or HR can approve.")
|
||||
)
|
||||
|
||||
rec.state = 'approved'
|
||||
|
||||
def action_reject(self):
|
||||
|
||||
for rec in self:
|
||||
|
||||
if (
|
||||
rec.manager_id.user_id != self.env.user
|
||||
and not self.env.user.has_group(
|
||||
'hr.group_hr_manager'
|
||||
)
|
||||
):
|
||||
|
||||
raise UserError(
|
||||
_("Only Manager or HR can reject.")
|
||||
)
|
||||
|
||||
if not rec.employee_id.work_email:
|
||||
|
||||
raise UserError(
|
||||
_("Employee work email not configured.")
|
||||
)
|
||||
|
||||
rec.state = 'rejected'
|
||||
|
||||
template = self.env.ref(
|
||||
'grace_period.email_template_late_coming_rejected'
|
||||
)
|
||||
|
||||
template.send_mail(
|
||||
rec.id,
|
||||
force_send=True
|
||||
)
|
||||
rec.message_post(
|
||||
body=_("Request Rejected.")
|
||||
)
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
|
||||
self.state = 'draft'
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
from odoo import api, fields, models, tools
|
||||
|
||||
|
||||
class ResourceCalendar(models.Model):
|
||||
_inherit = 'resource.calendar'
|
||||
|
||||
weekday_ids = fields.Many2many(
|
||||
'resource.calendar.weekday',
|
||||
'resource_calendar_weekday_rel',
|
||||
'calendar_id',
|
||||
'weekday_id',
|
||||
string="Weekdays"
|
||||
)
|
||||
|
||||
weekend_ids = fields.Many2many(
|
||||
'resource.calendar.weekday',
|
||||
'resource_calendar_weekend_rel',
|
||||
'calendar_id',
|
||||
'weekday_id',
|
||||
string="Weekends"
|
||||
)
|
||||
|
||||
shift_start_time = fields.Float(string="Start Time")
|
||||
shift_end_time = fields.Float(string="End Time")
|
||||
late_grace_period = fields.Integer(default=15)
|
||||
early_out_grace_period = fields.Integer(default=0)
|
||||
department_grace_ids = fields.One2many(
|
||||
'resource.calendar.department.grace',
|
||||
'calendar_id',
|
||||
string="Department Grace Periods"
|
||||
)
|
||||
|
||||
def action_generate_schedule(self):
|
||||
for rec in self:
|
||||
|
||||
attendance_commands = [(5, 0, 0)]
|
||||
|
||||
added_days = []
|
||||
|
||||
for day in rec.weekday_ids:
|
||||
|
||||
# Avoid duplicate weekdays
|
||||
if day.code in added_days:
|
||||
continue
|
||||
|
||||
added_days.append(day.code)
|
||||
|
||||
attendance_commands.append((0, 0, {
|
||||
'name': day.name,
|
||||
'dayofweek': day.code,
|
||||
'hour_from': rec.shift_start_time,
|
||||
'hour_to': rec.shift_end_time,
|
||||
}))
|
||||
|
||||
# Single write operation
|
||||
rec.write({
|
||||
'attendance_ids': attendance_commands
|
||||
})
|
||||
|
||||
# ------------------------------------
|
||||
# Department Grace Period Generation
|
||||
# ------------------------------------
|
||||
|
||||
department_commands = [(5, 0, 0)]
|
||||
|
||||
departments = self.env['hr.department'].search([])
|
||||
|
||||
for dept in departments:
|
||||
department_commands.append((0, 0, {
|
||||
'department_id': dept.id,
|
||||
'grace_period': 0,
|
||||
}))
|
||||
|
||||
rec.write({
|
||||
'department_grace_ids': department_commands
|
||||
})
|
||||
class ResourceCalendarWeekday(models.Model):
|
||||
_name = 'resource.calendar.weekday'
|
||||
_description = 'Weekday Master'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
|
||||
code = fields.Selection([
|
||||
('0', 'Monday'),
|
||||
('1', 'Tuesday'),
|
||||
('2', 'Wednesday'),
|
||||
('3', 'Thursday'),
|
||||
('4', 'Friday'),
|
||||
('5', 'Saturday'),
|
||||
('6', 'Sunday'),
|
||||
], required=True)
|
||||
|
||||
class ResourceCalendarDepartmentGrace(models.Model):
|
||||
_name = 'resource.calendar.department.grace'
|
||||
_description = 'Department Wise Grace Period'
|
||||
|
||||
calendar_id = fields.Many2one(
|
||||
'resource.calendar',
|
||||
string="Working Schedule",
|
||||
ondelete='cascade'
|
||||
)
|
||||
|
||||
department_id = fields.Many2one(
|
||||
'hr.department',
|
||||
string="Department",
|
||||
required=True
|
||||
)
|
||||
|
||||
grace_period = fields.Integer(
|
||||
string="Grace Period (Minutes)",
|
||||
required=True
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_resource_calendar_weekday, resource_calendar_weekday,model_resource_calendar_weekday,base.group_user,1,1,1,1
|
||||
access_attendance_analytics_user,attendance.analytics.user,model_attendance_analytics,base.group_user,1,0,0,0
|
||||
access_attendance_analytics_manager,attendance.analytics.manager,model_attendance_analytics,hr.group_hr_manager,1,1,1,0
|
||||
access_attendance_analytics_admin,attendance.analytics.admin,model_attendance_analytics,base.group_system,1,1,1,1
|
||||
access_late_coming_request,attendance_late_coming_request,model_late_coming_request,base.group_user,1,1,1,1
|
||||
access_resource_calendar_department_grace,resource_calendar_department_grace,model_resource_calendar_department_grace,base.group_user,1,1,1,1
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- ===================================================== -->
|
||||
<!-- EMPLOYEE: Can see only own attendance -->
|
||||
<!-- ===================================================== -->
|
||||
|
||||
<record id="attendance_analytics_employee_rule"
|
||||
model="ir.rule">
|
||||
|
||||
<field name="name">
|
||||
Attendance Analytics - Employee Own Records
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_attendance_analytics"/>
|
||||
|
||||
<field name="domain_force">
|
||||
[('employee_id.user_id', '=', user.id)]
|
||||
</field>
|
||||
|
||||
<field name="groups"
|
||||
eval="[(4, ref('base.group_user'))]"/>
|
||||
|
||||
</record>
|
||||
|
||||
|
||||
<!-- ===================================================== -->
|
||||
<!-- MANAGER: Can see subordinate employees -->
|
||||
<!-- ===================================================== -->
|
||||
|
||||
<record id="attendance_analytics_manager_rule"
|
||||
model="ir.rule">
|
||||
|
||||
<field name="name">
|
||||
Attendance Analytics - Manager Records
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_attendance_analytics"/>
|
||||
|
||||
<field name="domain_force">
|
||||
|
||||
[
|
||||
'|',
|
||||
|
||||
('employee_id.user_id', '=', user.id),
|
||||
|
||||
('employee_id.parent_id.user_id', '=', user.id)
|
||||
]
|
||||
|
||||
</field>
|
||||
|
||||
<field name="groups"
|
||||
eval="[(4, ref('your_module_name.group_attendance_analytics_manager'))]"/>
|
||||
|
||||
</record>
|
||||
|
||||
|
||||
<!-- ===================================================== -->
|
||||
<!-- ADMIN: Full Access -->
|
||||
<!-- ===================================================== -->
|
||||
|
||||
<record id="attendance_analytics_admin_rule"
|
||||
model="ir.rule">
|
||||
|
||||
<field name="name">
|
||||
Attendance Analytics - Admin Full Access
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_attendance_analytics"/>
|
||||
|
||||
<field name="domain_force">
|
||||
[(1, '=', 1)]
|
||||
</field>
|
||||
|
||||
<field name="groups"
|
||||
eval="[(4, ref('hr.group_hr_manager'))]"/>
|
||||
|
||||
</record>
|
||||
|
||||
|
||||
<!-- ================================================= -->
|
||||
<!-- EMPLOYEE -->
|
||||
<!-- ================================================= -->
|
||||
|
||||
<record id="late_coming_request_employee_rule"
|
||||
model="ir.rule">
|
||||
|
||||
<field name="name">
|
||||
Late Coming Request Employee Rule
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_late_coming_request"/>
|
||||
|
||||
<field name="domain_force">
|
||||
[('employee_id.user_id', '=', user.id)]
|
||||
</field>
|
||||
|
||||
<field name="groups"
|
||||
eval="[(4, ref('base.group_user'))]"/>
|
||||
|
||||
</record>
|
||||
<record id="late_coming_request_manager_rule"
|
||||
model="ir.rule">
|
||||
|
||||
<field name="name">
|
||||
Late Coming Request Manager Rule
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_late_coming_request"/>
|
||||
|
||||
<field name="domain_force">
|
||||
|
||||
[
|
||||
'|',
|
||||
('employee_id.user_id', '=', user.id),
|
||||
('employee_id.parent_id.user_id', '=', user.id)
|
||||
]
|
||||
|
||||
</field>
|
||||
|
||||
<field name="groups"
|
||||
eval="[(4, ref('your_module.group_attendance_analytics_manager'))]"/>
|
||||
|
||||
</record>
|
||||
|
||||
<!-- ================================================= -->
|
||||
<!-- HR MANAGER FULL ACCESS -->
|
||||
<!-- ================================================= -->
|
||||
|
||||
<record id="late_coming_request_hr_rule"
|
||||
model="ir.rule">
|
||||
|
||||
<field name="name">
|
||||
Late Coming Request HR Rule
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_late_coming_request"/>
|
||||
|
||||
<field name="domain_force">
|
||||
[(1, '=', 1)]
|
||||
</field>
|
||||
|
||||
<field name="groups"
|
||||
eval="[(4, ref('hr.group_hr_manager'))]"/>
|
||||
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<odoo>
|
||||
|
||||
<!-- =============================================== -->
|
||||
<!-- TREE VIEW -->
|
||||
<!-- =============================================== -->
|
||||
|
||||
<record id="view_attendance_analytics_list" model="ir.ui.view">
|
||||
<field name="name">attendance.analytics.list</field>
|
||||
<field name="model">attendance.analytics</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Attendance Analytics">
|
||||
<field name="employee_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="date"/>
|
||||
<field name="min_check_in"/>
|
||||
<field name="max_check_out"/>
|
||||
<field name="worked_hours"/>
|
||||
<field name="department_grace_period"/>
|
||||
<field name="late_time"/>
|
||||
<field name="early_out_time"/>
|
||||
<field name="required_checkout_time" widget="float_time"/>
|
||||
<field name="is_late"/>
|
||||
<field name="is_early_out"/>
|
||||
<field name="is_compensation_pending"/>
|
||||
<field name="late_approved"/>
|
||||
<field name="status_message"/>
|
||||
<button name="action_create_late_request"
|
||||
type="object"
|
||||
string="Request Approval"
|
||||
class="btn-primary"
|
||||
invisible="
|
||||
is_holiday == True
|
||||
or is_week_off == True
|
||||
or late_approved == True
|
||||
or (
|
||||
late_minutes == 0
|
||||
and early_out_minutes == 0
|
||||
)
|
||||
"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- =============================================== -->
|
||||
<!-- CALENDAR VIEW -->
|
||||
<!-- =============================================== -->
|
||||
|
||||
<record id="view_attendance_analytics_calendar" model="ir.ui.view">
|
||||
<field name="name">attendance.analytics.calendar</field>
|
||||
<field name="model">attendance.analytics</field>
|
||||
<field name="arch" type="xml">
|
||||
<calendar string="Attendance Calendar"
|
||||
date_start="date"
|
||||
date_stop="date_end"
|
||||
color="color"
|
||||
mode="month"
|
||||
quick_create="false"
|
||||
event_open_popup="true">
|
||||
<field name="display_label"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="status_message"/>
|
||||
</calendar>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- =============================================== -->
|
||||
<!-- SEARCH VIEW -->
|
||||
<!-- =============================================== -->
|
||||
|
||||
<record id="view_attendance_analytics_search" model="ir.ui.view">
|
||||
<field name="name">attendance.analytics.search</field>
|
||||
<field name="model">attendance.analytics</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Attendance Analytics">
|
||||
<field name="employee_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="date"/>
|
||||
<separator/>
|
||||
<filter string="Late"
|
||||
name="late"
|
||||
domain="[('is_late','=',True)]"/>
|
||||
<filter string="Early Out"
|
||||
name="early_out"
|
||||
domain="[('is_early_out','=',True)]"/>
|
||||
<filter string="Compensation Pending"
|
||||
name="compensation_pending"
|
||||
domain="[('is_compensation_pending','=',True)]"/>
|
||||
<filter string="Holiday"
|
||||
name="holiday"
|
||||
domain="[('is_holiday','=',True)]"/>
|
||||
<filter string="Week Off"
|
||||
name="week_off"
|
||||
domain="[('is_week_off','=',True)]"/>
|
||||
<group expand="0"
|
||||
string="Group By">
|
||||
<filter string="Employee"
|
||||
name="group_employee"
|
||||
context="{'group_by':'employee_id'}"/>
|
||||
<filter string="Department"
|
||||
name="group_department"
|
||||
context="{'group_by':'department_id'}"/>
|
||||
<filter string="Date"
|
||||
name="group_date"
|
||||
context="{'group_by':'date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="action_attendance_analytics" model="ir.actions.act_window">
|
||||
<field name="name">Attendance Analytics</field>
|
||||
<field name="res_model">attendance.analytics</field>
|
||||
<field name="view_mode">list,calendar</field>
|
||||
<field name="domain"> [ '|', ('employee_id.user_id', '=', uid), ('employee_id.parent_id.user_id', '=', uid) ] </field>
|
||||
<field name="search_view_id" ref="view_attendance_analytics_search"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_attendance_analytics"
|
||||
name="Attendance Report"
|
||||
parent="hr_attendance.menu_hr_attendance_root"
|
||||
action="action_attendance_analytics"
|
||||
sequence="50"/>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<odoo>
|
||||
|
||||
<record id="view_employee_form_inherit_attendance_analytics"
|
||||
model="ir.ui.view">
|
||||
|
||||
<field name="name">
|
||||
hr.employee.form.attendance.analytics
|
||||
</field>
|
||||
|
||||
<field name="model">hr.employee</field>
|
||||
|
||||
<field name="inherit_id"
|
||||
ref="hr.view_employee_form"/>
|
||||
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//div[@name='button_box']"
|
||||
position="inside">
|
||||
|
||||
<button name="action_open_attendance_analytics"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-calendar">
|
||||
|
||||
<field name="attendance_analytics_count"
|
||||
string="Attendance"
|
||||
widget="statinfo"/>
|
||||
|
||||
</button>
|
||||
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,447 @@
|
|||
<odoo>
|
||||
|
||||
<record id="email_template_late_coming_request"
|
||||
model="mail.template">
|
||||
|
||||
<field name="name">
|
||||
Late Coming Request Submitted
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_late_coming_request"/>
|
||||
|
||||
<field name="subject">
|
||||
Late Coming Request - {{ object.employee_id.name }}
|
||||
</field>
|
||||
|
||||
<field name="email_from">
|
||||
{{ user.email }}
|
||||
</field>
|
||||
|
||||
<field name="email_to">
|
||||
{{ object.manager_id.work_email }}
|
||||
</field>
|
||||
|
||||
<field name="body_html" type="html">
|
||||
|
||||
<div style="font-family: Arial, sans-serif;">
|
||||
|
||||
<p>
|
||||
Hello
|
||||
<strong t-out="object.manager_id.name"/>,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Employee
|
||||
<strong t-out="object.employee_id.name"/>
|
||||
has submitted a Late Coming / Compensation Request.
|
||||
</p>
|
||||
|
||||
<table border="1"
|
||||
cellpadding="6"
|
||||
cellspacing="0"
|
||||
style="border-collapse: collapse;">
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Employee</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.employee_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Department</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.department_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Date</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.attendance_date"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Check In</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.check_in"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Late Minutes</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.late_minutes"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.department_grace_period">
|
||||
<td>
|
||||
<strong>Department Grace</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.department_grace_period"/>
|
||||
Minutes
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.compensation_pending">
|
||||
<td>
|
||||
<strong>Compensation Required</strong>
|
||||
</td>
|
||||
<td>
|
||||
Yes
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.compensation_minutes">
|
||||
<td>
|
||||
<strong>Pending Minutes</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.compensation_minutes"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.required_checkout_time">
|
||||
<td>
|
||||
<strong>Required Checkout Time</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.required_checkout_time"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.status_message">
|
||||
<td>
|
||||
<strong>Status</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.status_message"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>
|
||||
<strong>Reason:</strong>
|
||||
</p>
|
||||
|
||||
<p t-if="object.reason">
|
||||
<span t-out="object.reason"/>
|
||||
</p>
|
||||
|
||||
<p t-if="not object.reason">
|
||||
No reason provided.
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>
|
||||
Please review and take appropriate action.
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>
|
||||
Thank You
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</field>
|
||||
|
||||
</record>
|
||||
<record id="email_template_late_coming_rejected"
|
||||
model="mail.template">
|
||||
|
||||
<field name="name">
|
||||
Late Coming Request Rejected
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_late_coming_request"/>
|
||||
|
||||
<field name="subject">
|
||||
Your Late Coming Request was Rejected
|
||||
</field>
|
||||
|
||||
<field name="email_from">
|
||||
{{ user.email }}
|
||||
</field>
|
||||
|
||||
<field name="email_to">
|
||||
{{ object.employee_id.work_email }}
|
||||
</field>
|
||||
|
||||
<field name="body_html" type="html">
|
||||
|
||||
<div style="font-family: Arial, sans-serif;">
|
||||
|
||||
<p>
|
||||
Hello
|
||||
<strong t-out="object.employee_id.name"/>,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Your Late Coming / Compensation Request has been
|
||||
<strong style="color: red;">
|
||||
Rejected
|
||||
</strong>.
|
||||
</p>
|
||||
|
||||
<table border="1"
|
||||
cellpadding="6"
|
||||
cellspacing="0"
|
||||
style="border-collapse: collapse;">
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Employee</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.employee_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Department</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.department_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Attendance Date</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.attendance_date"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Manager</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.manager_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Late Minutes</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.late_minutes"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.department_grace_period">
|
||||
<td>
|
||||
<strong>Department Grace</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.department_grace_period"/>
|
||||
Minutes
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.compensation_pending">
|
||||
<td>
|
||||
<strong>Compensation Required</strong>
|
||||
</td>
|
||||
<td>
|
||||
Yes
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.compensation_minutes">
|
||||
<td>
|
||||
<strong>Pending Minutes</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.compensation_minutes"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.required_checkout_time">
|
||||
<td>
|
||||
<strong>Required Checkout Time</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.required_checkout_time"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr t-if="object.status_message">
|
||||
<td>
|
||||
<strong>Status</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.status_message"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>
|
||||
<strong>Reason Submitted:</strong>
|
||||
</p>
|
||||
|
||||
<p t-if="object.reason">
|
||||
<span t-out="object.reason"/>
|
||||
</p>
|
||||
|
||||
<p t-if="not object.reason">
|
||||
No reason provided.
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>
|
||||
Please contact your manager or HR for further clarification.
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>
|
||||
Thank You
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</field>
|
||||
|
||||
</record>
|
||||
|
||||
|
||||
<record id="email_template_absent_alert"
|
||||
model="mail.template">
|
||||
|
||||
<field name="name">
|
||||
Attendance Missing Alert
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_attendance_analytics"/>
|
||||
|
||||
<field name="subject">
|
||||
Attendance Punch Missing
|
||||
</field>
|
||||
|
||||
<field name="email_from">
|
||||
{{ user.email }}
|
||||
</field>
|
||||
|
||||
<field name="email_to">
|
||||
{{ object.employee_id.work_email }}
|
||||
</field>
|
||||
|
||||
<field name="body_html" type="html">
|
||||
|
||||
<div style="font-family: Arial, sans-serif;">
|
||||
|
||||
<p>
|
||||
Hello
|
||||
<strong t-out="object.employee_id.name"/>,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Your attendance punch has not been recorded.
|
||||
</p>
|
||||
|
||||
<table border="1"
|
||||
cellpadding="6"
|
||||
cellspacing="0"
|
||||
style="border-collapse: collapse;">
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Employee</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.employee_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Department</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.department_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Date</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span t-out="object.date"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Status</strong>
|
||||
</td>
|
||||
<td>
|
||||
Absent / No Attendance Recorded
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>
|
||||
Please login your attendance immediately
|
||||
or contact HR/Admin if already marked.
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>
|
||||
Thank You
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</field>
|
||||
|
||||
</record>
|
||||
|
||||
|
||||
<record id="ir_cron_send_absent_alert" model="ir.cron">
|
||||
<field name="name">Send Absent Attendance Alert</field>
|
||||
<field name="model_id" ref="model_attendance_analytics"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.action_send_absent_mail()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="active">True</field>
|
||||
<field name="nextcall"
|
||||
eval="DateTime.now().replace(hour=10, minute=0, second=0)"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<odoo>
|
||||
<!-- ============================================= -->
|
||||
<!-- LIST VIEW -->
|
||||
<!-- ============================================= -->
|
||||
<record id="view_late_coming_request_list" model="ir.ui.view">
|
||||
<field name="name">late.coming.request.list</field>
|
||||
<field name="model">late.coming.request</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="employee_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="attendance_date"/>
|
||||
<field name="late_minutes"/>
|
||||
<field name="department_grace_period"/>
|
||||
<field name="compensation_pending"/>
|
||||
<field name="compensation_minutes"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
<!-- ============================================= -->
|
||||
<!-- FORM VIEW -->
|
||||
<!-- ============================================= -->
|
||||
<record id="view_late_coming_request_form" model="ir.ui.view">
|
||||
<field name="name">late.coming.request.form</field>
|
||||
<field name="model">late.coming.request</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<form string="Permission Request">
|
||||
|
||||
<header>
|
||||
<button name="action_reset_to_draft"
|
||||
string="Reset To Draft"
|
||||
type="object"
|
||||
invisible="state == 'draft' or state == 'approved'"
|
||||
class="btn-secondary"/>
|
||||
|
||||
<button name="action_submit"
|
||||
string="Submit"
|
||||
type="object"
|
||||
invisible="state != 'draft'"
|
||||
class="btn-primary"/>
|
||||
|
||||
<button name="action_approve"
|
||||
string="Approve"
|
||||
type="object"
|
||||
invisible="state != 'submitted'"
|
||||
class="btn-success"/>
|
||||
|
||||
<button name="action_reject"
|
||||
string="Reject"
|
||||
type="object"
|
||||
invisible="state != 'submitted'"
|
||||
class="btn-danger"/>
|
||||
|
||||
<field name="state"
|
||||
widget="statusbar"/>
|
||||
|
||||
</header>
|
||||
|
||||
<sheet>
|
||||
|
||||
<group>
|
||||
|
||||
<group>
|
||||
|
||||
<field name="employee_id"
|
||||
readonly="1"/>
|
||||
|
||||
<field name="manager_id"
|
||||
readonly="1"/>
|
||||
|
||||
<field name="attendance_date"
|
||||
readonly="1"/>
|
||||
|
||||
<field name="worked_hours"
|
||||
readonly="1"/>
|
||||
|
||||
</group>
|
||||
|
||||
<group>
|
||||
|
||||
<field name="late_minutes"
|
||||
readonly="1"/>
|
||||
|
||||
<field name="compensation_minutes"
|
||||
readonly="1"/>
|
||||
|
||||
<field name="department_grace_period"
|
||||
readonly="1"/>
|
||||
|
||||
<field name="required_checkout_time"
|
||||
readonly="1"/>
|
||||
|
||||
</group>
|
||||
|
||||
</group>
|
||||
|
||||
<group>
|
||||
|
||||
<field name="status_message"
|
||||
readonly="1"/>
|
||||
|
||||
<field name="reason"
|
||||
placeholder="Enter reason here..."
|
||||
readonly="state in ('approved','rejected')"/>
|
||||
</group>
|
||||
</sheet>
|
||||
|
||||
</form>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_late_coming_request" model="ir.actions.act_window">
|
||||
<field name="name">Permission Requests</field>
|
||||
<field name="res_model">late.coming.request</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="domain">
|
||||
[
|
||||
'|',
|
||||
('employee_id.user_id', '=', uid),
|
||||
('employee_id.parent_id.user_id', '=', uid)
|
||||
]
|
||||
</field>
|
||||
</record>
|
||||
<menuitem id="menu_late_coming_request"
|
||||
name="Permission Requests"
|
||||
parent="hr_attendance.menu_hr_attendance_root"
|
||||
action="action_late_coming_request"
|
||||
sequence="30"/>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_resource_calendar_form_inherit_grace_period"
|
||||
model="ir.ui.view">
|
||||
|
||||
<field name="name">
|
||||
resource.calendar.form.inherit.grace.period
|
||||
</field>
|
||||
|
||||
<field name="model">resource.calendar</field>
|
||||
|
||||
<field name="inherit_id"
|
||||
ref="resource.resource_calendar_form"/>
|
||||
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//sheet/notebook"
|
||||
position="before">
|
||||
|
||||
<group string="Attendance Configuration">
|
||||
|
||||
<group>
|
||||
|
||||
<field name="weekday_ids"
|
||||
widget="many2many_tags"/>
|
||||
|
||||
<field name="weekend_ids"
|
||||
widget="many2many_tags"/>
|
||||
|
||||
</group>
|
||||
|
||||
<group>
|
||||
|
||||
<field name="shift_start_time"
|
||||
widget="float_time"/>
|
||||
|
||||
<field name="shift_end_time"
|
||||
widget="float_time"/>
|
||||
|
||||
</group>
|
||||
|
||||
<group>
|
||||
|
||||
<field name="late_grace_period"/>
|
||||
|
||||
<field name="early_out_grace_period"/>
|
||||
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<button name="action_generate_schedule"
|
||||
type="object"
|
||||
string="Generate Schedule"
|
||||
class="oe_highlight oe_inline"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
</xpath>
|
||||
<xpath expr="//sheet/notebook"
|
||||
position="inside">
|
||||
|
||||
<page string="Department Grace Period">
|
||||
|
||||
<field name="department_grace_ids">
|
||||
|
||||
<list editable="bottom">
|
||||
|
||||
<field name="department_id"/>
|
||||
<field name="grace_period"/>
|
||||
|
||||
</list>
|
||||
|
||||
</field>
|
||||
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
|
||||
</field>
|
||||
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -15,6 +15,9 @@
|
|||
'data': [
|
||||
'data/project_task_data.xml',
|
||||
'views/hr_leave_views.xml',
|
||||
'views/hr_leave_type.xml',
|
||||
'views/hr_leave_allocation.xml',
|
||||
'views/hr_leave_allocation_mail_template.xml',
|
||||
],
|
||||
|
||||
'installable': True,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<field name="user_id" ref="base.user_admin"/>
|
||||
|
||||
<field name="allow_timesheets">True</field>
|
||||
<field name="privacy_visibility">employees</field>
|
||||
</record>
|
||||
|
||||
<!-- Default Leave Task -->
|
||||
|
|
@ -23,4 +24,5 @@
|
|||
eval="[(4, ref('base.user_admin'))]"/>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -1 +1,2 @@
|
|||
from . import hr_leave
|
||||
from . import hr_leave
|
||||
from . import hr_leave_allocation
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
from odoo import models, fields, api
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
class HrLeave(models.Model):
|
||||
_inherit = 'hr.leave'
|
||||
|
||||
project_id = fields.Many2one(
|
||||
'project.project',
|
||||
'project.project',related="holiday_status_id.project_id",
|
||||
string='Project'
|
||||
)
|
||||
|
||||
task_id = fields.Many2one(
|
||||
'project.task',
|
||||
'project.task',related="holiday_status_id.task_id",
|
||||
string='Task'
|
||||
)
|
||||
|
||||
|
|
@ -19,12 +19,12 @@ class HrLeave(models.Model):
|
|||
string='Timesheet Entry',
|
||||
readonly=True,
|
||||
copy=False
|
||||
)
|
||||
)
|
||||
|
||||
# CREATE TIMESHEET ON APPROVAL
|
||||
|
||||
def action_validate(self, check_state=True):
|
||||
res = super().action_validate(check_state=check_state)
|
||||
def action_draft(self):
|
||||
res = super().action_draft()
|
||||
|
||||
default_project = self.env.ref(
|
||||
'leaves_timesheets_extended.project_internal_leave'
|
||||
|
|
@ -40,8 +40,15 @@ class HrLeave(models.Model):
|
|||
if leave.timesheet_line_id:
|
||||
continue
|
||||
|
||||
project = leave.project_id or default_project
|
||||
task = leave.task_id or default_task
|
||||
project = (
|
||||
leave.project_id
|
||||
or default_project
|
||||
)
|
||||
|
||||
task = (
|
||||
leave.task_id
|
||||
or default_task
|
||||
)
|
||||
|
||||
# Hours Calculation
|
||||
if leave.request_unit_half:
|
||||
|
|
@ -49,9 +56,7 @@ class HrLeave(models.Model):
|
|||
else:
|
||||
hours = leave.number_of_days * 8
|
||||
|
||||
analytic_line = self.env[
|
||||
'account.analytic.line'
|
||||
].create({
|
||||
analytic_line = self.env['account.analytic.line'].create({
|
||||
'name': f"Leave: {leave.holiday_status_id.name}",
|
||||
'employee_id': leave.employee_id.id,
|
||||
'user_id': leave.employee_id.user_id.id,
|
||||
|
|
@ -65,7 +70,6 @@ class HrLeave(models.Model):
|
|||
leave.timesheet_line_id = analytic_line.id
|
||||
|
||||
return res
|
||||
# DELETE TIMESHEET ON REFUSE
|
||||
|
||||
def action_refuse(self):
|
||||
res = super().action_refuse()
|
||||
|
|
@ -78,3 +82,42 @@ class HrLeave(models.Model):
|
|||
|
||||
return res
|
||||
|
||||
#ACTION CANCEL
|
||||
def action_cancel(self):
|
||||
res = super().action_cancel()
|
||||
|
||||
for leave in self:
|
||||
|
||||
if leave.timesheet_line_id:
|
||||
leave.timesheet_line_id.unlink()
|
||||
leave.timesheet_line_id = False
|
||||
|
||||
return res
|
||||
|
||||
def _check_validity(self):
|
||||
|
||||
res = super(HrLeave, self)._check_validity()
|
||||
|
||||
for leave in self:
|
||||
|
||||
# Public holiday / weekoff validation
|
||||
if leave.number_of_days <= 0:
|
||||
raise ValidationError(_(
|
||||
"The selected day is either a Public Holiday or Week Off."
|
||||
))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
class HRLeaveType(models.Model):
|
||||
_inherit='hr.leave.type'
|
||||
|
||||
project_id = fields.Many2one(
|
||||
'project.project',
|
||||
string='Project'
|
||||
)
|
||||
|
||||
task_id = fields.Many2one(
|
||||
'project.task',
|
||||
string='Task'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class HrLeaveAllocation(models.Model):
|
||||
_inherit = 'hr.leave.allocation'
|
||||
|
||||
state = fields.Selection(selection_add=[
|
||||
('draft', 'Draft'),
|
||||
('submitted', 'Submitted'),
|
||||
('refuse', 'Rejected'),
|
||||
])
|
||||
|
||||
def action_submit(self):
|
||||
|
||||
for rec in self:
|
||||
|
||||
if rec.state != 'draft':
|
||||
raise UserError(
|
||||
_("Only Draft allocations can be submitted.")
|
||||
)
|
||||
|
||||
manager_email = rec.employee_id.parent_id.work_email
|
||||
|
||||
if not manager_email:
|
||||
raise UserError(
|
||||
_("Manager work email not configured.")
|
||||
)
|
||||
|
||||
rec.state = 'submitted'
|
||||
|
||||
# SEND MAIL TO MANAGER
|
||||
template = self.env.ref(
|
||||
'leaves_timesheets_extended.email_template_leave_allocation_submit'
|
||||
)
|
||||
|
||||
template.send_mail(
|
||||
rec.id,
|
||||
force_send=True
|
||||
)
|
||||
|
||||
rec.message_post(
|
||||
body=_("Leave Allocation Submitted.")
|
||||
)
|
||||
|
||||
def action_approve(self):
|
||||
|
||||
for rec in self:
|
||||
|
||||
if rec.state != 'submitted':
|
||||
raise UserError(
|
||||
_("Only Submitted allocations can be approved.")
|
||||
)
|
||||
|
||||
# Convert back to Odoo valid approval state
|
||||
rec.state = 'confirm'
|
||||
|
||||
# Odoo internal approval
|
||||
rec.action_validate()
|
||||
|
||||
template = self.env.ref(
|
||||
'leaves_timesheets_extended.email_template_leave_allocation_approved'
|
||||
)
|
||||
|
||||
template.send_mail(
|
||||
rec.id,
|
||||
force_send=True
|
||||
)
|
||||
|
||||
rec.message_post(
|
||||
body=_("Leave Allocation Approved.")
|
||||
)
|
||||
def action_reject(self):
|
||||
|
||||
for rec in self:
|
||||
|
||||
if rec.state != 'submitted':
|
||||
raise UserError(
|
||||
_("Only Submitted allocations can be rejected.")
|
||||
)
|
||||
|
||||
if not rec.employee_id.work_email:
|
||||
raise UserError(
|
||||
_("Employee work email not configured.")
|
||||
)
|
||||
|
||||
rec.state = 'refuse'
|
||||
|
||||
# SEND MAIL TO EMPLOYEE
|
||||
template = self.env.ref(
|
||||
'leaves_timesheets_extended.email_template_leave_allocation_rejected'
|
||||
)
|
||||
|
||||
template.send_mail(
|
||||
rec.id,
|
||||
force_send=True
|
||||
)
|
||||
|
||||
rec.message_post(
|
||||
body=_("Leave Allocation Rejected.")
|
||||
)
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
|
||||
for rec in self:
|
||||
rec.state = 'draft'
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="view_hr_leave_allocation_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.leave.allocation.form.inherit</field>
|
||||
<field name="model">hr.leave.allocation</field>
|
||||
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- SUBMIT BUTTON -->
|
||||
<xpath expr="//header" position="inside">
|
||||
<button name="action_submit"
|
||||
string="Submit"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
invisible="state != 'draft'"/>
|
||||
<button name="action_approve"
|
||||
string="Approve"
|
||||
type="object"
|
||||
class="btn-success"
|
||||
invisible="state != 'submitted'"/>
|
||||
<button name="action_reject"
|
||||
string="Reject"
|
||||
type="object"
|
||||
class="btn-danger"
|
||||
invisible="state != 'submitted'"/>
|
||||
<button name="action_reset_to_draft"
|
||||
string="Reset To Draft"
|
||||
type="object"
|
||||
invisible="state == 'draft'"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<odoo>
|
||||
<record id="email_template_leave_allocation_submit" model="mail.template">
|
||||
<field name="name">Leave Allocation Submitted</field>
|
||||
<field name="model_id" ref="hr_holidays.model_hr_leave_allocation"/>
|
||||
<field name="subject">Leave Allocation Submitted - {{ object.employee_id.name }}</field>
|
||||
<field name="email_from">{{ user.email }}</field>
|
||||
<field name="email_to">{{ object.employee_id.parent_id.work_email }}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div>
|
||||
<p>Hello,</p>
|
||||
<p>
|
||||
Employee
|
||||
<strong t-out="object.employee_id.name"/>
|
||||
submitted a Leave Allocation request.
|
||||
</p>
|
||||
<table border="1" cellpadding="5">
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Time Off Type</strong>
|
||||
</td>
|
||||
<td>
|
||||
<t t-out="object.holiday_status_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Days</strong>
|
||||
</td>
|
||||
<td>
|
||||
<t t-out="'%s Days' % object.number_of_days"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<p>
|
||||
Please review the request.
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
<record id="email_template_leave_allocation_approved" model="mail.template">
|
||||
<field name="name">Leave Allocation Approved</field>
|
||||
<field name="model_id" ref="hr_holidays.model_hr_leave_allocation"/>
|
||||
<field name="subject">Leave Allocation Approved</field>
|
||||
<field name="email_from">{{ user.email }}</field>
|
||||
<field name="email_to">{{ object.employee_id.work_email }}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div>
|
||||
<p>Hello
|
||||
<strong t-out="object.employee_id.name"/>,
|
||||
</p>
|
||||
<p>
|
||||
Your Leave Allocation request has been
|
||||
<strong style="color:green;">
|
||||
Approved
|
||||
</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Leave Days:
|
||||
<strong>
|
||||
<t t-out="'%s Days' % object.number_of_days"/>
|
||||
</strong>
|
||||
</p>
|
||||
<br/>
|
||||
<p>Thank You</p>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
<record id="email_template_leave_allocation_rejected" model="mail.template">
|
||||
<field name="name">Leave Allocation Rejected</field>
|
||||
<field name="model_id" ref="hr_holidays.model_hr_leave_allocation"/>
|
||||
<field name="subject">Leave Allocation Rejected</field>
|
||||
<field name="email_from">{{ user.email }}</field>
|
||||
<field name="email_to">{{ object.employee_id.work_email }}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div>
|
||||
<p>Hello
|
||||
<strong t-out="object.employee_id.name"/>,
|
||||
</p>
|
||||
<p>
|
||||
Your Leave Allocation request has been
|
||||
<strong style="color:red;">
|
||||
Rejected
|
||||
</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Please contact HR or your manager.
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<odoo>
|
||||
|
||||
<record id="view_hr_leave_type_form_inherit_project_task"
|
||||
model="ir.ui.view">
|
||||
|
||||
<field name="name">
|
||||
hr.leave.type.form.project.task
|
||||
</field>
|
||||
|
||||
<field name="model">hr.leave.type</field>
|
||||
|
||||
<field name="inherit_id"
|
||||
ref="hr_holidays.edit_holiday_status_form"/>
|
||||
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//sheet/group"
|
||||
position="inside">
|
||||
|
||||
<group string="Project Details">
|
||||
|
||||
<field name="project_id"/>
|
||||
|
||||
<field name="task_id"
|
||||
domain="[('project_id', '=', project_id)]"/>
|
||||
|
||||
</group>
|
||||
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -17,11 +17,10 @@
|
|||
|
||||
<xpath expr="//group" position="inside">
|
||||
|
||||
<field name="project_id"/>
|
||||
<field name="project_id" invisible="1"/>
|
||||
|
||||
<field name="task_id"
|
||||
domain="[('project_id', '=', project_id)]"/>
|
||||
|
||||
domain="[('project_id', '=', project_id)]" invisible="1"/>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ class projectTask(models.Model):
|
|||
project_privacy_visibility = fields.Selection(related="project_id.privacy_visibility")
|
||||
show_submission_button = fields.Boolean(compute="_compute_access_check")
|
||||
show_approval_button = fields.Boolean(compute="_compute_access_check")
|
||||
show_skip_approval_button = fields.Boolean(compute="_compute_access_check")
|
||||
show_refuse_button = fields.Boolean(compute="_compute_access_check")
|
||||
show_back_button = fields.Boolean(compute="_compute_access_check")
|
||||
|
||||
|
|
@ -164,6 +165,7 @@ class projectTask(models.Model):
|
|||
|
||||
|
||||
has_supervisor_access = fields.Boolean(compute="_compute_has_supervisor_access")
|
||||
show_reassign_button = fields.Boolean(compute="_compute_show_reassign_button")
|
||||
actual_hours = fields.Float(
|
||||
string="Actual Hours",
|
||||
compute="_compute_actual_hours",
|
||||
|
|
@ -538,6 +540,20 @@ class projectTask(models.Model):
|
|||
if current_user.has_group("project.group_project_manager") or current_user == task.project_id.user_id or current_user == task.project_id.project_lead or (current_user.id in list(set(create_access_users.ids)) and task.stage_id.id == first_stage.id):
|
||||
task.has_supervisor_access = True
|
||||
|
||||
|
||||
@api.depends("project_id", "project_id.user_id", "project_id.project_lead")
|
||||
def _compute_show_reassign_button(self):
|
||||
current_user = self.env.user
|
||||
is_admin = current_user.has_group("base.group_system") or current_user.has_group("project.group_project_manager")
|
||||
for task in self:
|
||||
task.show_reassign_button = bool(
|
||||
task.project_id
|
||||
and (
|
||||
is_admin
|
||||
or current_user == task.project_id.user_id
|
||||
or current_user == task.project_id.project_lead
|
||||
)
|
||||
)
|
||||
@api.depends('assignees_timelines.estimated_time', 'show_approval_flow')
|
||||
def _compute_estimated_hours(self):
|
||||
for task in self:
|
||||
|
|
@ -681,11 +697,12 @@ class projectTask(models.Model):
|
|||
record.can_edit_approval_flow_stages = False
|
||||
record._add_activity_log(f"Task {action} by {self.env.user.name}")
|
||||
|
||||
@api.depends("assignees_timelines", "stage_id", "project_id", "approval_status")
|
||||
@api.depends("assignees_timelines", "assignees_timelines.assigned_to", "assignees_timelines.responsible_lead", "stage_id", "project_id", "approval_status", "involved_user_ids")
|
||||
def _compute_access_check(self):
|
||||
for task in self:
|
||||
task.show_submission_button = False
|
||||
task.show_approval_button = False
|
||||
task.show_skip_approval_button = False
|
||||
task.show_refuse_button = False
|
||||
task.show_back_button = False
|
||||
if task.project_id:
|
||||
|
|
@ -699,11 +716,20 @@ class projectTask(models.Model):
|
|||
next_stage = task.project_id.type_ids.filtered(lambda s: s.sequence > task.stage_id.sequence).sorted(
|
||||
key=lambda s: s.sequence)[:1]
|
||||
|
||||
approval_not_required = False
|
||||
can_skip_approval_stage = (
|
||||
user in task.involved_user_ids
|
||||
or user in [project_manager, project_lead]
|
||||
or user.has_group("project.group_project_manager")
|
||||
or user.has_group("base.group_system")
|
||||
)
|
||||
|
||||
# Compute buttons visibility
|
||||
if current_timeline:
|
||||
line = current_timeline[0]
|
||||
assigned_to = line.assigned_to
|
||||
responsible_lead = line.responsible_lead
|
||||
approval_not_required = not assigned_to and not responsible_lead
|
||||
|
||||
if (
|
||||
assigned_to
|
||||
|
|
@ -713,16 +739,25 @@ class projectTask(models.Model):
|
|||
):
|
||||
task.show_submission_button = True
|
||||
|
||||
if (
|
||||
approval_not_required
|
||||
and can_skip_approval_stage
|
||||
and task.approval_status != "submitted"
|
||||
):
|
||||
task.show_submission_button = True
|
||||
task.show_skip_approval_button = True
|
||||
|
||||
# a) Submitted + current user is responsible lead / project manager
|
||||
if (
|
||||
task.approval_status == "submitted"
|
||||
and not approval_not_required
|
||||
and (responsible_lead == user or project_manager == user)
|
||||
):
|
||||
task.show_approval_button = True
|
||||
task.show_refuse_button = True # both approve & refuse in review state
|
||||
|
||||
# b) No assigned user: directly approvable
|
||||
elif not assigned_to and (responsible_lead == user or project_manager == user):
|
||||
# b) No assigned user, but a responsible user exists: directly approvable
|
||||
elif not assigned_to and responsible_lead and (responsible_lead == user or project_manager == user):
|
||||
task.show_approval_button = True
|
||||
|
||||
# c) Assigned_to == responsible_lead: no submission needed, direct approve
|
||||
|
|
@ -734,13 +769,16 @@ class projectTask(models.Model):
|
|||
task.show_approval_button = True
|
||||
|
||||
else:
|
||||
# Allow project lead or project manager to approve directly
|
||||
if user in [project_lead, project_manager]:
|
||||
task.show_approval_button = True
|
||||
approval_not_required = True
|
||||
if can_skip_approval_stage and task.approval_status != "submitted":
|
||||
task.show_submission_button = True
|
||||
task.show_skip_approval_button = True
|
||||
|
||||
if user in [project_manager] or user.has_group("project.group_project_manager"):
|
||||
if (user in [project_manager] or user.has_group("project.group_project_manager")) and not approval_not_required:
|
||||
task.show_approval_button = True
|
||||
task.show_back_button = True
|
||||
elif user in [project_manager] or user.has_group("project.group_project_manager"):
|
||||
task.show_back_button = True
|
||||
|
||||
if task.stage_id and task.project_id.type_ids:
|
||||
is_first_stage = task.stage_id.sequence == min(task.project_id.type_ids.mapped('sequence'))
|
||||
|
|
@ -749,6 +787,7 @@ class projectTask(models.Model):
|
|||
is_last_stage = task.stage_id.sequence == max(task.project_id.type_ids.mapped('sequence'))
|
||||
if is_last_stage:
|
||||
task.show_submission_button = False
|
||||
task.show_skip_approval_button = False
|
||||
task.show_approval_button = False
|
||||
task.show_refuse_button = False
|
||||
|
||||
|
|
@ -786,8 +825,42 @@ class projectTask(models.Model):
|
|||
def submit_for_approval(self):
|
||||
for task in self:
|
||||
task.can_edit_approval_flow_stages = True
|
||||
task.approval_status = "submitted"
|
||||
stage = task.assignees_timelines.filtered(lambda s: s.stage_id == task.stage_id)
|
||||
if not stage or (not stage[0].assigned_to and not stage[0].responsible_lead):
|
||||
current_stage = task.stage_id
|
||||
next_stage = task.project_id.type_ids.filtered(lambda s: s.sequence > current_stage.sequence)
|
||||
next_stage = next_stage.sorted(key=lambda s: s.sequence)[:1]
|
||||
if next_stage:
|
||||
task.stage_id = next_stage
|
||||
task.approval_status = False
|
||||
activity_log = "%s: approval skipped by %s and moved to %s" % (
|
||||
current_stage.name,
|
||||
self.env.user.employee_id.name,
|
||||
next_stage.name
|
||||
)
|
||||
task._add_activity_log(activity_log)
|
||||
channel_message = _("Task %s moved from %s to %s. Approval was not required for the empty stage.") % (
|
||||
task.sequence_name or task.name,
|
||||
current_stage.name,
|
||||
next_stage.name
|
||||
)
|
||||
task._post_to_project_channel(channel_message)
|
||||
task.message_post(body=channel_message, message_type="notification", subtype_xmlid="mail.mt_comment")
|
||||
else:
|
||||
task.approval_status = "approved"
|
||||
activity_log = "%s: approval skipped by %s and task completed" % (
|
||||
current_stage.name,
|
||||
self.env.user.employee_id.name
|
||||
)
|
||||
task._add_activity_log(activity_log)
|
||||
channel_message = _("Task %s completed. Approval was not required for the empty stage.") % (
|
||||
task.sequence_name or task.name
|
||||
)
|
||||
task._post_to_project_channel(channel_message)
|
||||
task.message_post(body=channel_message, message_type="notification", subtype_xmlid="mail.mt_comment")
|
||||
task.can_edit_approval_flow_stages = False
|
||||
continue
|
||||
task.approval_status = "submitted"
|
||||
responsible_user = stage.responsible_lead if stage and stage.responsible_lead else False
|
||||
|
||||
activity_log = "%s : %s Submitted to %s for approval" % (
|
||||
|
|
@ -1151,9 +1224,10 @@ class projectTask(models.Model):
|
|||
def _append_reassignment_history(self, stage, users, reason):
|
||||
self.ensure_one()
|
||||
formatted_datetime = self._get_current_datetime_formatted()
|
||||
entry = "[%s] Stage: %s | Users: %s | Reason: %s" % (
|
||||
entry = "[%s] Stage: %s | Assigned By: %s | Assigned To: %s | Reason: %s" % (
|
||||
formatted_datetime,
|
||||
stage.name,
|
||||
self.env.user.name,
|
||||
", ".join(users.mapped('name')),
|
||||
reason or "",
|
||||
)
|
||||
|
|
@ -1164,6 +1238,8 @@ class projectTask(models.Model):
|
|||
|
||||
def action_open_reassign_wizard(self):
|
||||
self.ensure_one()
|
||||
if not self.show_reassign_button:
|
||||
raise UserError(_("Only the project manager, project lead, or administrator can re-assign assignees."))
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"name": _("Re-Assign Assignees"),
|
||||
|
|
@ -1848,3 +1924,7 @@ class projectTaskTimelines(models.Model):
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -40,11 +40,17 @@
|
|||
<record id="project_task_search_project_by_module" model="ir.ui.view">
|
||||
<field name="name">project.task.search.project.by.module</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_search_form"/>
|
||||
<field name="inherit_id" eval="False"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//search" position="inside">
|
||||
<search>
|
||||
<field name="name" string="Task"/>
|
||||
<field name="project_id" string="Project"/>
|
||||
<field name="model_id" string="Module" filter_domain="[('model_id.name', 'ilike', self)]"/>
|
||||
<filter name="group_by_module" string="Module" context="{'group_by': 'model_id'}"/>
|
||||
</xpath>
|
||||
<searchpanel>
|
||||
<field name="model_id" string="Module"/>
|
||||
</searchpanel>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
@ -54,8 +60,8 @@
|
|||
<field name="name">Project by Module</field>
|
||||
<field name="res_model">project.task</field>
|
||||
<field name="view_mode">kanban,list,form,calendar,activity</field>
|
||||
<field name="search_view_id" ref="project.view_task_search_form"/>
|
||||
<field name="context">{'search_default_group_by_module': 1}</field>
|
||||
<field name="search_view_id" ref="project_task_timesheet_extended.project_task_search_project_by_module"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No projects found.
|
||||
|
|
@ -75,3 +81,6 @@
|
|||
</odoo>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
<div class="o_task_assignees_inline">
|
||||
<field name="user_ids" nolabel="1" widget="involved_assignee_avatar_user" domain="[('id', 'in', involved_user_ids)]" options="{'no_create': True, 'no_quick_create': True, 'no_create_edit': True}" invisible="is_generic"/>
|
||||
<field name="user_ids" nolabel="1" class="o_task_user_field" options="{'no_open': True, 'no_quick_create': True}" widget="many2many_avatar_user" domain="[('id', 'in', assignee_domain_ids)]" invisible="not is_generic"/>
|
||||
<button name="action_open_reassign_wizard" type="object" string="Re-Assign" title="Re-Assign Assignees" class="btn btn-secondary btn-sm o_task_reassign_button" invisible="record_paused"/>
|
||||
<button name="action_open_reassign_wizard" type="object" string="Re-Assign" title="Re-Assign Assignees" class="btn btn-secondary btn-sm o_task_reassign_button" invisible="record_paused or not show_reassign_button"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='timesheet_ids']" position="attributes">
|
||||
|
|
@ -104,7 +104,8 @@
|
|||
</xpath>
|
||||
<xpath expr="//header" position="inside">
|
||||
<button type="object" name="request_timelines" string="Request Timelines" class="oe_highlight" invisible="not show_approval_flow or timelines_requested or record_paused"/>
|
||||
<button type="object" name="submit_for_approval" string="Request Approval" class="oe_highlight" invisible="not show_approval_flow or not show_submission_button or not timelines_requested or record_paused"/>
|
||||
<button type="object" name="submit_for_approval" string="Request Approval" class="oe_highlight" invisible="not show_approval_flow or not show_submission_button or show_skip_approval_button or record_paused"/>
|
||||
<button type="object" name="submit_for_approval" string="Next" class="oe_highlight" invisible="not show_approval_flow or not show_skip_approval_button or record_paused"/>
|
||||
<button type="object" name="proceed_further" string="Approve & Proceed" class="oe_highlight" invisible="not show_approval_flow or not show_approval_button or not timelines_requested or record_paused"/>
|
||||
<button type="object" name="action_open_reject_wizard" string="Reject & Return" class="oe_highlight" invisible="not show_approval_flow or not show_refuse_button or not timelines_requested or record_paused"/>
|
||||
<button type="object" name="back_button" string="Go Back" class="oe_highlight" invisible="not show_approval_flow or not show_back_button or not timelines_requested or record_paused"/>
|
||||
|
|
@ -115,9 +116,11 @@
|
|||
<field name="show_approval_flow" invisible="1"/>
|
||||
<field name="show_submission_button" invisible="1"/>
|
||||
<field name="show_approval_button" invisible="1"/>
|
||||
<field name="show_skip_approval_button" invisible="1"/>
|
||||
<field name="show_refuse_button" invisible="1"/>
|
||||
<field name="show_back_button" invisible="1"/>
|
||||
<field name="has_supervisor_access" invisible="1"/>
|
||||
<field name="show_reassign_button" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='stage_id']" position="attributes">
|
||||
<attribute name="readonly">show_approval_flow or state in ['1_canceled','04_waiting_normal'] or record_paused</attribute>
|
||||
|
|
@ -211,7 +214,9 @@
|
|||
<filter name="task_need_approval" string="Need Approval" domain="[('approval_status', '=', 'submitted')]"/>
|
||||
<filter name="task_approved" string="Approved" domain="[('approval_status', '=', 'approved')]"/>
|
||||
<filter name="task_rejected" string="Rejected" domain="[('approval_status', '=', 'refused')]"/>
|
||||
</xpath> </field>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//searchpanel" position="replace"/>-->
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_task_kanban_task_id_module_base" model="ir.ui.view">
|
||||
|
|
@ -304,3 +309,6 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
'views/hr_timesheet_inherit.xml',
|
||||
'views/weekly_period_timesheets.xml',
|
||||
'views/week_timesheet.xml',
|
||||
'views/mail_template.xml',
|
||||
],
|
||||
|
||||
'installable': True,
|
||||
|
|
|
|||
|
|
@ -82,6 +82,15 @@ class WeeklyPeriodTimesheets(models.Model):
|
|||
line.write(vals)
|
||||
|
||||
rec.state = 'submitted'
|
||||
template = self.env.ref(
|
||||
'weekly_timesheets.email_template_weekly_timesheet_submit'
|
||||
)
|
||||
|
||||
if template:
|
||||
template.send_mail(
|
||||
rec.id,
|
||||
force_send=True
|
||||
)
|
||||
|
||||
def action_approve(self):
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
<odoo>
|
||||
|
||||
<record id="email_template_weekly_timesheet_submit"
|
||||
model="mail.template">
|
||||
|
||||
<field name="name">
|
||||
Weekly Timesheet Submitted
|
||||
</field>
|
||||
|
||||
<field name="model_id"
|
||||
ref="model_weekly_periodtimesheets"/>
|
||||
|
||||
<field name="subject">
|
||||
Weekly Timesheet Submitted - ${object.employee_id.name}
|
||||
</field>
|
||||
|
||||
<field name="email_from">
|
||||
${user.email | safe}
|
||||
</field>
|
||||
|
||||
<field name="email_to">
|
||||
${object.employee_id.parent_id.work_email}
|
||||
</field>
|
||||
|
||||
<field name="body_html" type="html">
|
||||
|
||||
<div>
|
||||
<p>Hello,</p>
|
||||
|
||||
<p>
|
||||
Employee
|
||||
<strong>${object.employee_id.name}</strong>
|
||||
has submitted the weekly timesheet.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Week:
|
||||
${object.week_line_id.name}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Total Hours:
|
||||
${object.total_hours}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Please review and approve.
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>Thank You</p>
|
||||
</div>
|
||||
|
||||
</field>
|
||||
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue