[IMP] {hr,sale}_timesheet: improve generic UX

Purpose of the commit is to improve the UX of the timesheet.

So in this commit did the following changes:
- SOL field display by default in timesheet list view
- If the timesheet is invoiced, it will appear in read-only mode

task-2918757

closes odoo/odoo#97203

Related: odoo/enterprise#30000
Related: odoo/upgrade#3729
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
This commit is contained in:
Kartik Chavda
2023-04-03 10:41:23 +02:00
parent df1139856b
commit ad40298a9a
6 changed files with 51 additions and 31 deletions
@@ -63,6 +63,7 @@ class AccountAnalyticLine(models.Model):
manager_id = fields.Many2one('hr.employee', "Manager", related='employee_id.parent_id', store=True)
encoding_uom_id = fields.Many2one('uom.uom', compute='_compute_encoding_uom_id')
partner_id = fields.Many2one(compute='_compute_partner_id', store=True, readonly=False)
readonly_timesheet = fields.Boolean(string="Readonly Timesheet", compute="_compute_readonly_timesheet")
def name_get(self):
result = super().name_get()
@@ -82,6 +83,16 @@ class AccountAnalyticLine(models.Model):
timesheet_dict = {res['id']: _get_display_name(res['project_id'], res['task_id']) for res in timesheets_read}
return list({**dict(result), **timesheet_dict}.items())
def _is_readonly(self):
self.ensure_one()
# is overridden in other timesheet related modules
return False
def _compute_readonly_timesheet(self):
readonly_timesheets = self.filtered(lambda timesheet: timesheet._is_readonly())
readonly_timesheets.readonly_timesheet = True
(self - readonly_timesheets).readonly_timesheet = False
def _compute_encoding_uom_id(self):
for analytic_line in self:
analytic_line.encoding_uom_id = analytic_line.company_id.timesheet_encode_uom_id
@@ -5,13 +5,14 @@
<field name="name">account.analytic.line.tree.hr_timesheet</field>
<field name="model">account.analytic.line</field>
<field name="arch" type="xml">
<tree editable="bottom" string="Timesheet Activities" sample="1">
<field name="date"/>
<field name="employee_id" invisible="1"/>
<field name="project_id" required="1" options="{'no_create_edit': True, 'no_open': 1}"/>
<field name="task_id" optional="show" options="{'no_create_edit': True, 'no_open': True}" widget="task_with_hours" context="{'default_project_id': project_id}"/>
<field name="name" optional="show" required="0"/>
<field name="unit_amount" optional="show" widget="timesheet_uom" sum="Total" decoration-danger="unit_amount &gt; 24 or unit_amount &lt; 0"/>
<tree editable="bottom" string="Timesheet Activities" sample="1" decoration-muted="readonly_timesheet == True">
<field name="readonly_timesheet" invisible="1"/>
<field name="date" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="employee_id" invisible="1" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="project_id" required="1" options="{'no_create_edit': True, 'no_open': 1}" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="task_id" optional="show" options="{'no_create_edit': True, 'no_open': True}" widget="task_with_hours" context="{'default_project_id': project_id}" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="name" optional="show" required="0" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="unit_amount" optional="show" widget="timesheet_uom" sum="Total" decoration-danger="unit_amount &gt; 24 or unit_amount &lt; 0" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="company_id" invisible="1"/>
<field name="user_id" invisible="1"/>
</tree>
@@ -120,15 +121,18 @@
<sheet string="Analytic Entry">
<group>
<group>
<field name="project_id" required="1"/>
<field name="task_id" widget="task_with_hours" context="{'default_project_id': project_id}"/>
<field name="name"/>
<field name="readonly_timesheet" invisible="1"/>
<field name="project_id" required="1" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="task_id" widget="task_with_hours" context="{'default_project_id': project_id}"
attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="name" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="company_id" groups="base.group_multi_company" invisible="1"/>
</group>
<group>
<field name="date"/>
<field name="date" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="amount" invisible="1"/>
<field name="unit_amount" widget="timesheet_uom" decoration-danger="unit_amount &gt; 24"/>
<field name="unit_amount" widget="timesheet_uom" decoration-danger="unit_amount &gt; 24"
attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="currency_id" invisible="1"/>
<field name="company_id" invisible="1"/>
</group>
@@ -146,7 +150,7 @@
<field name="priority">10</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='date']" position="before">
<field name="employee_id" required="1" context="{'active_test': True}"/>
<field name="employee_id" required="1" context="{'active_test': True}" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="user_id" invisible="1"/>
</xpath>
</field>
@@ -40,12 +40,15 @@
</div>
</group>
<field name="timesheet_ids" mode="tree,kanban" attrs="{'invisible': [('analytic_account_active', '=', False)]}" context="{'default_project_id': project_id, 'default_name':''}">
<tree editable="bottom" string="Timesheet Activities" default_order="date">
<field name="date"/>
<tree editable="bottom" string="Timesheet Activities" default_order="date" decoration-muted="readonly_timesheet == True">
<field name="readonly_timesheet" invisible="1"/>
<field name="date" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="user_id" invisible="1"/>
<field name="employee_id" required="1" widget="many2one_avatar_employee" context="{'active_test': True}"/>
<field name="name" required="0"/>
<field name="unit_amount" widget="timesheet_uom" decoration-danger="unit_amount &gt; 24 or unit_amount &lt; 0"/>
<field name="employee_id" required="1" widget="many2one_avatar_employee"
context="{'active_test': True}" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="name" required="0" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="unit_amount" widget="timesheet_uom" decoration-danger="unit_amount &gt; 24 or unit_amount &lt; 0"
attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="project_id" invisible="1"/>
<field name="task_id" invisible="1"/>
<field name="company_id" invisible="1"/>
@@ -86,11 +89,14 @@
<form string="Timesheet Activities">
<sheet>
<group>
<field name="date"/>
<field name="user_id" invisible="1"/>
<field name="employee_id" required="1" widget="many2one_avatar_employee" context="{'active_test': True}"/>
<field name="name" required="0"/>
<field name="unit_amount" string="Duration" widget="float_time" decoration-danger="unit_amount &gt; 24"/>
<field name="readonly_timesheet" invisible="1"/>
<field name="date" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="user_id" invisible="1" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="employee_id" required="1" widget="many2one_avatar_employee" context="{'active_test': True}"
attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="name" required="0" attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="unit_amount" string="Duration" widget="float_time" decoration-danger="unit_amount &gt; 24"
attrs="{'readonly': [('readonly_timesheet', '=', True)]}"/>
<field name="project_id" invisible="1"/>
<field name="task_id" invisible="1"/>
<field name="company_id" invisible="1"/>
+3
View File
@@ -79,6 +79,9 @@ class AccountAnalyticLine(models.Model):
def _compute_project_id(self):
super(AccountAnalyticLine, self.filtered(lambda t: t._is_not_billed()))._compute_project_id()
def _is_readonly(self):
return super()._is_readonly() or not self._is_not_billed()
def _is_not_billed(self):
self.ensure_one()
return not self.timesheet_invoice_id or self.timesheet_invoice_id.state == 'cancel'
@@ -45,7 +45,7 @@
<field name="commercial_partner_id" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="is_so_line_edited" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="allow_billable" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="so_line" widget="so_line_field" optional="hide" options="{'no_create': True}" context="{'create': False, 'edit': False, 'delete': False}" attrs="{'invisible': [('allow_billable', '=', False)]}" placeholder="Non-billable" groups="sales_team.group_sale_salesman"/>
<field name="so_line" widget="so_line_field" optional="show" options="{'no_create': True}" context="{'create': False, 'edit': False, 'delete': False}" attrs="{'invisible': [('allow_billable', '=', False)], 'readonly': [('readonly_timesheet', '=', True)]}" placeholder="Non-billable" groups="sales_team.group_sale_salesman"/>
</xpath>
</field>
</record>
@@ -59,7 +59,7 @@
<field name="commercial_partner_id" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="is_so_line_edited" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="allow_billable" invisible="1" groups="sales_team.group_sale_salesman"/>
<field name="so_line" widget="so_line_field" options='{"no_create": True}' context="{'create': False, 'edit': False, 'delete': False, 'with_price_unit': True}" attrs="{'invisible': [('allow_billable', '=', False)]}" placeholder="Non-billable" groups="sales_team.group_sale_salesman"/>
<field name="so_line" widget="so_line_field" options='{"no_create": True}' context="{'create': False, 'edit': False, 'delete': False, 'with_price_unit': True}" attrs="{'invisible': [('allow_billable', '=', False)], 'readonly': [('readonly_timesheet', '=', True)]}" placeholder="Non-billable" groups="sales_team.group_sale_salesman"/>
</xpath>
</field>
</record>
@@ -126,10 +126,6 @@
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='timesheet_ids']/tree" position="attributes">
<!-- <field name="timesheet_ids"/> is already inside a block groups="hr_timesheet.group_hr_timesheet_user" -->
<attribute name="decoration-muted">timesheet_invoice_id != False</attribute>
</xpath>
<xpath expr="//field[@name='user_ids']" position="after">
<field name="is_project_map_empty" invisible="1" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="has_multi_sol" invisible="1" groups="hr_timesheet.group_hr_timesheet_user"/>
@@ -149,12 +145,12 @@
<!-- <field name="timesheet_ids"/> is already inside a block groups="hr_timesheet.group_hr_timesheet_user" -->
<field name="timesheet_invoice_id" invisible="1"/>
<field name="so_line" groups="!sales_team.group_sale_salesman"
attrs="{'column_invisible': [('parent.allow_billable', '=', False)]}"
attrs="{'column_invisible': [('parent.allow_billable', '=', False)], 'readonly': [('readonly_timesheet', '=', True)]}"
context="{'with_remaining_hours': True, 'with_price_unit': True}" options="{'no_create': True, 'no_open': True}"
domain="[('is_service', '=', True), ('order_partner_id', 'child_of', parent.partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]"
optional="hide"/>
<field name="so_line" groups="sales_team.group_sale_salesman"
attrs="{'column_invisible': [('parent.allow_billable', '=', False)]}"
attrs="{'column_invisible': [('parent.allow_billable', '=', False)], 'readonly': [('readonly_timesheet', '=', True)]}"
context="{'with_remaining_hours': True, 'with_price_unit': True}" options="{'no_create': True}"
domain="[('is_service', '=', True), ('order_partner_id', 'child_of', parent.partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]"
optional="hide"/>