Implemented Roles Management and Weekly Timesheet enhancements
This commit is contained in:
parent
92a45a847b
commit
59285440a5
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Role-Based Group Management",
|
||||
"summary": "Manage reusable security-group roles for users",
|
||||
"version": "18.0.1.4.0",
|
||||
"version": "18.0.1.6.0",
|
||||
"category": "Administration/Access Rights",
|
||||
"license": "LGPL-3",
|
||||
"author": "Custom",
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
from . import res_users
|
||||
from . import user_role
|
||||
from . import res_users
|
||||
|
|
|
|||
|
|
@ -7,11 +7,21 @@ class ResUsers(models.Model):
|
|||
|
||||
role_id = fields.Many2one(
|
||||
comodel_name="res.users.role",
|
||||
string="Role",
|
||||
string="Legacy Role",
|
||||
groups="base.group_system",
|
||||
ondelete="set null",
|
||||
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(
|
||||
comodel_name="res.groups",
|
||||
|
|
@ -28,11 +38,13 @@ class ResUsers(models.Model):
|
|||
@api.model_create_multi
|
||||
def create(self, 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
|
||||
|
||||
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)
|
||||
if role_changed:
|
||||
self._sync_groups_from_role()
|
||||
|
|
@ -61,14 +73,15 @@ class ResUsers(models.Model):
|
|||
protected_groups = self._get_protected_role_group_ids()
|
||||
|
||||
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 -= protected_groups
|
||||
|
||||
current_groups = user.groups_id
|
||||
previously_managed = user.role_managed_group_ids
|
||||
|
||||
if user.role_id:
|
||||
if roles:
|
||||
groups_to_remove = current_groups - desired_groups - protected_groups
|
||||
direct_groups_to_add = role_groups - current_groups - protected_groups
|
||||
managed_groups = desired_groups
|
||||
|
|
@ -94,8 +107,8 @@ class ResUsers(models.Model):
|
|||
self.ensure_one()
|
||||
if not self.env.user.has_group("base.group_system"):
|
||||
raise AccessError(_("Only Settings administrators can synchronize roles."))
|
||||
if not self.role_id:
|
||||
raise UserError(_("Select a role before fetching groups."))
|
||||
if not (self.role_ids or self.role_id):
|
||||
raise UserError(_("Select at least one role before fetching groups."))
|
||||
self._sync_groups_from_role()
|
||||
return {
|
||||
"type": "ir.actions.client",
|
||||
|
|
|
|||
|
|
@ -17,17 +17,12 @@ class ResUsersRole(models.Model):
|
|||
domain=lambda self: self._get_allowed_group_domain(),
|
||||
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(
|
||||
comodel_name="res.users",
|
||||
relation="res_users_role_user_rel",
|
||||
column1="role_id",
|
||||
column2="user_id",
|
||||
string="Assigned Users",
|
||||
compute="_compute_assigned_user_ids",
|
||||
inverse="_inverse_assigned_user_ids",
|
||||
domain=[("share", "=", False)],
|
||||
help="Select internal users to assign this role. Their groups are synchronized automatically.",
|
||||
)
|
||||
|
|
@ -49,33 +44,15 @@ class ResUsersRole(models.Model):
|
|||
else []
|
||||
)
|
||||
|
||||
@api.depends("user_ids")
|
||||
@api.depends("assigned_user_ids")
|
||||
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:
|
||||
role.user_count = count_by_role.get(role.id, 0)
|
||||
|
||||
def _compute_assigned_user_ids(self):
|
||||
users = self.env["res.users"].with_context(active_test=False).search(
|
||||
[("role_id", "in", self.ids)]
|
||||
)
|
||||
for role in self:
|
||||
role.assigned_user_ids = users.filtered(
|
||||
lambda user: user.role_id == role
|
||||
role.user_count = self.env["res.users"].with_context(
|
||||
active_test=False
|
||||
).search_count(
|
||||
["|", ("role_ids", "in", [role.id]), ("role_id", "=", role.id)]
|
||||
)
|
||||
|
||||
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")
|
||||
def _check_group_ids_do_not_include_user_types(self):
|
||||
user_type_category = self.env.ref(
|
||||
|
|
@ -97,19 +74,24 @@ class ResUsersRole(models.Model):
|
|||
)
|
||||
|
||||
def write(self, values):
|
||||
groups_changed = "group_ids" in values
|
||||
result = super().write(values)
|
||||
if groups_changed:
|
||||
users = self.env["res.users"].with_context(active_test=False).search(
|
||||
[("role_id", "in", self.ids)]
|
||||
sync_needed = "group_ids" in values or "assigned_user_ids" in values
|
||||
users_before = self.env["res.users"]
|
||||
if sync_needed:
|
||||
users_before = self.env["res.users"].with_context(active_test=False).search(
|
||||
["|", ("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
|
||||
|
||||
def action_update_users(self):
|
||||
self.ensure_one()
|
||||
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()
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -10,14 +10,15 @@
|
|||
appears at the top of Access Rights, just above User Type. -->
|
||||
<xpath expr="//page[@name='access_rights']/field[@name='groups_id']" position="before">
|
||||
<group string="Role Access" groups="base.group_system">
|
||||
<field name="role_id"
|
||||
string="Assigned Role"
|
||||
<field name="role_ids"
|
||||
string="Assigned Roles"
|
||||
widget="many2many_tags"
|
||||
options="{'no_create': True, 'no_create_edit': True}"/>
|
||||
<button name="action_fetch_role_groups"
|
||||
string="Fetch Groups"
|
||||
type="object"
|
||||
class="btn-secondary"
|
||||
invisible="not role_id"/>
|
||||
invisible="not role_ids"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@
|
|||
'hr_holidays',
|
||||
'hr_timesheet',
|
||||
'project',
|
||||
'project_task_timesheet_extended'
|
||||
],
|
||||
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'security/weekly_timesheet_rules.xml',
|
||||
'views/hr_timesheet_inherit.xml',
|
||||
'views/weekly_period_timesheets.xml',
|
||||
'views/week_timesheet.xml',
|
||||
|
|
@ -22,4 +24,4 @@
|
|||
|
||||
'installable': True,
|
||||
'application': False,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from odoo import api, fields, models, _
|
||||
from datetime import timedelta
|
||||
from datetime import date, timedelta
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
|
||||
|
|
@ -12,6 +12,7 @@ class WeeklyPeriodTimesheets(models.Model):
|
|||
'hr.employee',
|
||||
string="Employee",
|
||||
required=True,
|
||||
default=lambda self: self.env.user.employee_id.id,
|
||||
)
|
||||
|
||||
year_id = fields.Many2one(
|
||||
|
|
@ -54,6 +55,102 @@ class WeeklyPeriodTimesheets(models.Model):
|
|||
('rejected', 'Rejected'),
|
||||
], 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):
|
||||
|
||||
for rec in self:
|
||||
|
|
@ -96,6 +193,11 @@ class WeeklyPeriodTimesheets(models.Model):
|
|||
def action_reject(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[
|
||||
'account.analytic.line'
|
||||
].search([
|
||||
|
|
@ -137,17 +239,15 @@ class WeeklyPeriodTimesheets(models.Model):
|
|||
employee_manager = (
|
||||
rec.employee_id.parent_id.user_id
|
||||
)
|
||||
# Project Administrator
|
||||
is_admin = self.env.user.has_group(
|
||||
'project.group_project_manager'
|
||||
)
|
||||
# Administrator
|
||||
is_admin = self._is_weekly_admin()
|
||||
# Validation
|
||||
if (
|
||||
self.env.user != employee_manager
|
||||
and not is_admin
|
||||
):
|
||||
raise UserError(
|
||||
_("Only Employee Manager or Project Administrator can approve.")
|
||||
_("Only Employee Manager or Administrator can approve.")
|
||||
)
|
||||
|
||||
# Ensure all PM approvals completed
|
||||
|
|
@ -196,13 +296,19 @@ class WeeklyPeriodTimesheets(models.Model):
|
|||
def action_refresh_timesheets(self):
|
||||
|
||||
for rec in self:
|
||||
|
||||
rec.timesheet_line_ids = [(5, 0, 0)]
|
||||
if not rec.can_current_user_refresh_weekly:
|
||||
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:
|
||||
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
|
||||
|
||||
|
|
@ -218,15 +324,25 @@ class WeeklyPeriodTimesheets(models.Model):
|
|||
analytic_lines.mapped('unit_amount')
|
||||
)
|
||||
|
||||
lines.append((0, 0, {
|
||||
existing_line = existing_lines_by_date.get(current_date)
|
||||
values = {
|
||||
'date': current_date,
|
||||
'day_name': current_date.strftime("%A"),
|
||||
'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)
|
||||
|
||||
rec.timesheet_line_ids = lines
|
||||
if lines_to_create:
|
||||
rec.timesheet_line_ids = lines_to_create
|
||||
|
||||
@api.onchange('employee_id', 'week_line_id')
|
||||
def _onchange_employee_week(self):
|
||||
|
|
@ -261,7 +377,6 @@ class WeeklyPeriodTimesheets(models.Model):
|
|||
current_date += timedelta(days=1)
|
||||
|
||||
self.timesheet_line_ids = lines
|
||||
self.action_refresh_timesheets()
|
||||
|
||||
analytic_line_ids = fields.One2many(
|
||||
'account.analytic.line',
|
||||
|
|
@ -290,6 +405,33 @@ class WeeklyPeriodTimesheets(models.Model):
|
|||
all_pm_approved = fields.Boolean(
|
||||
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):
|
||||
|
||||
for rec in self:
|
||||
|
|
@ -344,6 +486,17 @@ class AccountAnalyticLine(models.Model):
|
|||
compute='_compute_pm_approval_required',
|
||||
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(
|
||||
'task_id.is_generic',
|
||||
|
|
@ -357,6 +510,44 @@ class AccountAnalyticLine(models.Model):
|
|||
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):
|
||||
|
||||
for rec in self:
|
||||
|
|
@ -387,13 +578,19 @@ class AccountAnalyticLine(models.Model):
|
|||
rec.project_id.user_id == self.env.user
|
||||
)
|
||||
|
||||
is_admin = self.env.user.has_group(
|
||||
'project.group_project_manager'
|
||||
is_project_lead = (
|
||||
'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(
|
||||
_("Only Project Manager or Administrator can approve.")
|
||||
_("Only Project Manager, Project Lead or Administrator can approve.")
|
||||
)
|
||||
|
||||
rec.approval_state = 'approved'
|
||||
|
|
@ -424,13 +621,19 @@ class AccountAnalyticLine(models.Model):
|
|||
rec.project_id.user_id == self.env.user
|
||||
)
|
||||
|
||||
is_admin = self.env.user.has_group(
|
||||
'project.group_project_manager'
|
||||
is_project_lead = (
|
||||
'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(
|
||||
_("Only Project Manager or Administrator can reject.")
|
||||
_("Only Project Manager, Project Lead or Administrator can reject.")
|
||||
)
|
||||
|
||||
rec.approval_state = 'rejected'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -71,16 +71,19 @@
|
|||
<field name="arch" type="xml">
|
||||
<xpath expr="//list" position="inside">
|
||||
<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"
|
||||
string="Approve"
|
||||
type="object"
|
||||
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"
|
||||
string="Reject"
|
||||
type="object"
|
||||
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>
|
||||
</field>
|
||||
</record>
|
||||
|
|
@ -175,9 +178,9 @@
|
|||
</field>
|
||||
|
||||
</record>
|
||||
<record id="action_weekly_timesheet_approval"
|
||||
<record id="action_project_timesheet_approval"
|
||||
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="view_mode">list</field>
|
||||
<field name="view_id"
|
||||
|
|
@ -186,7 +189,8 @@
|
|||
[
|
||||
('weekly_submitted','=',True),
|
||||
('project_id.active','=',True),
|
||||
('pm_approval_required','=',True)
|
||||
('pm_approval_required','=',True),
|
||||
('can_current_user_approve_line','=',True)
|
||||
]
|
||||
</field>
|
||||
<field name="context">
|
||||
|
|
@ -213,7 +217,11 @@
|
|||
name="Weekly Timesheet Approval"
|
||||
parent="hr_timesheet.timesheet_menu_root"
|
||||
action="action_weekly_timesheet_approval"
|
||||
groups="weekly_timesheets.group_weekly_timesheet_manager,base.group_system"
|
||||
sequence="200"/>
|
||||
<record id="menu_project_timesheet_approval" model="ir.ui.menu">
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -17,25 +17,28 @@
|
|||
string="Reject"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
invisible="state != 'submitted'"/>
|
||||
invisible="state != 'submitted' or not can_current_user_approve_weekly"/>
|
||||
<button name="action_approve"
|
||||
string="Approve"
|
||||
type="object"
|
||||
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"
|
||||
string="Reset to Draft"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
invisible="state == 'draft'"/>
|
||||
invisible="state != 'rejected'"/>
|
||||
<button name="action_refresh_timesheets"
|
||||
string="Refresh"
|
||||
type="object"
|
||||
class="btn-secondary"
|
||||
invisible="state in ('submitted','approved')"/>
|
||||
invisible="context.get('weekly_timesheet_approval') or not can_current_user_refresh_weekly"/>
|
||||
|
||||
<field name="state"
|
||||
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>
|
||||
|
||||
|
|
@ -43,7 +46,9 @@
|
|||
|
||||
<group>
|
||||
<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"
|
||||
readonly="state in ('submitted','approved')"/>
|
||||
|
|
@ -77,17 +82,21 @@
|
|||
<field name="name"/>
|
||||
<field name="project_id"/>
|
||||
<field name="task_id"/>
|
||||
<field name="task_stage_id" string="Stage"/>
|
||||
<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"
|
||||
string="Approve"
|
||||
type="object"
|
||||
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"
|
||||
string="Reject"
|
||||
type="object"
|
||||
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>
|
||||
</field>
|
||||
|
|
@ -109,18 +118,36 @@
|
|||
<field name="employee_id"/>
|
||||
<field name="year_id"/>
|
||||
<field name="week_line_id"/>
|
||||
<field name="total_hours"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_weekly_period_timesheets" model="ir.actions.act_window">
|
||||
<field name="res_model">weekly.periodtimesheets</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"
|
||||
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>
|
||||
<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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue