PMT bug fix
This commit is contained in:
parent
b5b276f552
commit
ff806505e2
|
|
@ -14,6 +14,7 @@
|
|||
name="Project Dashboard"
|
||||
parent="project.menu_project_management"
|
||||
action="action_project_dashboard_fullscreen"
|
||||
groups="project.group_project_manager"
|
||||
sequence="10"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -54,7 +54,9 @@ Key Features:
|
|||
'view/timesheets.xml',
|
||||
'view/pro_task_gantt.xml',
|
||||
'view/user_availability.xml',
|
||||
'view/project_task_view.xml',
|
||||
# 'view/project_task_gantt.xml',
|
||||
'view/stage_approval_wizard.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend':{
|
||||
|
|
|
|||
|
|
@ -22,3 +22,5 @@ from . import timesheets
|
|||
# from . import project_task_gantt
|
||||
from . import user_availability
|
||||
from . import stage_visibility
|
||||
from . import account_analytic_line
|
||||
from . import stage_approval_wizard
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
from odoo import models, api
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class AccountAnalyticLine(models.Model):
|
||||
_inherit = 'account.analytic.line'
|
||||
|
||||
@api.constrains('unit_amount')
|
||||
def _check_unit_amount(self):
|
||||
print("🔥 TIMESHEET VALIDATION TRIGGERED")
|
||||
for rec in self:
|
||||
if rec.unit_amount < 0:
|
||||
raise ValidationError("Hours cannot be negative")
|
||||
|
|
@ -7,6 +7,22 @@ import pytz
|
|||
class ProjectProject(models.Model):
|
||||
_inherit = 'project.project'
|
||||
|
||||
|
||||
@api.constrains('name')
|
||||
def _check_duplicate_project_name(self):
|
||||
for rec in self:
|
||||
if rec.name:
|
||||
existing = self.search([
|
||||
('name', '=', rec.name),
|
||||
('id', '!=', rec.id)
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
raise ValidationError(
|
||||
"Project name already exists. Please choose a different name."
|
||||
)
|
||||
|
||||
|
||||
sequence_name = fields.Char("Project Number", copy=False, readonly=True)
|
||||
task_sequence_id = fields.Many2one(
|
||||
'ir.sequence',
|
||||
|
|
@ -127,6 +143,12 @@ class ProjectProject(models.Model):
|
|||
estimated_amount = fields.Float(string="Estimated planned Amount")
|
||||
total_planned_budget_amount = fields.Float(string="Total Estimated planned Budget Amount", compute="_compute_total_budget", store=True)
|
||||
|
||||
@api.constrains('estimated_amount')
|
||||
def _check_estimated_amount(self):
|
||||
for rec in self:
|
||||
if rec.estimated_amount < 0:
|
||||
raise ValidationError("Estimated Amount cannot be negative.")
|
||||
|
||||
# Manpower
|
||||
resource_cost_ids = fields.One2many(
|
||||
"project.resource.cost",
|
||||
|
|
@ -950,9 +972,15 @@ class ProjectProject(models.Model):
|
|||
members are users who can have an access to
|
||||
the tasks related to this project."""
|
||||
)
|
||||
user_id = fields.Many2one('res.users', string='Project Manager', default=False, tracking=True,
|
||||
user_id = fields.Many2one('res.users', string='Project Manager', default=False, tracking=True, #required = True,
|
||||
domain=lambda self: [('id','in',self.env.ref('project_task_timesheet_extended.role_project_manager').user_ids.ids),('groups_id', 'in', [self.env.ref('project.group_project_manager').id,self.env.ref('project_task_timesheet_extended.group_project_supervisor').id]),('share','=',False)],)
|
||||
|
||||
@api.constrains('user_id', 'selected_employee_id')
|
||||
def _check_team_lead_before_members(self):
|
||||
for rec in self:
|
||||
if rec.selected_employee_id and not rec.user_id:
|
||||
raise ValidationError("Assign Project Manager before adding members")
|
||||
|
||||
type_ids = fields.Many2many(default=lambda self: self._default_type_ids())
|
||||
|
||||
|
||||
|
|
@ -960,7 +988,11 @@ class ProjectProject(models.Model):
|
|||
task_estimated_hours = fields.Float(string="Task Estimated Hours", compute="_compute_task_estimated_hours", store=True)
|
||||
actual_hours = fields.Float(string="Actual Hours", compute="_compute_actual_hours", store=True)
|
||||
|
||||
|
||||
@api.constrains('estimated_hours')
|
||||
def _check_project_estimated_hours(self):
|
||||
for rec in self:
|
||||
if rec.estimated_hours < 0:
|
||||
raise ValidationError("Project Estimated Hours cannot be negative")
|
||||
|
||||
@api.depends('task_ids.estimated_hours')
|
||||
def _compute_task_estimated_hours(self):
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ from odoo.addons.resource.models.utils import filter_domain_leaf
|
|||
from odoo.osv.expression import is_leaf
|
||||
from odoo.osv import expression
|
||||
from collections import defaultdict
|
||||
import re
|
||||
from datetime import date
|
||||
|
||||
|
||||
|
||||
|
|
@ -25,6 +27,56 @@ class projectTask(models.Model):
|
|||
_inherit = 'project.task'
|
||||
_rec_name = 'name'
|
||||
|
||||
deadline_status = fields.Selection([
|
||||
('overdue', 'Overdue'),
|
||||
('near', 'Near Deadline'),
|
||||
('normal', 'Normal'),
|
||||
], compute='_compute_deadline_status')
|
||||
|
||||
@api.depends('date_deadline')
|
||||
def _compute_deadline_status(self):
|
||||
today = fields.datetime.today()
|
||||
for rec in self:
|
||||
if rec.date_deadline:
|
||||
if rec.date_deadline < today:
|
||||
rec.deadline_status = 'overdue'
|
||||
elif (rec.date_deadline - today).days <= 2:
|
||||
rec.deadline_status = 'near'
|
||||
else:
|
||||
rec.deadline_status = 'normal'
|
||||
else:
|
||||
rec.deadline_status = 'normal'
|
||||
|
||||
def write(self, vals):
|
||||
if 'stage_approval' in vals:
|
||||
raise UserError(_("Are you sure you want to change Stage Approval?"))
|
||||
|
||||
return super().write(vals)
|
||||
|
||||
|
||||
def unlink(self):
|
||||
if not self.env.user.has_group('project.group_project_manager'):
|
||||
raise UserError("Only Project Manager can delete tasks.")
|
||||
return super(projectTask, self).unlink()
|
||||
|
||||
def write(self, vals):
|
||||
# Allow stage update for multiple records
|
||||
if 'stage_id' in vals:
|
||||
return super(ProjectTask, self).write(vals)
|
||||
|
||||
return super(ProjectTask, self).write(vals)
|
||||
|
||||
|
||||
@api.constrains('name')
|
||||
def _check_task_name(self):
|
||||
pattern = r'^[A-Za-z0-9 ]+$' # only letters, numbers, space
|
||||
|
||||
for rec in self:
|
||||
if rec.name and not re.match(pattern, rec.name):
|
||||
raise ValidationError(
|
||||
"Task name can only contain letters, numbers, and spaces."
|
||||
)
|
||||
|
||||
sequence_name = fields.Char("Sequence", copy=False)
|
||||
is_generic = fields.Boolean(string='Generic', default=True, tracking=True,
|
||||
help='All the followers would be able to see this task if the generic is set to true else only the assigned users would have the access to it')
|
||||
|
|
@ -47,6 +99,34 @@ class projectTask(models.Model):
|
|||
store=True,
|
||||
readonly=False
|
||||
)
|
||||
|
||||
def write(self, vals):
|
||||
for rec in self:
|
||||
if 'is_generic' in vals:
|
||||
# Allow only creator or project manager
|
||||
if not (
|
||||
self.env.user == rec.create_uid or
|
||||
self.env.user.has_group('project.group_project_manager')
|
||||
):
|
||||
raise UserError("Only Task Creator or Project Manager can edit Generic field.")
|
||||
|
||||
return super(ProjectTask, self).write(vals)
|
||||
|
||||
@api.constrains('estimated_hours')
|
||||
def _check_estimated_hours(self):
|
||||
for rec in self:
|
||||
if rec.estimated_hours < 0:
|
||||
raise ValidationError("Estimated Hours cannot be negative")
|
||||
|
||||
@api.constrains('date_deadline')
|
||||
def _check_date_deadline(self):
|
||||
print("🔥🔥 DEADLINE CONSTRAINT WORKING 🔥🔥")
|
||||
now = fields.Datetime.now()
|
||||
for rec in self:
|
||||
if rec.date_deadline and rec.date_deadline < now:
|
||||
raise ValidationError("Deadline cannot be set in the past")
|
||||
|
||||
|
||||
has_supervisor_access = fields.Boolean(compute="_compute_has_supervisor_access")
|
||||
actual_hours = fields.Float(
|
||||
string="Actual Hours",
|
||||
|
|
@ -58,7 +138,7 @@ class projectTask(models.Model):
|
|||
suggested_deadline = fields.Datetime(string="Suggested Deadline", compute="_compute_suggested_deadline", store=True)
|
||||
is_suggested_deadline_warning = fields.Boolean(
|
||||
compute="_compute_deadline_warning",
|
||||
string="Deadline Warning"
|
||||
string="Deadline Warning",
|
||||
)
|
||||
allowed_employee_ids = fields.Many2many(
|
||||
'hr.employee',
|
||||
|
|
@ -145,6 +225,15 @@ class projectTask(models.Model):
|
|||
|
||||
task.allowed_employee_ids = employees
|
||||
|
||||
@api.onchange('user_ids')
|
||||
def _onchange_user_ids(self):
|
||||
if self.project_id and self.project_id.user_id:
|
||||
if self.project_id.user_id.id != self.env.user.id:
|
||||
raise ValidationError(
|
||||
"Only Project Manager can assign/remove assignees"
|
||||
)
|
||||
|
||||
|
||||
@api.depends('suggested_deadline', 'date_deadline','timelines_requested','show_approval_flow')
|
||||
def _compute_deadline_warning(self):
|
||||
for rec in self:
|
||||
|
|
@ -1462,3 +1551,4 @@ class projectTaskTimelines(models.Model):
|
|||
allowed_teams |= team
|
||||
allowed_teams |= team.child_ids
|
||||
rec.allowed_team_ids = allowed_teams
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
from odoo import models
|
||||
|
||||
class StageApprovalWizard(models.TransientModel):
|
||||
_name = 'stage.approval.wizard'
|
||||
_description = 'Stage Approval Confirmation'
|
||||
|
||||
def action_confirm(self):
|
||||
active_ids = self.env.context.get('active_ids', [])
|
||||
projects = self.env['project.project'].browse(active_ids)
|
||||
|
||||
# call original function
|
||||
projects.action_assign_approval_flow()
|
||||
|
|
@ -90,3 +90,8 @@ access_project_resource_actual_cost_user,project.resource.actual.cost.user,model
|
|||
access_project_resource_actual_cost_manager,project.resource.actual.cost.manager,model_project_resource_actual_cost,project.group_project_manager,1,1,1,1
|
||||
access_project_resource_contract_period_user,project.resource.contract.period.user,model_project_resource_contract_period,base.group_user,1,0,0,0
|
||||
access_project_resource_contract_period_manager,project.resource.contract.period.manager,model_project_resource_contract_period,project.group_project_manager,1,1,1,1
|
||||
|
||||
|
||||
access_timesheets_user,timesheets user,model_account_analytic_line,base.group_user,1,1,1,0
|
||||
|
||||
access_stage_approval_wizard,stage.approval.wizard,model_stage_approval_wizard,,1,1,1,1
|
||||
|
|
|
|||
|
|
|
@ -0,0 +1,184 @@
|
|||
<odoo>
|
||||
|
||||
<!-- Existing progress code -->
|
||||
<record id="view_task_form_progress_inherit" model="ir.ui.view">
|
||||
<field name="name">project.task.form.progress.inherit</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<group string="Progress">
|
||||
<field name="progress" widget="progressbar"/>
|
||||
</group>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- ✅ ADD THIS NEW BLOCK -->
|
||||
<record id="fix_generic_field_readonly" model="ir.ui.view">
|
||||
<field name="name">project.task.form.fix.generic</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//field[@name='is_generic']" position="attributes">
|
||||
<attribute name="readonly">0</attribute>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hide_deadline_field" model="ir.ui.view">
|
||||
<field name="name">project.task.hide.deadline</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="priority">999</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//field[@name='date_deadline']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hide_deadline_warning_field_final" model="ir.ui.view">
|
||||
<field name="name">hide.deadline.warning.safe</field>
|
||||
<field name="model">project.task</field>
|
||||
|
||||
<!-- IMPORTANT: change inherit_id -->
|
||||
<field name="inherit_id" ref="project_task_timesheet_extended.project_task_form_inherit"/>
|
||||
|
||||
<field name="priority">9999</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//field[@name='is_suggested_deadline_warning']" position="replace">
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="remove_extra_info_fields" model="ir.ui.view">
|
||||
<field name="name">project.task.remove.extra.fields</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="priority">9999</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<!-- Hide Sequence -->
|
||||
<xpath expr="//field[@name='sequence']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
|
||||
<!-- Hide Email CC -->
|
||||
<xpath expr="//field[@name='email_cc']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
|
||||
<!-- Hide Cover Image -->
|
||||
<xpath expr="//field[@name='displayed_image_id']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="update_project_placeholder_all" model="ir.ui.view">
|
||||
<field name="name">project.placeholder.update.all</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.project_project_view_form_simplified"/>
|
||||
<field name="priority">9999</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<!-- Project Name -->
|
||||
<xpath expr="//field[@name='name']" position="attributes">
|
||||
<attribute name="placeholder">e.g. Client Project</attribute>
|
||||
</xpath>
|
||||
|
||||
<!-- Alias Name -->
|
||||
<xpath expr="//field[@name='alias_name']" position="attributes">
|
||||
<attribute name="placeholder">e.g. client-support</attribute>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="project_sort_recent_first" model="ir.ui.view">
|
||||
<field name="name">project.sort.recent.first</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.view_project_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//kanban" position="attributes">
|
||||
<attribute name="default_order">create_date desc</attribute>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- <record id="action_internal_teams" model="ir.actions.act_window">-->
|
||||
<!-- <field name="name">Internal Teams</field>-->
|
||||
<!-- <field name="res_model">internal.teams</field>-->
|
||||
<!-- <field name="view_mode">list,form</field>-->
|
||||
|
||||
<!-- <!– ✅ ADD THIS LINE –>-->
|
||||
<!-- <field name="domain">[('parent_id','=',False)]</field>-->
|
||||
|
||||
<!-- </record>-->
|
||||
|
||||
|
||||
<record id="task_estimated_hours_widget" model="ir.ui.view">
|
||||
<field name="name">project.task.form.estimated.hours.widget</field>
|
||||
<field name="model">project.task</field>
|
||||
|
||||
<!-- ALWAYS SAFE BASE VIEW -->
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
|
||||
<field name="priority">9999</field>
|
||||
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<!-- SAFE XPATH (won’t crash if not found) -->
|
||||
<xpath expr="//field[@name='estimated_hours']" position="attributes">
|
||||
<attribute name="widget">float_time</attribute>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="task_list_color_inherit" model="ir.ui.view">
|
||||
<field name="name">project.task.list.color</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.open_view_all_tasks_list_view"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
|
||||
<xpath expr="//list" position="inside">
|
||||
<field name="deadline_status"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//list" position="attributes">
|
||||
|
||||
|
||||
<attribute name="decoration-danger">
|
||||
date_deadline and date_deadline < context_today()
|
||||
</attribute>
|
||||
|
||||
<attribute name="decoration-warning">
|
||||
date_deadline and date_deadline <= (context_today() + 2)
|
||||
</attribute>
|
||||
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<odoo>
|
||||
|
||||
<!-- ============================= -->
|
||||
<!-- 1. WIZARD FORM (POPUP) -->
|
||||
<!-- ============================= -->
|
||||
<record id="view_stage_approval_wizard_form" model="ir.ui.view">
|
||||
<field name="name">stage.approval.wizard.form</field>
|
||||
<field name="model">stage.approval.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Confirmation">
|
||||
<group>
|
||||
<div class="o_form_label">
|
||||
Are you sure you want to Enable/Disable Stage Approvals?
|
||||
</div>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_confirm" type="object"
|
||||
string="Yes" class="btn-primary"/>
|
||||
<button string="Cancel" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- ============================= -->
|
||||
<!-- 2. NEW SERVER ACTION -->
|
||||
<!-- ============================= -->
|
||||
<record id="action_assign_approval_flow_popup" model="ir.actions.server">
|
||||
<field name="name">Enable/Disable Stage Approvals</field>
|
||||
<field name="model_id" ref="project.model_project_project"/>
|
||||
<field name="binding_model_id" ref="project.model_project_project"/>
|
||||
<field name="binding_type">action</field>
|
||||
<field name="state">code</field>
|
||||
|
||||
<field name="code">
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Confirm',
|
||||
'res_model': 'stage.approval.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {'active_ids': records.ids}
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- ============================= -->
|
||||
<!-- 3. HIDE ORIGINAL ACTION -->
|
||||
<!-- ============================= -->
|
||||
<record id="action_assign_approval_flow" model="ir.actions.server">
|
||||
<field name="binding_model_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue