[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:
committed by
Barad Mahendra
parent
ac2074bd1a
commit
095efcd65c
@@ -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.")
|
||||
|
||||
@@ -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 >= 100" decoration-warning="progress >= 80 and progress < 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', '>=', 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 < 0"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
@@ -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': "['|', ('user_ids', 'in', uid), ('user_ids', '=', [])]"}" data-hotkey="q"/>
|
||||
<button name="action_assign_to_me" string="Assign to Me" type="object" class="oe_highlight" attrs="{'invisible' : "['|', ('user_ids', 'in', uid), ('user_ids', '!=', [])]"}" data-hotkey="q"/>
|
||||
<button name="action_assign_to_me" string="Assign to Me" type="object" attrs="{'invisible': "['|', ('user_ids', 'in', uid), ('user_ids', '=', [])]"}" 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>
|
||||
|
||||
@@ -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']:
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user