[IMP] project,sale,hr(_timesheet): improve the UX for project

Purpose of this commit to improve generic usage of project app.

So in this commit done following changes:
 - rename 'total hours' into 'total hours spent' (or 'total days spent' if the
   timesheet encoding  unit is in days) for project.task
 - timesheets (billed on timesheets) section > group the graph view by
   invoice > employee
 - add default action project update > profitability> timesheets
   (billed on timesheets) section > group the grid view by
   invoice > employee (display sections)
 - add default  the 'timesheet costs' measure in pivot view
 - display 'hours remaining' in red if the number is negative for project.task
   form view
 - display the 'week' scale by default in the grid view

task-2819458

closes odoo/odoo#90270

Related: odoo/enterprise#26795
Signed-off-by: Laurent Stukkens (ltu) <ltu@odoo.com>
This commit is contained in:
Rohitkumar (roku)
2022-11-23 15:13:50 +01:00
committed by Barad Mahendra
parent ac2074bd1a
commit 095efcd65c
6 changed files with 49 additions and 19 deletions
+1 -1
View File
@@ -242,7 +242,7 @@ class Task(models.Model):
remaining_hours = fields.Float("Remaining Hours", compute='_compute_remaining_hours', store=True, readonly=True, help="Number of allocated hours minus the number of hours spent.")
remaining_hours_percentage = fields.Float(compute='_compute_remaining_hours_percentage', search='_search_remaining_hours_percentage')
effective_hours = fields.Float("Hours Spent", compute='_compute_effective_hours', compute_sudo=True, store=True)
total_hours_spent = fields.Float("Total Hours", compute='_compute_total_hours_spent', store=True, help="Time spent on this task and its sub-tasks (and their own sub-tasks).")
total_hours_spent = fields.Float("Total Hours Spent", compute='_compute_total_hours_spent', store=True, help="Time spent on this task and its sub-tasks (and their own sub-tasks).")
progress = fields.Float("Progress", compute='_compute_progress_hours', store=True, group_operator="avg")
overtime = fields.Float(compute='_compute_progress_hours', store=True)
subtask_effective_hours = fields.Float("Sub-tasks Hours Spent", compute='_compute_subtask_effective_hours', recursive=True, store=True, help="Time spent on the sub-tasks (and their own sub-tasks) of this task.")
+2 -2
View File
@@ -115,7 +115,7 @@
<field name="planned_hours" widget="timesheet_uom_no_toggle" sum="Initially Planned Hours" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="effective_hours" widget="timesheet_uom" sum="Effective Hours" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="subtask_effective_hours" string="Sub-tasks Hours Spent" widget="timesheet_uom" sum="Sub-tasks Hours Spent" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="total_hours_spent" string="Total Hours" widget="timesheet_uom" sum="Total Hours" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="total_hours_spent" string="Total Hours Spent" widget="timesheet_uom" sum="Total Hours" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress &gt;= 100" decoration-warning="progress &gt;= 80 and progress &lt; 100" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="progress" widget="progressbar" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
</xpath>
@@ -241,7 +241,7 @@
attrs="{'invisible': ['|', '|', ('planned_hours', '=', 0.0), ('encode_uom_in_days', '=', False), ('remaining_hours', '&gt;=', 0)]}"/>
</span>
<field name="remaining_hours" widget="timesheet_uom" class="oe_subtotal_footer_separator"
attrs="{'invisible' : [('planned_hours', '=', 0.0)]}" nolabel="1"/>
attrs="{'invisible' : [('planned_hours', '=', 0.0)]}" nolabel="1" decoration-danger="remaining_hours &lt; 0"/>
</group>
</group>
</page>
+2 -2
View File
@@ -533,7 +533,7 @@
<field name="alias_enabled" invisible="1"/>
<label for="alias_name" class="fw-bold o_form_label" string="Create tasks by sending an email to"/>
<field name="alias_value" class="oe_read_only d-inline" readonly="1" widget="email" attrs="{'invisible': [('alias_name', '=', False)]}" />
<span class="oe_edit_only">
<span class="oe_edit_only o_row">
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
</span>
</div>
@@ -1089,8 +1089,8 @@
<field name="parent_id" invisible="1"/>
<field name="company_id" invisible="1"/>
<header>
<button name="action_assign_to_me" string="Assign to Me" type="object" attrs="{'invisible': &quot;['|', ('user_ids', 'in', uid), ('user_ids', '=', [])]&quot;}" data-hotkey="q"/>
<button name="action_assign_to_me" string="Assign to Me" type="object" class="oe_highlight" attrs="{'invisible' : &quot;['|', ('user_ids', 'in', uid), ('user_ids', '!=', [])]&quot;}" data-hotkey="q"/>
<button name="action_assign_to_me" string="Assign to Me" type="object" attrs="{'invisible': &quot;['|', ('user_ids', 'in', uid), ('user_ids', '=', [])]&quot;}" data-hotkey="q"/>
<field name="stage_id" widget="statusbar" options="{'clickable': '1', 'fold_field': 'fold'}" attrs="{'invisible': [('project_id', '=', False), ('stage_id', '=', False)]}"/>
<field name="personal_stage_type_id" widget="statusbar" options="{'clickable': '1', 'fold_field': 'fold'}" attrs="{'invisible': [('project_id', '!=', False)]}" domain="[('user_id', '=', uid)]" string="Personal Stage"/>
</header>
+1 -3
View File
@@ -276,7 +276,7 @@ class Project(models.Model):
action['context'].update(search_default_groupby_timesheet_invoice_type=False, **self.env.context)
graph_view = False
if section_name == 'billable_time':
graph_view = self.env.ref('hr_timesheet.view_hr_timesheet_line_graph_all').id
graph_view = self.env.ref('sale_timesheet.view_hr_timesheet_line_graph_invoice_employee').id
action['views'] = [
(view_id, view_type) if view_type != 'graph' else (graph_view or view_id, view_type)
for view_id, view_type in action['views']
@@ -440,7 +440,6 @@ class Project(models.Model):
if record_ids:
if invoice_type not in ['other_costs', 'other_revenues'] and can_see_timesheets: # action to see the timesheets
action = get_timesheets_action(invoice_type, record_ids)
action['context'] = json.dumps({'search_default_groupby_invoice': 1 if not cost and invoice_type == 'billable_time' else 0})
data['action'] = action
profitability_data.append(data)
return profitability_data
@@ -459,7 +458,6 @@ class Project(models.Model):
record_ids = aal_revenue.get('record_ids', [])
if can_see_timesheets and record_ids:
action = get_timesheets_action(revenue_id, record_ids)
action['context'] = json.dumps({'search_default_groupby_invoice': 1 if revenue_id == 'billable_time' else 0})
revenue['action'] = action
for cost in profitability_items['costs']['data']:
+17 -10
View File
@@ -134,18 +134,24 @@ class SaleOrderLine(models.Model):
def name_get(self):
res = super(SaleOrderLine, self).name_get()
with_remaining_hours = self.env.context.get('with_remaining_hours')
if with_remaining_hours:
if with_remaining_hours and any(line.remaining_hours_available for line in self):
names = dict(res)
result = []
uom_hour = self.env.ref('uom.product_uom_hour')
uom_day = self.env.ref('uom.product_uom_day')
company = self.env.company
encoding_uom = company.timesheet_encode_uom_id
is_hour = is_day = False
unit_label = ''
if encoding_uom == self.env.ref('uom.product_uom_hour'):
is_hour = True
unit_label = _('remaining')
elif encoding_uom == self.env.ref('uom.product_uom_day'):
is_day = True
unit_label = _('days remaining')
for line in self:
name = names.get(line.id)
if line.remaining_hours_available:
company = self.env.company
encoding_uom = company.timesheet_encode_uom_id
remaining_time = ''
if encoding_uom == uom_hour:
if is_hour:
hours, minutes = divmod(abs(line.remaining_hours) * 60, 60)
round_minutes = minutes / 30
minutes = math.ceil(round_minutes) if line.remaining_hours >= 0 else math.floor(round_minutes)
@@ -154,15 +160,16 @@ class SaleOrderLine(models.Model):
hours += 1
else:
minutes = minutes * 30
remaining_time = ' ({sign}{hours:02.0f}:{minutes:02.0f})'.format(
remaining_time = ' ({sign}{hours:02.0f}:{minutes:02.0f} {remaining})'.format(
sign='-' if line.remaining_hours < 0 else '',
hours=hours,
minutes=minutes)
elif encoding_uom == uom_day:
minutes=minutes,
remaining=unit_label)
elif is_day:
remaining_days = company.project_time_mode_id._compute_quantity(line.remaining_hours, encoding_uom, round=False)
remaining_time = ' ({qty:.02f} {unit})'.format(
qty=remaining_days,
unit=_('days') if abs(remaining_days) > 1 else _('day')
unit=unit_label
)
name = '{name}{remaining_time}'.format(
name=name,
@@ -88,6 +88,30 @@
</field>
</record>
<record id="view_hr_timesheet_line_graph_invoice_employee" model="ir.ui.view">
<field name="name">account.analytic.line.graph.invoice.employee</field>
<field name="model">account.analytic.line</field>
<field name="mode">primary</field>
<field name="inherit_id" ref="view_hr_timesheet_line_graph_employee_per_date"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='date']" position="replace">
<field name="timesheet_invoice_id"/>
</xpath>
</field>
</record>
<record id="view_hr_timesheet_line_pivot_inherited" model="ir.ui.view">
<field name="name">account.analytic.line.pivot</field>
<field name="model">account.analytic.line</field>
<field name="mode">primary</field>
<field name="inherit_id" ref="hr_timesheet.view_hr_timesheet_line_pivot"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='amount']" position="attributes">
<attribute name="type">measure</attribute>
</xpath>
</field>
</record>
<!--
Timesheet from Sales Order
-->
@@ -122,6 +146,7 @@
<field name="search_view_id" ref="hr_timesheet.hr_timesheet_line_search"/>
<field name="domain">[('project_id', '!=', False), ('so_line', '=', active_id)]</field>
<field name="context">{
'grid_range': 'week',
'search_default_billable_timesheet': True,
'search_default_week': 1,
'default_so_line': active_id,
@@ -155,7 +180,7 @@
<record id="timesheet_action_from_sales_order_item_pivot" model="ir.actions.act_window.view">
<field name="sequence" eval="30"/>
<field name="view_mode">pivot</field>
<field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_pivot"/>
<field name="view_id" ref="view_hr_timesheet_line_pivot_inherited"/>
<field name="act_window_id" ref="timesheet_action_from_sales_order_item"/>
</record>