diff --git a/addons/hr_timesheet/report/project_report.py b/addons/hr_timesheet/report/project_report.py
index 2f5890f834c..be4238b46db 100644
--- a/addons/hr_timesheet/report/project_report.py
+++ b/addons/hr_timesheet/report/project_report.py
@@ -11,20 +11,26 @@ class ReportProjectTaskUser(models.Model):
hours_effective = fields.Float('Effective Hours', readonly=True)
remaining_hours = fields.Float('Remaining Hours', readonly=True)
progress = fields.Float('Progress', group_operator='avg', readonly=True)
+ overtime = fields.Float(readonly=True)
def _select(self):
- return super(ReportProjectTaskUser, self)._select() + """,
- (t.effective_hours * 100) / NULLIF(planned_hours, 0) as progress,
- t.effective_hours as hours_effective,
- t.planned_hours - t.effective_hours - t.subtask_effective_hours as remaining_hours,
- NULLIF(planned_hours, 0) as hours_planned"""
+ select_to_append = """,
+ (t.effective_hours * 100) / NULLIF(t.planned_hours, 0) as progress,
+ t.effective_hours as hours_effective,
+ t.planned_hours - t.effective_hours - t.subtask_effective_hours as remaining_hours,
+ NULLIF(t.planned_hours, 0) as hours_planned,
+ t.overtime as overtime
+ """
+ return super(ReportProjectTaskUser, self)._select() + select_to_append
def _group_by(self):
- return super(ReportProjectTaskUser, self)._group_by() + """,
- remaining_hours,
- t.effective_hours,
- planned_hours
- """
+ group_by_append = """,
+ t.effective_hours,
+ t.subtask_effective_hours,
+ t.planned_hours,
+ t.overtime
+ """
+ return super(ReportProjectTaskUser, self)._group_by() + group_by_append
@api.model
def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
diff --git a/addons/hr_timesheet/report/project_report_view.xml b/addons/hr_timesheet/report/project_report_view.xml
index fe3fdaf6a76..710b5eafd41 100644
--- a/addons/hr_timesheet/report/project_report_view.xml
+++ b/addons/hr_timesheet/report/project_report_view.xml
@@ -36,13 +36,13 @@
-
+
-
+
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index fd26fbea16b..b9f88d54c59 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -333,7 +333,7 @@ class Project(models.Model):
@api.depends('partner_id.email')
def _compute_partner_email(self):
for project in self:
- if project.partner_id and project.partner_id.email != project.partner_email:
+ if project.partner_id.email != project.partner_email:
project.partner_email = project.partner_id.email
def _inverse_partner_email(self):
@@ -344,7 +344,7 @@ class Project(models.Model):
@api.depends('partner_id.phone')
def _compute_partner_phone(self):
for project in self:
- if project.partner_id and project.partner_phone != project.partner_id.phone:
+ if project.partner_phone != project.partner_id.phone:
project.partner_phone = project.partner_id.phone
def _inverse_partner_phone(self):
@@ -1325,7 +1325,7 @@ class Task(models.Model):
@api.depends('partner_id.email')
def _compute_partner_email(self):
for task in self:
- if task.partner_id and task.partner_id.email != task.partner_email:
+ if task.partner_id.email != task.partner_email:
task.partner_email = task.partner_id.email
def _inverse_partner_email(self):
@@ -1336,7 +1336,7 @@ class Task(models.Model):
@api.depends('partner_id.phone')
def _compute_partner_phone(self):
for task in self:
- if task.partner_id and task.partner_phone != task.partner_id.phone:
+ if task.partner_phone != task.partner_id.phone:
task.partner_phone = task.partner_id.phone
def _inverse_partner_phone(self):
diff --git a/addons/project/report/project_report.py b/addons/project/report/project_report.py
index 1c7c3aec975..a40c6dfee97 100644
--- a/addons/project/report/project_report.py
+++ b/addons/project/report/project_report.py
@@ -3,6 +3,7 @@
from odoo import fields, models, tools
+from odoo.addons.rating.models.rating import RATING_LIMIT_MIN
class ReportProjectTaskUser(models.Model):
_name = "report.project.task.user"
@@ -30,6 +31,7 @@ class ReportProjectTaskUser(models.Model):
working_hours_open = fields.Float(string='Working Hours to Assign', digits=(16, 2), readonly=True, group_operator="avg", help="Number of Working Hours to open the task")
working_hours_close = fields.Float(string='Working Hours to Close', digits=(16, 2), readonly=True, group_operator="avg", help="Number of Working Hours to close the task")
rating_last_value = fields.Float('Rating Value (/5)', group_operator="avg", readonly=True, groups="project.group_project_rating")
+ rating_avg = fields.Float('Average Rating', readonly=True)
priority = fields.Selection([
('0', 'Low'),
('1', 'Normal'),
@@ -44,61 +46,85 @@ class ReportProjectTaskUser(models.Model):
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True)
stage_id = fields.Many2one('project.task.type', string='Stage', readonly=True)
task_id = fields.Many2one('project.task', string='Tasks', readonly=True)
+ active = fields.Boolean(readonly=True)
+ tag_ids = fields.Many2many('project.tags', relation='project_tags_project_task_rel',
+ column1='project_task_id', column2='project_tags_id',
+ string='Tags', readonly=True)
+ parent_id = fields.Many2one('project.task', string='Parent Task', readonly=True)
def _select(self):
- select_str = """
- SELECT
- (select 1 ) AS nbr,
- t.id as id,
- t.id as task_id,
- t.create_date as create_date,
- t.date_assign as date_assign,
- t.date_end as date_end,
- t.date_last_stage_update as date_last_stage_update,
- t.date_deadline as date_deadline,
- t.project_id,
- t.priority,
- t.name as name,
- t.company_id,
- t.partner_id,
- t.stage_id as stage_id,
- t.kanban_state as state,
- NULLIF(t.rating_last_value, 0) as rating_last_value,
- t.working_days_close as working_days_close,
- t.working_days_open as working_days_open,
- t.working_hours_open as working_hours_open,
- t.working_hours_close as working_hours_close,
- (extract('epoch' from (t.date_deadline-(now() at time zone 'UTC'))))/(3600*24) as delay_endings_days
+ return """
+ (select 1) AS nbr,
+ t.id as id,
+ t.id as task_id,
+ t.active,
+ t.create_date as create_date,
+ t.date_assign as date_assign,
+ t.date_end as date_end,
+ t.date_last_stage_update as date_last_stage_update,
+ t.date_deadline as date_deadline,
+ t.project_id,
+ t.priority,
+ t.name as name,
+ t.company_id,
+ t.partner_id,
+ t.parent_id as parent_id,
+ t.stage_id as stage_id,
+ t.kanban_state as state,
+ NULLIF(t.rating_last_value, 0) as rating_last_value,
+ AVG(rt.rating) as rating_avg,
+ t.working_days_close as working_days_close,
+ t.working_days_open as working_days_open,
+ t.working_hours_open as working_hours_open,
+ t.working_hours_close as working_hours_close,
+ (extract('epoch' from (t.date_deadline-(now() at time zone 'UTC'))))/(3600*24) as delay_endings_days
"""
- return select_str
def _group_by(self):
- group_by_str = """
- GROUP BY
- t.id,
- t.create_date,
- t.write_date,
- t.date_assign,
- t.date_end,
- t.date_deadline,
- t.date_last_stage_update,
- t.project_id,
- t.priority,
- t.name,
- t.company_id,
- t.partner_id,
- t.stage_id
+ return """
+ t.id,
+ t.active,
+ t.create_date,
+ t.date_assign,
+ t.date_end,
+ t.date_last_stage_update,
+ t.date_deadline,
+ t.project_id,
+ t.priority,
+ t.name,
+ t.company_id,
+ t.partner_id,
+ t.parent_id,
+ t.stage_id,
+ t.kanban_state,
+ t.rating_last_value,
+ t.working_days_close,
+ t.working_days_open,
+ t.working_hours_open,
+ t.working_hours_close
+ """
+
+ def _from(self):
+ return f"""
+ project_task t
+ LEFT JOIN project_task_user_rel tu on t.id=tu.task_id
+ LEFT JOIN rating_rating rt ON rt.parent_res_id = t.id
+ AND rt.res_model = 'project.task'
+ AND rt.consumed = True
+ AND rt.rating >= {RATING_LIMIT_MIN}
+ """
+
+ def _where(self):
+ return """
+ t.project_id IS NOT NULL
"""
- return group_by_str
def init(self):
tools.drop_view_if_exists(self._cr, self._table)
self._cr.execute("""
- CREATE view %s as
- %s
- FROM project_task t
- LEFT JOIN project_task_user_rel tu on t.id=tu.task_id
- WHERE t.active = 'true'
- AND t.project_id IS NOT NULL
- %s
- """ % (self._table, self._select(), self._group_by()))
+ CREATE view %s as
+ SELECT %s
+ FROM %s
+ WHERE %s
+ GROUP BY %s
+ """ % (self._table, self._select(), self._from(), self._where(), self._group_by()))
diff --git a/addons/project/report/project_report_views.xml b/addons/project/report/project_report_views.xml
index d968e6086a3..19f8c10827c 100644
--- a/addons/project/report/project_report_views.xml
+++ b/addons/project/report/project_report_views.xml
@@ -45,21 +45,23 @@
-
+
-
+
+
+
-
+
-
+
-
+
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index 8dc9e751a2d..2b2b6c4d78b 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -500,11 +500,11 @@
+
-
@@ -914,7 +914,7 @@
@@ -1173,7 +1173,7 @@
-
+
diff --git a/addons/sale/models/sale_order.py b/addons/sale/models/sale_order.py
index c8ca70703c8..917287510a4 100644
--- a/addons/sale/models/sale_order.py
+++ b/addons/sale/models/sale_order.py
@@ -22,6 +22,13 @@ LOCKED_FIELD_STATES = {
for state in {'done', 'cancel'}
}
+INVOICE_STATUS = [
+ ('upselling', 'Upselling Opportunity'),
+ ('invoiced', 'Fully Invoiced'),
+ ('to invoice', 'To Invoice'),
+ ('no', 'Nothing to Invoice')
+]
+
class SaleOrder(models.Model):
_name = "sale.order"
@@ -218,12 +225,7 @@ class SaleOrder(models.Model):
invoice_count = fields.Integer(string='Invoice Count', compute='_get_invoiced')
invoice_ids = fields.Many2many("account.move", string='Invoices', compute="_get_invoiced", copy=False, search="_search_invoice_ids")
- invoice_status = fields.Selection([
- ('upselling', 'Upselling Opportunity'),
- ('invoiced', 'Fully Invoiced'),
- ('to invoice', 'To Invoice'),
- ('no', 'Nothing to Invoice')
- ], string='Invoice Status', compute='_get_invoice_status', store=True)
+ invoice_status = fields.Selection(INVOICE_STATUS, string='Invoice Status', compute='_get_invoice_status', store=True)
note = fields.Html(
'Terms and conditions',
diff --git a/addons/sale_project/models/project.py b/addons/sale_project/models/project.py
index aaf71760940..7bbd1ca69d8 100644
--- a/addons/sale_project/models/project.py
+++ b/addons/sale_project/models/project.py
@@ -150,7 +150,7 @@ class ProjectTask(models.Model):
'sale.order.line', 'Sales Order Item',
copy=False, tracking=True, index='btree_not_null', recursive=True,
compute='_compute_sale_line', store=True, readonly=False,
- domain="[('company_id', '=', company_id), ('is_service', '=', True), ('order_partner_id', 'child_of', commercial_partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]",
+ domain="[('company_id', '=', company_id), ('is_service', '=', True), ('order_partner_id', '=?', partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]",
help="Sales Order Item to which the time spent on this task will be added, in order to be invoiced to your customer.")
project_sale_order_id = fields.Many2one('sale.order', string="Project's sale order", related='project_id.sale_order_id')
task_to_invoice = fields.Boolean("To invoice", compute='_compute_task_to_invoice', search='_search_task_to_invoice', groups='sales_team.group_sale_salesman_all_leads')
@@ -271,6 +271,10 @@ class ProjectTask(models.Model):
operator_new = 'not inselect'
return [('sale_order_id', operator_new, (query, ()))]
+ @api.onchange('sale_line_id')
+ def _onchange_partner_id(self):
+ if not self.partner_id and self.sale_line_id:
+ self.partner_id = self.sale_line_id.order_partner_id
class ProjectTaskRecurrence(models.Model):
_inherit = 'project.task.recurrence'
diff --git a/addons/sale_project/views/project_task_views.xml b/addons/sale_project/views/project_task_views.xml
index f899fe78f5a..d1e80f5bbd8 100644
--- a/addons/sale_project/views/project_task_views.xml
+++ b/addons/sale_project/views/project_task_views.xml
@@ -67,7 +67,7 @@
-
+
diff --git a/addons/sale_timesheet/models/project.py b/addons/sale_timesheet/models/project.py
index 10521746604..ae4b471f359 100644
--- a/addons/sale_timesheet/models/project.py
+++ b/addons/sale_timesheet/models/project.py
@@ -437,7 +437,7 @@ class ProjectTask(models.Model):
has_multi_sol = fields.Boolean(compute='_compute_has_multi_sol', compute_sudo=True)
allow_billable = fields.Boolean(related="project_id.allow_billable")
timesheet_product_id = fields.Many2one(related="project_id.timesheet_product_id")
- remaining_hours_so = fields.Float('Remaining Hours on SO', compute='_compute_remaining_hours_so', compute_sudo=True)
+ remaining_hours_so = fields.Float('Remaining Hours on SO', compute='_compute_remaining_hours_so', search='_search_remaining_hours_so', compute_sudo=True)
remaining_hours_available = fields.Boolean(related="sale_line_id.remaining_hours_available")
@property
@@ -468,6 +468,10 @@ class ProjectTask(models.Model):
for task in self:
task.remaining_hours_so = mapped_remaining_hours[task._origin.id]
+ @api.model
+ def _search_remaining_hours_so(self, operator, value):
+ return [('sale_line_id.remaining_hours', operator, value)]
+
@api.depends('so_analytic_account_id.active')
def _compute_analytic_account_active(self):
super()._compute_analytic_account_active()
diff --git a/addons/sale_timesheet/views/project_task_views.xml b/addons/sale_timesheet/views/project_task_views.xml
index b9f29741aa0..ecaedc6bc3b 100644
--- a/addons/sale_timesheet/views/project_task_views.xml
+++ b/addons/sale_timesheet/views/project_task_views.xml
@@ -212,7 +212,7 @@
{'with_remaining_hours': True, 'with_price_unit': True}
- {'invisible': ['|', ('allow_billable', '=', False), ('partner_id', '=', False)]}
+ {'invisible': [('allow_billable', '=', False)]}