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:
Bhagya-K 2026-06-08 16:33:01 +05:30
commit 5c6341d8b7
30 changed files with 2937 additions and 34 deletions

View File

@ -0,0 +1 @@
from . import models

View File

@ -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,
}

View File

@ -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>

View File

@ -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

View File

@ -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
}
}

View File

@ -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'

View File

@ -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
)

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_resource_calendar_weekday resource_calendar_weekday model_resource_calendar_weekday base.group_user 1 1 1 1
3 access_attendance_analytics_user attendance.analytics.user model_attendance_analytics base.group_user 1 0 0 0
4 access_attendance_analytics_manager attendance.analytics.manager model_attendance_analytics hr.group_hr_manager 1 1 1 0
5 access_attendance_analytics_admin attendance.analytics.admin model_attendance_analytics base.group_system 1 1 1 1
6 access_late_coming_request attendance_late_coming_request model_late_coming_request base.group_user 1 1 1 1
7 access_resource_calendar_department_grace resource_calendar_department_grace model_resource_calendar_department_grace base.group_user 1 1 1 1

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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,

View File

@ -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>

View File

@ -1 +1,2 @@
from . import hr_leave
from . import hr_leave
from . import hr_leave_allocation

View File

@ -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'
)

View File

@ -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'

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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):

View File

@ -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>

View File

@ -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 &amp; 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 &amp; 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 @@

View File

@ -17,6 +17,7 @@
'views/hr_timesheet_inherit.xml',
'views/weekly_period_timesheets.xml',
'views/week_timesheet.xml',
'views/mail_template.xml',
],
'installable': True,

View File

@ -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):

View File

@ -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>