Merge pull request 'feature/share_module' (#22) from feature/share_module into srivyn_test
Reviewed-on: https://gitea.ftprotech.in/administrator/odoo18/pulls/22
This commit is contained in:
commit
ebe6146e15
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Base Changes',
|
||||||
|
'category': 'Base',
|
||||||
|
'version': '1.1',
|
||||||
|
'author': 'Seshi Kanth',
|
||||||
|
'summary': 'Changes of the form',
|
||||||
|
'description': 'This module contains form changes.',
|
||||||
|
'depends': [
|
||||||
|
'base',
|
||||||
|
'web',
|
||||||
|
],
|
||||||
|
|
||||||
|
'data': [
|
||||||
|
# 'views/user_menu.xml',
|
||||||
|
],
|
||||||
|
|
||||||
|
'assets': {
|
||||||
|
'web.assets_backend': [
|
||||||
|
'base_custom/static/src/css/backend.css',
|
||||||
|
'base_custom/static/src/js/user_menu_patch.js',
|
||||||
|
'base_custom/static/src/js/hide_user_menu.js',
|
||||||
|
'base_custom/static/src/xml/user_menu.xml',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
from . import ir_http
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
class IrHttp(models.AbstractModel):
|
||||||
|
_inherit = "ir.http"
|
||||||
|
|
||||||
|
def session_info(self):
|
||||||
|
session_info = super().session_info()
|
||||||
|
|
||||||
|
employee = self.env.user.employee_id
|
||||||
|
|
||||||
|
session_info.update({
|
||||||
|
"employee_name": employee.name or self.env.user.name,
|
||||||
|
"employee_designation": employee.job_id.name if employee.job_id else "",
|
||||||
|
})
|
||||||
|
|
||||||
|
return session_info
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
.o_switch_company_menu .oe_topbar_name{
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oe_topbar_name{
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
line-height:1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oe_topbar_name .fw-bold{
|
||||||
|
font-size:13px;
|
||||||
|
font-weight:600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oe_topbar_name .designation{
|
||||||
|
font-size:11px;
|
||||||
|
color:#7b7b7b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #000000 !important;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { registry } from "@web/core/registry";
|
||||||
|
|
||||||
|
const userMenuRegistry = registry.category("user_menuitems");
|
||||||
|
|
||||||
|
const itemsToHide = [
|
||||||
|
"documentation",
|
||||||
|
"support",
|
||||||
|
"shortcuts",
|
||||||
|
"odoo_account",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const item of itemsToHide) {
|
||||||
|
if (userMenuRegistry.contains(item)) {
|
||||||
|
userMenuRegistry.remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
import { UserMenu } from "@web/webclient/user_menu/user_menu";
|
||||||
|
import { session } from "@web/session";
|
||||||
|
|
||||||
|
patch(UserMenu.prototype, {
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
super.setup();
|
||||||
|
|
||||||
|
this.employeeName = session.employee_name;
|
||||||
|
this.employeeDesignation = session.employee_designation;
|
||||||
|
},
|
||||||
|
|
||||||
|
get employeeInfo() {
|
||||||
|
return {
|
||||||
|
employeeName: this.employeeName,
|
||||||
|
employeeDesignation: this.employeeDesignation,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<templates xml:space="preserve">
|
||||||
|
|
||||||
|
<t t-inherit="web.UserMenu"
|
||||||
|
t-inherit-mode="extension">
|
||||||
|
|
||||||
|
<xpath expr="//small[contains(@class,'oe_topbar_name')]"
|
||||||
|
position="replace">
|
||||||
|
|
||||||
|
<small class="oe_topbar_name d-none d-lg-inline-block ms-2 text-start"
|
||||||
|
style="max-width:220px">
|
||||||
|
|
||||||
|
<div class="fw-bold text-truncate">
|
||||||
|
<t t-esc="employeeInfo.employeeName"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-muted text-truncate designation">
|
||||||
|
<t t-esc="employeeInfo.employeeDesignation"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</small>
|
||||||
|
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
</t>
|
||||||
|
|
||||||
|
</templates>
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="hrms-filter-box">
|
<div class="hrms-filter-box">
|
||||||
<label>Period</label>
|
<span>Period</span>
|
||||||
<select class="form-select form-select-sm" t-on-change="onPeriodChange" t-att-value="state.period">
|
<select class="form-select form-select-sm" t-on-change="onPeriodChange" t-att-value="state.period">
|
||||||
<option value="this_month" t-att-selected="state.period === 'this_month'">This Month</option>
|
<option value="this_month" t-att-selected="state.period === 'this_month'">This Month</option>
|
||||||
<option value="this_year" t-att-selected="state.period === 'this_year'">This Year</option>
|
<option value="this_year" t-att-selected="state.period === 'this_year'">This Year</option>
|
||||||
|
|
|
||||||
|
|
@ -1067,13 +1067,15 @@ class projectTask(models.Model):
|
||||||
}
|
}
|
||||||
def _get_stage_responsible_user(self, stage):
|
def _get_stage_responsible_user(self, stage):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
if stage.approval_by == 'assigned_team_lead' and stage.team_id.team_lead:
|
if stage.team_id and stage.team_id.team_lead:
|
||||||
return stage.team_id.team_lead
|
return stage.team_id.team_lead
|
||||||
if stage.approval_by == 'project_manager' and self.project_id.user_id:
|
return self.env['res.users']
|
||||||
return self.project_id.user_id
|
|
||||||
if stage.approval_by == 'project_lead' and self.project_id.project_lead:
|
def _get_stage_assignee_user(self, stage):
|
||||||
return self.project_id.project_lead
|
self.ensure_one()
|
||||||
return self.project_id.project_lead or self.project_id.user_id
|
if stage.involved_user_ids:
|
||||||
|
return stage.involved_user_ids[:1]
|
||||||
|
return self.env['res.users']
|
||||||
|
|
||||||
def _ensure_task_timeline_rows(self):
|
def _ensure_task_timeline_rows(self):
|
||||||
Timeline = self.env['project.task.time.lines'].sudo()
|
Timeline = self.env['project.task.time.lines'].sudo()
|
||||||
|
|
@ -1081,14 +1083,21 @@ class projectTask(models.Model):
|
||||||
stages = task.project_id.type_ids.filtered(lambda s: not s.fold)
|
stages = task.project_id.type_ids.filtered(lambda s: not s.fold)
|
||||||
for stage in stages:
|
for stage in stages:
|
||||||
line = task.assignees_timelines.filtered(lambda t: t.stage_id == stage)[:1]
|
line = task.assignees_timelines.filtered(lambda t: t.stage_id == stage)[:1]
|
||||||
if line:
|
|
||||||
continue
|
|
||||||
responsible_user = task._get_stage_responsible_user(stage)
|
responsible_user = task._get_stage_responsible_user(stage)
|
||||||
|
assignee_user = task._get_stage_assignee_user(stage)
|
||||||
|
if line:
|
||||||
|
if not task.timelines_requested:
|
||||||
|
line.sudo().write({
|
||||||
|
'team_id': stage.team_id.id if stage.team_id else False,
|
||||||
|
'responsible_lead': responsible_user.id if responsible_user else False,
|
||||||
|
'assigned_to': assignee_user.id if assignee_user else False,
|
||||||
|
})
|
||||||
|
continue
|
||||||
Timeline.create({
|
Timeline.create({
|
||||||
'stage_id': stage.id,
|
'stage_id': stage.id,
|
||||||
'team_id': stage.team_id.id if stage.team_id else False,
|
'team_id': stage.team_id.id if stage.team_id else False,
|
||||||
'responsible_lead': responsible_user.id if responsible_user else False,
|
'responsible_lead': responsible_user.id if responsible_user else False,
|
||||||
'assigned_to': responsible_user.id if responsible_user else False,
|
'assigned_to': assignee_user.id if assignee_user else False,
|
||||||
'estimated_time': 0.0,
|
'estimated_time': 0.0,
|
||||||
'task_id': task.id,
|
'task_id': task.id,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ class TaskStages(models.Model):
|
||||||
string='Approval Owner',
|
string='Approval Owner',
|
||||||
)
|
)
|
||||||
involved_user_ids = fields.Many2many('res.users', string='Related Users')
|
involved_user_ids = fields.Many2many('res.users', string='Related Users')
|
||||||
|
responsible_user_id = fields.Many2one(
|
||||||
|
'res.users',
|
||||||
|
string='Responsible User',
|
||||||
|
related='team_id.team_lead',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
team_related_user_ids = fields.Many2many(related='team_id.all_members_ids', string='Team Users')
|
team_related_user_ids = fields.Many2many(related='team_id.all_members_ids', string='Team Users')
|
||||||
is_workflow_template = fields.Boolean(
|
is_workflow_template = fields.Boolean(
|
||||||
string='Workflow Template Stage',
|
string='Workflow Template Stage',
|
||||||
|
|
|
||||||
|
|
@ -38,27 +38,27 @@
|
||||||
<value eval="{'noupdate': True}"/>
|
<value eval="{'noupdate': True}"/>
|
||||||
</function>
|
</function>
|
||||||
|
|
||||||
|
|
||||||
<function name="write" model="ir.model.data">
|
<function name="write" model="ir.model.data">
|
||||||
<function name="search" model="ir.model.data">
|
<function name="search" model="ir.model.data">
|
||||||
<value eval="[('module', '=', 'project'), ('name', '=', 'project_project_manager_rule')]"/>
|
<value eval="[('module', '=', 'project'), ('name', '=', 'project_project_manager_rule')]"/>
|
||||||
</function>
|
</function>
|
||||||
<value eval="{'noupdate': False}"/>
|
<value eval="{'noupdate': False}"/>
|
||||||
</function>
|
</function>
|
||||||
|
|
||||||
<record id="project.project_project_manager_rule" model="ir.rule">
|
<record id="project.project_project_manager_rule" model="ir.rule">
|
||||||
<field name="domain_force">[(1, '=', 1)]</field>
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
<field name="perm_read" eval="1"/>
|
<field name="perm_read" eval="1"/>
|
||||||
<field name="perm_write" eval="1"/>
|
<field name="perm_write" eval="1"/>
|
||||||
<field name="perm_create" eval="1"/>
|
<field name="perm_create" eval="1"/>
|
||||||
<field name="perm_unlink" eval="1"/>
|
<field name="perm_unlink" eval="1"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<function name="write" model="ir.model.data">
|
<function name="write" model="ir.model.data">
|
||||||
<function name="search" model="ir.model.data">
|
<function name="search" model="ir.model.data">
|
||||||
<value eval="[('module', '=', 'project'), ('name', '=', 'project_project_manager_rule')]"/>
|
<value eval="[('module', '=', 'project'), ('name', '=', 'project_project_manager_rule')]"/>
|
||||||
</function>
|
</function>
|
||||||
<value eval="{'noupdate': True}"/>
|
<value eval="{'noupdate': True}"/>
|
||||||
</function>
|
</function>
|
||||||
<record id="portfolio_rule_company_projects" model="ir.rule">
|
<record id="portfolio_rule_company_projects" model="ir.rule">
|
||||||
<field name="name">company: Own Company</field>
|
<field name="name">company: Own Company</field>
|
||||||
|
|
@ -122,27 +122,27 @@
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<function name="write" model="ir.model.data">
|
<function name="write" model="ir.model.data">
|
||||||
<function name="search" model="ir.model.data">
|
<function name="search" model="ir.model.data">
|
||||||
<value eval="[('module', '=', 'project'), ('name', '=', 'project_manager_all_project_tasks_rule')]"/>
|
<value eval="[('module', '=', 'project'), ('name', '=', 'project_manager_all_project_tasks_rule')]"/>
|
||||||
</function>
|
</function>
|
||||||
<value eval="{'noupdate': False}"/>
|
<value eval="{'noupdate': False}"/>
|
||||||
</function>
|
</function>
|
||||||
|
|
||||||
<record id="project.project_manager_all_project_tasks_rule" model="ir.rule">
|
<record id="project.project_manager_all_project_tasks_rule" model="ir.rule">
|
||||||
<field name="domain_force">[(1, '=', 1)]</field>
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
<field name="perm_read" eval="1"/>
|
<field name="perm_read" eval="1"/>
|
||||||
<field name="perm_write" eval="1"/>
|
<field name="perm_write" eval="1"/>
|
||||||
<field name="perm_create" eval="1"/>
|
<field name="perm_create" eval="1"/>
|
||||||
<field name="perm_unlink" eval="1"/>
|
<field name="perm_unlink" eval="1"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<function name="write" model="ir.model.data">
|
<function name="write" model="ir.model.data">
|
||||||
<function name="search" model="ir.model.data">
|
<function name="search" model="ir.model.data">
|
||||||
<value eval="[('module', '=', 'project'), ('name', '=', 'project_manager_all_project_tasks_rule')]"/>
|
<value eval="[('module', '=', 'project'), ('name', '=', 'project_manager_all_project_tasks_rule')]"/>
|
||||||
</function>
|
</function>
|
||||||
<value eval="{'noupdate': True}"/>
|
<value eval="{'noupdate': True}"/>
|
||||||
</function>
|
</function>
|
||||||
<record model="ir.rule" id="project.ir_rule_private_task">
|
<record model="ir.rule" id="project.ir_rule_private_task">
|
||||||
<field name="domain_force">[
|
<field name="domain_force">[
|
||||||
|
|
@ -279,6 +279,20 @@
|
||||||
]
|
]
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record model="ir.rule" id="user_task_follower_view_projets_follower_required">
|
||||||
|
<field name="name">Project: employees: following required for follower-only projects</field>
|
||||||
|
<field name="model_id" ref="project.model_project_project"/>
|
||||||
|
<field name="domain_force">
|
||||||
|
[
|
||||||
|
('privacy_visibility', '=', 'followers'),
|
||||||
|
('members_ids', 'in', [user.id])
|
||||||
|
]
|
||||||
|
</field>
|
||||||
|
<field name="perm_read" eval="1"/>
|
||||||
|
<field name="perm_write" eval="0"/>
|
||||||
|
<field name="perm_create" eval="0"/>
|
||||||
|
<field name="perm_unlink" eval="0"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- <record model="ir.rule" id="timesheet_users_normal_timesheets">-->
|
<!-- <record model="ir.rule" id="timesheet_users_normal_timesheets">-->
|
||||||
<!-- <field name="name">timesheet: users: see own tasks</field>-->
|
<!-- <field name="name">timesheet: users: see own tasks</field>-->
|
||||||
|
|
|
||||||
|
|
@ -117,11 +117,12 @@
|
||||||
<page name="task_stages" string="Task Stages" invisible="not is_project_editor">
|
<page name="task_stages" string="Task Stages" invisible="not is_project_editor">
|
||||||
<field name="type_ids" context="{'default_project_id': id, 'project_stage_project_id': id}">
|
<field name="type_ids" context="{'default_project_id': id, 'project_stage_project_id': id}">
|
||||||
<list editable="bottom" open_form_view="True" delete="0">
|
<list editable="bottom" open_form_view="True" delete="0">
|
||||||
<field name="sequence" widget="handle"/>
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="team_id" string="Assigned Team"/>
|
<field name="team_id" string="Assigned Team"/>
|
||||||
<field name="approval_by" string="Approval Owner"/>
|
<field name="responsible_user_id" string="Responsible User" readonly="1"/>
|
||||||
<field name="fold"/>
|
<field name="approval_by" string="Approval Owner"/>
|
||||||
|
<field name="fold"/>
|
||||||
<field name="team_related_user_ids" invisible="1" column_invisible="1"/>
|
<field name="team_related_user_ids" invisible="1" column_invisible="1"/>
|
||||||
<field name="involved_user_ids" widget="many2many_tags" domain="[('id', 'in', team_related_user_ids)]"/>
|
<field name="involved_user_ids" widget="many2many_tags" domain="[('id', 'in', team_related_user_ids)]"/>
|
||||||
<field name="is_workflow_template" invisible="1" column_invisible="1"/>
|
<field name="is_workflow_template" invisible="1" column_invisible="1"/>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='fold']" position="after">
|
<xpath expr="//field[@name='fold']" position="after">
|
||||||
<field name="team_id"/>
|
<field name="team_id"/>
|
||||||
|
<field name="responsible_user_id"/>
|
||||||
<field name="approval_by"/>
|
<field name="approval_by"/>
|
||||||
<field name="team_related_user_ids" invisible="1"/>
|
<field name="team_related_user_ids" invisible="1"/>
|
||||||
<field name="involved_user_ids" widget="many2many_tags" domain="[('id', 'in', team_related_user_ids)]"/>
|
<field name="involved_user_ids" widget="many2many_tags" domain="[('id', 'in', team_related_user_ids)]"/>
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='name']" position="after">
|
<xpath expr="//field[@name='name']" position="after">
|
||||||
<field name="team_id" optional="show"/>
|
<field name="team_id" optional="show"/>
|
||||||
|
<field name="responsible_user_id" optional="show"/>
|
||||||
<field name="approval_by" optional="show"/>
|
<field name="approval_by" optional="show"/>
|
||||||
<field name="team_related_user_ids" invisible="1"/>
|
<field name="team_related_user_ids" invisible="1"/>
|
||||||
<field name="involved_user_ids" optional="show" domain="[('id', 'in', team_related_user_ids)]"/>
|
<field name="involved_user_ids" optional="show" domain="[('id', 'in', team_related_user_ids)]"/>
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
from . import res_users
|
|
||||||
from . import user_role
|
from . import user_role
|
||||||
|
from . import res_users
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,12 @@
|
||||||
/*}*/
|
/*}*/
|
||||||
|
|
||||||
.srivyn_brand_footer{
|
.srivyn_brand_footer{
|
||||||
position: fixed;
|
position: relative;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
right: 20px;
|
/*right: 20px;*/
|
||||||
|
left: 260px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 18px;
|
font-size: 10px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<!-- Increase login form width -->
|
<!-- Increase login form width -->
|
||||||
<xpath expr="//form" position="attributes">
|
<xpath expr="//form" position="attributes">
|
||||||
<attribute name="style">max-width:250px;margin:auto;padding-top:70px;;</attribute>
|
<attribute name="style">max-width:280px;margin:auto;padding-top:95px;;</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
<!-- <!– Company Logo –>-->
|
<!-- <!– Company Logo –>-->
|
||||||
|
|
@ -16,26 +16,24 @@
|
||||||
<!-- </xpath>-->
|
<!-- </xpath>-->
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<!-- <xpath expr="//select[@name='master_select']" position="after">-->
|
<!-- <xpath expr="//select[@name='master_select']" position="after">-->
|
||||||
<!-- <div class="text-center mt-3">-->
|
<!-- <div class="text-center mt-3">-->
|
||||||
<!-- <small class="text-muted">-->
|
<!-- <small class="text-muted">-->
|
||||||
<!-- Powered by-->
|
<!-- Powered by-->
|
||||||
<!-- <strong>SRIVYN PLATFORMS</strong>-->
|
<!-- <strong>SRIVYN PLATFORMS</strong>-->
|
||||||
<!-- </small>-->
|
<!-- </small>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<!-- </xpath>-->
|
<!-- </xpath>-->
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<odoo>
|
<!-- Powered by -->
|
||||||
<template id="brand_promotion_inherit"
|
|
||||||
inherit_id="web.brand_promotion">
|
|
||||||
|
|
||||||
|
<template id="brand_promotion_inherit" inherit_id="web.brand_promotion">
|
||||||
<xpath expr="//div[@class='o_brand_promotion']" position="replace">
|
<xpath expr="//div[@class='o_brand_promotion']" position="replace">
|
||||||
<div class="o_brand_promotion srivyn_brand_footer">
|
<div class="o_brand_promotion srivyn_brand_footer">
|
||||||
Powered by <strong>SRIVYN PLATFORMS PVT.LTD</strong>
|
Powered by
|
||||||
|
<span>SRIVYN PLATFORMS PVT</span>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</odoo>
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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">
|
<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,12 @@
|
||||||
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"
|
||||||
sequence="200"/>
|
sequence="200"/>
|
||||||
|
<record id="menu_project_timesheet_approval" model="ir.ui.menu">
|
||||||
|
<field name="name">Project Timesheet Approval</field>
|
||||||
|
<field name="active" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue