Implemented Roles Management and Weekly Timesheet enhancements

This commit is contained in:
karuna 2026-07-10 16:34:39 +05:30
parent 92a45a847b
commit 59285440a5
10 changed files with 385 additions and 88 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "Role-Based Group Management", "name": "Role-Based Group Management",
"summary": "Manage reusable security-group roles for users", "summary": "Manage reusable security-group roles for users",
"version": "18.0.1.4.0", "version": "18.0.1.6.0",
"category": "Administration/Access Rights", "category": "Administration/Access Rights",
"license": "LGPL-3", "license": "LGPL-3",
"author": "Custom", "author": "Custom",

View File

@ -1,2 +1,2 @@
from . import res_users
from . import user_role from . import user_role
from . import res_users

View File

@ -7,11 +7,21 @@ class ResUsers(models.Model):
role_id = fields.Many2one( role_id = fields.Many2one(
comodel_name="res.users.role", comodel_name="res.users.role",
string="Role", string="Legacy Role",
groups="base.group_system", groups="base.group_system",
ondelete="set null", ondelete="set null",
copy=False, copy=False,
help="Reusable role whose groups can be synchronized to this user.", help="Legacy single role field kept only to migrate old records.",
)
role_ids = fields.Many2many(
comodel_name="res.users.role",
relation="res_users_role_user_rel",
column1="user_id",
column2="role_id",
string="Assigned Roles",
groups="base.group_system",
copy=False,
help="Reusable roles whose groups can be synchronized to this user.",
) )
role_managed_group_ids = fields.Many2many( role_managed_group_ids = fields.Many2many(
comodel_name="res.groups", comodel_name="res.groups",
@ -28,11 +38,13 @@ class ResUsers(models.Model):
@api.model_create_multi @api.model_create_multi
def create(self, vals_list): def create(self, vals_list):
users = super().create(vals_list) users = super().create(vals_list)
users.filtered("role_id")._sync_groups_from_role() users.filtered(
lambda user: user.role_ids or user.role_id
)._sync_groups_from_role()
return users return users
def write(self, values): def write(self, values):
role_changed = "role_id" in values role_changed = "role_ids" in values or "role_id" in values
result = super().write(values) result = super().write(values)
if role_changed: if role_changed:
self._sync_groups_from_role() self._sync_groups_from_role()
@ -61,14 +73,15 @@ class ResUsers(models.Model):
protected_groups = self._get_protected_role_group_ids() protected_groups = self._get_protected_role_group_ids()
for user in self: for user in self:
role_groups = user.role_id.group_ids roles = user.role_ids | user.role_id
role_groups = roles.mapped("group_ids")
desired_groups = role_groups | role_groups.trans_implied_ids desired_groups = role_groups | role_groups.trans_implied_ids
desired_groups -= protected_groups desired_groups -= protected_groups
current_groups = user.groups_id current_groups = user.groups_id
previously_managed = user.role_managed_group_ids previously_managed = user.role_managed_group_ids
if user.role_id: if roles:
groups_to_remove = current_groups - desired_groups - protected_groups groups_to_remove = current_groups - desired_groups - protected_groups
direct_groups_to_add = role_groups - current_groups - protected_groups direct_groups_to_add = role_groups - current_groups - protected_groups
managed_groups = desired_groups managed_groups = desired_groups
@ -94,8 +107,8 @@ class ResUsers(models.Model):
self.ensure_one() self.ensure_one()
if not self.env.user.has_group("base.group_system"): if not self.env.user.has_group("base.group_system"):
raise AccessError(_("Only Settings administrators can synchronize roles.")) raise AccessError(_("Only Settings administrators can synchronize roles."))
if not self.role_id: if not (self.role_ids or self.role_id):
raise UserError(_("Select a role before fetching groups.")) raise UserError(_("Select at least one role before fetching groups."))
self._sync_groups_from_role() self._sync_groups_from_role()
return { return {
"type": "ir.actions.client", "type": "ir.actions.client",

View File

@ -17,17 +17,12 @@ class ResUsersRole(models.Model):
domain=lambda self: self._get_allowed_group_domain(), domain=lambda self: self._get_allowed_group_domain(),
help="Security groups granted by this role. User-type groups are intentionally excluded.", help="Security groups granted by this role. User-type groups are intentionally excluded.",
) )
user_ids = fields.One2many(
comodel_name="res.users",
inverse_name="role_id",
string="Users",
readonly=True,
)
assigned_user_ids = fields.Many2many( assigned_user_ids = fields.Many2many(
comodel_name="res.users", comodel_name="res.users",
relation="res_users_role_user_rel",
column1="role_id",
column2="user_id",
string="Assigned Users", string="Assigned Users",
compute="_compute_assigned_user_ids",
inverse="_inverse_assigned_user_ids",
domain=[("share", "=", False)], domain=[("share", "=", False)],
help="Select internal users to assign this role. Their groups are synchronized automatically.", help="Select internal users to assign this role. Their groups are synchronized automatically.",
) )
@ -49,33 +44,15 @@ class ResUsersRole(models.Model):
else [] else []
) )
@api.depends("user_ids") @api.depends("assigned_user_ids")
def _compute_user_count(self): def _compute_user_count(self):
counts = self.env["res.users"]._read_group(
[("role_id", "in", self.ids)], ["role_id"], ["__count"]
)
count_by_role = {role.id: count for role, count in counts}
for role in self: for role in self:
role.user_count = count_by_role.get(role.id, 0) role.user_count = self.env["res.users"].with_context(
active_test=False
def _compute_assigned_user_ids(self): ).search_count(
users = self.env["res.users"].with_context(active_test=False).search( ["|", ("role_ids", "in", [role.id]), ("role_id", "=", role.id)]
[("role_id", "in", self.ids)]
)
for role in self:
role.assigned_user_ids = users.filtered(
lambda user: user.role_id == role
) )
def _inverse_assigned_user_ids(self):
"""Apply additions/removals made through the role's user selector."""
Users = self.env["res.users"].with_context(active_test=False)
for role in self.filtered("id"):
current_users = Users.search([("role_id", "=", role.id)])
selected_users = role.assigned_user_ids
(current_users - selected_users).write({"role_id": False})
(selected_users - current_users).write({"role_id": role.id})
@api.constrains("group_ids") @api.constrains("group_ids")
def _check_group_ids_do_not_include_user_types(self): def _check_group_ids_do_not_include_user_types(self):
user_type_category = self.env.ref( user_type_category = self.env.ref(
@ -97,19 +74,24 @@ class ResUsersRole(models.Model):
) )
def write(self, values): def write(self, values):
groups_changed = "group_ids" in values sync_needed = "group_ids" in values or "assigned_user_ids" in values
result = super().write(values) users_before = self.env["res.users"]
if groups_changed: if sync_needed:
users = self.env["res.users"].with_context(active_test=False).search( users_before = self.env["res.users"].with_context(active_test=False).search(
[("role_id", "in", self.ids)] ["|", ("role_ids", "in", self.ids), ("role_id", "in", self.ids)]
) )
users._sync_groups_from_role() result = super().write(values)
if sync_needed:
users_after = self.env["res.users"].with_context(active_test=False).search(
["|", ("role_ids", "in", self.ids), ("role_id", "in", self.ids)]
)
(users_before | users_after)._sync_groups_from_role()
return result return result
def action_update_users(self): def action_update_users(self):
self.ensure_one() self.ensure_one()
users = self.env["res.users"].with_context(active_test=False).search( users = self.env["res.users"].with_context(active_test=False).search(
[("role_id", "=", self.id)] ["|", ("role_ids", "in", [self.id]), ("role_id", "=", self.id)]
) )
users._sync_groups_from_role() users._sync_groups_from_role()
return { return {

View File

@ -10,14 +10,15 @@
appears at the top of Access Rights, just above User Type. --> appears at the top of Access Rights, just above User Type. -->
<xpath expr="//page[@name='access_rights']/field[@name='groups_id']" position="before"> <xpath expr="//page[@name='access_rights']/field[@name='groups_id']" position="before">
<group string="Role Access" groups="base.group_system"> <group string="Role Access" groups="base.group_system">
<field name="role_id" <field name="role_ids"
string="Assigned Role" string="Assigned Roles"
widget="many2many_tags"
options="{'no_create': True, 'no_create_edit': True}"/> options="{'no_create': True, 'no_create_edit': True}"/>
<button name="action_fetch_role_groups" <button name="action_fetch_role_groups"
string="Fetch Groups" string="Fetch Groups"
type="object" type="object"
class="btn-secondary" class="btn-secondary"
invisible="not role_id"/> invisible="not role_ids"/>
</group> </group>
</xpath> </xpath>
</field> </field>

View File

@ -10,10 +10,12 @@
'hr_holidays', 'hr_holidays',
'hr_timesheet', 'hr_timesheet',
'project', 'project',
'project_task_timesheet_extended'
], ],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'security/weekly_timesheet_rules.xml',
'views/hr_timesheet_inherit.xml', 'views/hr_timesheet_inherit.xml',
'views/weekly_period_timesheets.xml', 'views/weekly_period_timesheets.xml',
'views/week_timesheet.xml', 'views/week_timesheet.xml',
@ -22,4 +24,4 @@
'installable': True, 'installable': True,
'application': False, 'application': False,
} }

View File

@ -1,5 +1,5 @@
from odoo import api, fields, models, _ from odoo import api, fields, models, _
from datetime import timedelta from datetime import date, timedelta
from odoo.exceptions import ValidationError, UserError from odoo.exceptions import ValidationError, UserError
@ -12,6 +12,7 @@ class WeeklyPeriodTimesheets(models.Model):
'hr.employee', 'hr.employee',
string="Employee", string="Employee",
required=True, required=True,
default=lambda self: self.env.user.employee_id.id,
) )
year_id = fields.Many2one( year_id = fields.Many2one(
@ -54,6 +55,102 @@ class WeeklyPeriodTimesheets(models.Model):
('rejected', 'Rejected'), ('rejected', 'Rejected'),
], string="Status", default='draft') ], string="Status", default='draft')
can_current_user_refresh_weekly = fields.Boolean(
compute='_compute_can_current_user_refresh_weekly',
)
def _compute_can_current_user_refresh_weekly(self):
for rec in self:
rec.can_current_user_refresh_weekly = (
rec.employee_id.user_id == self.env.user
and rec.state in ('draft', 'rejected')
)
@api.model
def _get_current_week_defaults(self):
today = date.today()
year_name = str(today.year)
year = self.env['week.timesheet'].sudo().search([
('year', '=', year_name),
], limit=1)
if not year:
year = self.env['week.timesheet'].sudo().create({
'year': year_name,
})
year.action_generate_weeks()
elif not year.week_line_ids:
year.action_generate_weeks()
week = self.env['week.timesheet.line'].sudo().search([
('week_timesheet_id', '=', year.id),
('date_from', '<=', today),
('date_to', '>=', today),
], limit=1)
return year, week
@api.model
def default_get(self, fields_list):
vals = super().default_get(fields_list)
if 'employee_id' in fields_list and not vals.get('employee_id'):
vals['employee_id'] = self.env.user.employee_id.id
year, week = self._get_current_week_defaults()
if 'year_id' in fields_list and not vals.get('year_id') and year:
vals['year_id'] = year.id
if 'week_line_id' in fields_list and not vals.get('week_line_id') and week:
vals['week_line_id'] = week.id
return vals
@api.onchange('year_id')
def _onchange_year_id(self):
if not self.year_id:
self.week_line_id = False
return
today = date.today()
week = self.env['week.timesheet.line'].search([
('week_timesheet_id', '=', self.year_id.id),
('date_from', '<=', today),
('date_to', '>=', today),
], limit=1)
if week:
self.week_line_id = week
def _is_weekly_admin(self):
return self.env.user.has_group('base.group_system')
def _is_employee_manager_for(self, employee):
return employee.parent_id.user_id == self.env.user
def _check_employee_can_create(self, vals_list):
if self._is_weekly_admin():
return
current_employee = self.env.user.employee_id
for vals in vals_list:
employee_id = vals.get('employee_id') or current_employee.id
if not current_employee or employee_id != current_employee.id:
raise UserError(_("Employees can create only their own weekly timesheets."))
@api.model_create_multi
def create(self, vals_list):
self._check_employee_can_create(vals_list)
return super().create(vals_list)
def write(self, vals):
if not self._is_weekly_admin() and 'employee_id' in vals:
current_employee = self.env.user.employee_id
if vals['employee_id'] != current_employee.id:
raise UserError(_("Employees can update only their own weekly timesheets."))
return super().write(vals)
def action_submit(self): def action_submit(self):
for rec in self: for rec in self:
@ -96,6 +193,11 @@ class WeeklyPeriodTimesheets(models.Model):
def action_reject(self): def action_reject(self):
for rec in self: for rec in self:
if not rec.can_current_user_approve_weekly:
raise UserError(
_("Only Employee Manager or Administrator can reject.")
)
analytic_lines = self.env[ analytic_lines = self.env[
'account.analytic.line' 'account.analytic.line'
].search([ ].search([
@ -137,17 +239,15 @@ class WeeklyPeriodTimesheets(models.Model):
employee_manager = ( employee_manager = (
rec.employee_id.parent_id.user_id rec.employee_id.parent_id.user_id
) )
# Project Administrator # Administrator
is_admin = self.env.user.has_group( is_admin = self._is_weekly_admin()
'project.group_project_manager'
)
# Validation # Validation
if ( if (
self.env.user != employee_manager self.env.user != employee_manager
and not is_admin and not is_admin
): ):
raise UserError( raise UserError(
_("Only Employee Manager or Project Administrator can approve.") _("Only Employee Manager or Administrator can approve.")
) )
# Ensure all PM approvals completed # Ensure all PM approvals completed
@ -196,13 +296,19 @@ class WeeklyPeriodTimesheets(models.Model):
def action_refresh_timesheets(self): def action_refresh_timesheets(self):
for rec in self: for rec in self:
if not rec.can_current_user_refresh_weekly:
rec.timesheet_line_ids = [(5, 0, 0)] raise UserError(
_("Only the employee can refresh their own draft or rejected weekly timesheet.")
)
if not rec.employee_id or not rec.week_line_id: if not rec.employee_id or not rec.week_line_id:
return continue
lines = [] lines_to_create = []
existing_lines_by_date = {
line.date: line
for line in rec.timesheet_line_ids
}
current_date = rec.week_line_id.date_from current_date = rec.week_line_id.date_from
@ -218,15 +324,25 @@ class WeeklyPeriodTimesheets(models.Model):
analytic_lines.mapped('unit_amount') analytic_lines.mapped('unit_amount')
) )
lines.append((0, 0, { existing_line = existing_lines_by_date.get(current_date)
values = {
'date': current_date, 'date': current_date,
'day_name': current_date.strftime("%A"), 'day_name': current_date.strftime("%A"),
'hours': total_hours, 'hours': total_hours,
})) }
if analytic_lines:
if existing_line:
existing_line.write(values)
else:
lines_to_create.append((0, 0, values))
elif not existing_line:
lines_to_create.append((0, 0, values))
current_date += timedelta(days=1) current_date += timedelta(days=1)
rec.timesheet_line_ids = lines if lines_to_create:
rec.timesheet_line_ids = lines_to_create
@api.onchange('employee_id', 'week_line_id') @api.onchange('employee_id', 'week_line_id')
def _onchange_employee_week(self): def _onchange_employee_week(self):
@ -261,7 +377,6 @@ class WeeklyPeriodTimesheets(models.Model):
current_date += timedelta(days=1) current_date += timedelta(days=1)
self.timesheet_line_ids = lines self.timesheet_line_ids = lines
self.action_refresh_timesheets()
analytic_line_ids = fields.One2many( analytic_line_ids = fields.One2many(
'account.analytic.line', 'account.analytic.line',
@ -290,6 +405,33 @@ class WeeklyPeriodTimesheets(models.Model):
all_pm_approved = fields.Boolean( all_pm_approved = fields.Boolean(
compute='_compute_all_pm_approved' compute='_compute_all_pm_approved'
) )
can_current_user_approve_weekly = fields.Boolean(
compute='_compute_can_current_user_approve_weekly',
search='_search_can_current_user_approve_weekly',
)
def _compute_can_current_user_approve_weekly(self):
is_admin = self._is_weekly_admin()
for rec in self:
rec.can_current_user_approve_weekly = (
is_admin
or rec._is_employee_manager_for(rec.employee_id)
)
def _search_can_current_user_approve_weekly(self, operator, value):
if operator not in ('=', '!='):
return []
match_true = (operator == '=' and value) or (operator == '!=' and not value)
if self._is_weekly_admin():
return [] if match_true else [('id', '=', 0)]
domain = [('employee_id.parent_id.user_id', '=', self.env.uid)]
if match_true:
return domain
return ['!', domain[0]]
def _compute_all_pm_approved(self): def _compute_all_pm_approved(self):
for rec in self: for rec in self:
@ -344,6 +486,17 @@ class AccountAnalyticLine(models.Model):
compute='_compute_pm_approval_required', compute='_compute_pm_approval_required',
store=True store=True
) )
task_stage_id = fields.Many2one(
'project.task.type',
string="Stage",
related='task_id.stage_id',
store=True,
readonly=True,
)
can_current_user_approve_line = fields.Boolean(
compute='_compute_can_current_user_approve_line',
search='_search_can_current_user_approve_line',
)
@api.depends( @api.depends(
'task_id.is_generic', 'task_id.is_generic',
@ -357,6 +510,44 @@ class AccountAnalyticLine(models.Model):
or rec.project_id.privacy_visibility != 'followers' or rec.project_id.privacy_visibility != 'followers'
) )
def _compute_can_current_user_approve_line(self):
is_admin = (
self.env.user.has_group('project.group_project_manager')
or self.env.user.has_group('base.group_system')
)
for rec in self:
rec.can_current_user_approve_line = (
is_admin
or rec.project_id.user_id == self.env.user
or (
'project_lead' in rec.project_id._fields
and rec.project_id.project_lead == self.env.user
)
)
def _search_can_current_user_approve_line(self, operator, value):
if operator not in ('=', '!='):
return []
match_true = (operator == '=' and value) or (operator == '!=' and not value)
if (
self.env.user.has_group('project.group_project_manager')
or self.env.user.has_group('base.group_system')
):
return [] if match_true else [('id', '=', 0)]
if 'project_lead' in self.env['project.project']._fields:
domain = [
'|',
('project_id.user_id', '=', self.env.uid),
('project_id.project_lead', '=', self.env.uid),
]
else:
domain = [('project_id.user_id', '=', self.env.uid)]
if match_true:
return domain
return ['!'] + domain
def action_pm_approve(self): def action_pm_approve(self):
for rec in self: for rec in self:
@ -387,13 +578,19 @@ class AccountAnalyticLine(models.Model):
rec.project_id.user_id == self.env.user rec.project_id.user_id == self.env.user
) )
is_admin = self.env.user.has_group( is_project_lead = (
'project.group_project_manager' 'project_lead' in rec.project_id._fields
and rec.project_id.project_lead == self.env.user
) )
if not (is_project_manager or is_admin): is_admin = (
self.env.user.has_group('project.group_project_manager')
or self.env.user.has_group('base.group_system')
)
if not (is_project_manager or is_project_lead or is_admin):
raise UserError( raise UserError(
_("Only Project Manager or Administrator can approve.") _("Only Project Manager, Project Lead or Administrator can approve.")
) )
rec.approval_state = 'approved' rec.approval_state = 'approved'
@ -424,13 +621,19 @@ class AccountAnalyticLine(models.Model):
rec.project_id.user_id == self.env.user rec.project_id.user_id == self.env.user
) )
is_admin = self.env.user.has_group( is_project_lead = (
'project.group_project_manager' 'project_lead' in rec.project_id._fields
and rec.project_id.project_lead == self.env.user
) )
if not (is_project_manager or is_admin): is_admin = (
self.env.user.has_group('project.group_project_manager')
or self.env.user.has_group('base.group_system')
)
if not (is_project_manager or is_project_lead or is_admin):
raise UserError( raise UserError(
_("Only Project Manager or Administrator can reject.") _("Only Project Manager, Project Lead or Administrator can reject.")
) )
rec.approval_state = 'rejected' rec.approval_state = 'rejected'

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="group_weekly_timesheet_manager" model="res.groups">
<field name="name">Weekly Timesheet Manager</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="rule_weekly_periodtimesheets_employee_manager" model="ir.rule">
<field name="name">Weekly timesheets: own and managed employees</field>
<field name="model_id" ref="model_weekly_periodtimesheets"/>
<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('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="rule_weekly_periodtimesheets_admin" model="ir.rule">
<field name="name">Weekly timesheets: admin all</field>
<field name="model_id" ref="model_weekly_periodtimesheets"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<record id="rule_weekly_periodtimesheets_line_employee_manager" model="ir.rule">
<field name="name">Weekly timesheet lines: own and managed employees</field>
<field name="model_id" ref="model_weekly_periodtimesheets_line"/>
<field name="domain_force">
['|',
('weekly_timesheet_id.employee_id.user_id', '=', user.id),
('weekly_timesheet_id.employee_id.parent_id.user_id', '=', user.id)
]
</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="rule_weekly_periodtimesheets_line_admin" model="ir.rule">
<field name="name">Weekly timesheet lines: admin all</field>
<field name="model_id" ref="model_weekly_periodtimesheets_line"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
</odoo>

View File

@ -71,16 +71,19 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//list" position="inside"> <xpath expr="//list" position="inside">
<field name="approval_state" optional="hide"/> <field name="approval_state" optional="hide"/>
<field name="task_stage_id" string="Stage" optional="show"/>
<field name="weekly_submitted" optional="hide"/>
<field name="can_current_user_approve_line" optional="hide"/>
<button name="action_pm_approve" <button name="action_pm_approve"
string="Approve" string="Approve"
type="object" type="object"
class="btn-success" class="btn-success"
invisible="approval_state == 'approved' or not project_id "/> invisible="approval_state == 'approved' or not project_id or not can_current_user_approve_line"/>
<button name="action_pm_reject" <button name="action_pm_reject"
string="Reject" string="Reject"
type="object" type="object"
class="btn-danger" class="btn-danger"
invisible="approval_state == 'rejected' or not project_id "/> invisible="approval_state == 'rejected' or not project_id or not can_current_user_approve_line"/>
</xpath> </xpath>
</field> </field>
</record> </record>
@ -175,9 +178,9 @@
</field> </field>
</record> </record>
<record id="action_weekly_timesheet_approval" <record id="action_project_timesheet_approval"
model="ir.actions.act_window"> model="ir.actions.act_window">
<field name="name">Weekly Timesheet Approval</field> <field name="name">Project Timesheet Approval</field>
<field name="res_model">account.analytic.line</field> <field name="res_model">account.analytic.line</field>
<field name="view_mode">list</field> <field name="view_mode">list</field>
<field name="view_id" <field name="view_id"
@ -186,7 +189,8 @@
[ [
('weekly_submitted','=',True), ('weekly_submitted','=',True),
('project_id.active','=',True), ('project_id.active','=',True),
('pm_approval_required','=',True) ('pm_approval_required','=',True),
('can_current_user_approve_line','=',True)
] ]
</field> </field>
<field name="context"> <field name="context">
@ -213,7 +217,11 @@
name="Weekly Timesheet Approval" name="Weekly Timesheet Approval"
parent="hr_timesheet.timesheet_menu_root" parent="hr_timesheet.timesheet_menu_root"
action="action_weekly_timesheet_approval" action="action_weekly_timesheet_approval"
groups="weekly_timesheets.group_weekly_timesheet_manager,base.group_system"
sequence="200"/> sequence="200"/>
<record id="menu_project_timesheet_approval" model="ir.ui.menu">
<field name="active" eval="False"/>
</record>
</odoo> </odoo>

View File

@ -17,25 +17,28 @@
string="Reject" string="Reject"
type="object" type="object"
class="btn-primary" class="btn-primary"
invisible="state != 'submitted'"/> invisible="state != 'submitted' or not can_current_user_approve_weekly"/>
<button name="action_approve" <button name="action_approve"
string="Approve" string="Approve"
type="object" type="object"
class="btn-primary" class="btn-primary"
invisible="state != 'submitted' or not all_pm_approved"/> invisible="state != 'submitted' or not all_pm_approved or not can_current_user_approve_weekly"/>
<button name="action_reset_to_draft" <button name="action_reset_to_draft"
string="Reset to Draft" string="Reset to Draft"
type="object" type="object"
class="btn-primary" class="btn-primary"
invisible="state == 'draft'"/> invisible="state != 'rejected'"/>
<button name="action_refresh_timesheets" <button name="action_refresh_timesheets"
string="Refresh" string="Refresh"
type="object" type="object"
class="btn-secondary" class="btn-secondary"
invisible="state in ('submitted','approved')"/> invisible="context.get('weekly_timesheet_approval') or not can_current_user_refresh_weekly"/>
<field name="state" <field name="state"
widget="statusbar"/> widget="statusbar"/>
<field name="all_pm_approved" invisible="1"/>
<field name="can_current_user_approve_weekly" invisible="1"/>
<field name="can_current_user_refresh_weekly" invisible="1"/>
</header> </header>
@ -43,7 +46,9 @@
<group> <group>
<field name="employee_id" <field name="employee_id"
readonly="state in ('submitted','approved')"/> readonly="1"
domain="[('user_id', '=', uid)]"
options="{'no_open': True, 'no_create': True}"/>
<field name="year_id" <field name="year_id"
readonly="state in ('submitted','approved')"/> readonly="state in ('submitted','approved')"/>
@ -77,17 +82,21 @@
<field name="name"/> <field name="name"/>
<field name="project_id"/> <field name="project_id"/>
<field name="task_id"/> <field name="task_id"/>
<field name="task_stage_id" string="Stage"/>
<field name="unit_amount" string="Time Spent"/> <field name="unit_amount" string="Time Spent"/>
<field name="approval_state"/>
<field name="weekly_submitted" column_invisible="1"/>
<field name="can_current_user_approve_line" column_invisible="1"/>
<button name="action_pm_approve" <button name="action_pm_approve"
string="Approve" string="Approve"
type="object" type="object"
class="btn-success" class="btn-success"
invisible="approval_state == 'approved' or not project_id or not weekly_submitted"/> invisible="approval_state == 'approved' or not project_id or not weekly_submitted or not can_current_user_approve_line"/>
<button name="action_pm_reject" <button name="action_pm_reject"
string="Reject" string="Reject"
type="object" type="object"
class="btn-danger" class="btn-danger"
invisible="approval_state == 'rejected' or not project_id or not weekly_submitted"/> invisible="approval_state == 'rejected' or not project_id or not weekly_submitted or not can_current_user_approve_line"/>
</list> </list>
</field> </field>
@ -109,18 +118,36 @@
<field name="employee_id"/> <field name="employee_id"/>
<field name="year_id"/> <field name="year_id"/>
<field name="week_line_id"/> <field name="week_line_id"/>
<field name="total_hours"/>
<field name="state"/>
</list> </list>
</field> </field>
</record> </record>
<record id="action_weekly_period_timesheets" model="ir.actions.act_window"> <record id="action_weekly_period_timesheets" model="ir.actions.act_window">
<field name="res_model">weekly.periodtimesheets</field> <field name="res_model">weekly.periodtimesheets</field>
<field name="view_mode">list,form</field> <field name="view_mode">list,form</field>
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
<field name="context">{'weekly_timesheet_approval': 0}</field>
<field name="view_ids" <field name="view_ids"
eval="[(5, 0, 0), eval="[(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_weekly_period_timesheets_list')}), (0, 0, {'view_mode': 'list', 'view_id': ref('view_weekly_period_timesheets_list')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_weekly_period_timesheets_form')})]"/> (0, 0, {'view_mode': 'form', 'view_id': ref('view_weekly_period_timesheets_form')})]"/>
</record> </record>
<record id="action_weekly_timesheet_approval" model="ir.actions.act_window">
<field name="name">Weekly Timesheet Approval</field>
<field name="res_model">weekly.periodtimesheets</field>
<field name="view_mode">list,form</field>
<field name="domain">[
('state', '=', 'submitted'),
('can_current_user_approve_weekly', '=', True)
]</field>
<field name="context">{'weekly_timesheet_approval': 1}</field>
<field name="view_ids"
eval="[(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_weekly_period_timesheets_list')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_weekly_period_timesheets_form')})]"/>
</record>
</odoo> </odoo>