[IMP] {sale_,}project: hide customer for non-billble project/task

Before this commit user able to see and add customers on non fsm and
non billable tasks/projects which is not provide any significance as
user don't need customer for normal tasks/projects.

So, in this commit hide customer field and also move partner phone
and city to field service app as it was only usefull for FSM project
and task.

task-3141350

closes odoo/odoo#111335

Related: odoo/upgrade#4274
Related: odoo/enterprise#36459
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
This commit is contained in:
Kartik Chavda
2023-03-31 19:10:48 +02:00
parent 421537e67e
commit f7ed7052fb
17 changed files with 132 additions and 56 deletions
+2 -2
View File
@@ -33,8 +33,8 @@ class TimesheetCustomerPortal(CustomerPortal):
'name': {'input': 'name', 'label': _('Search in Description')},
}
def _task_get_searchbar_sortings(self, milestones_allowed):
values = super()._task_get_searchbar_sortings(milestones_allowed)
def _task_get_searchbar_sortings(self, milestones_allowed, project=False):
values = super()._task_get_searchbar_sortings(milestones_allowed, project)
values['progress'] = {'label': _('Progress'), 'order': 'progress asc', 'sequence': 10}
return values
+17 -11
View File
@@ -33,7 +33,7 @@ class ProjectCustomerPortal(CustomerPortal):
domain = [('project_id', '=', project.id)]
# pager
url = "/my/projects/%s" % project.id
values = self._prepare_tasks_values(page, date_begin, date_end, sortby, search, search_in, groupby, url, domain, su=bool(access_token))
values = self._prepare_tasks_values(page, date_begin, date_end, sortby, search, search_in, groupby, url, domain, su=bool(access_token), project=project)
pager = portal_pager(**values['pager'])
values.update(
@@ -264,11 +264,10 @@ class ProjectCustomerPortal(CustomerPortal):
return values
def _task_get_searchbar_sortings(self, milestones_allowed):
def _task_get_searchbar_sortings(self, milestones_allowed, project=False):
values = {
'date': {'label': _('Newest'), 'order': 'create_date desc', 'sequence': 1},
'name': {'label': _('Title'), 'order': 'name', 'sequence': 2},
'project': {'label': _('Project'), 'order': 'project_id, stage_id', 'sequence': 3},
'users': {'label': _('Assignees'), 'order': 'user_ids', 'sequence': 4},
'stage': {'label': _('Stage'), 'order': 'stage_id, project_id', 'sequence': 5},
'status': {'label': _('Status'), 'order': 'state', 'sequence': 6},
@@ -276,19 +275,22 @@ class ProjectCustomerPortal(CustomerPortal):
'date_deadline': {'label': _('Deadline'), 'order': 'date_deadline asc', 'sequence': 9},
'update': {'label': _('Last Stage Update'), 'order': 'date_last_stage_update desc', 'sequence': 11},
}
if not project:
values['project'] = {'label': _('Project'), 'order': 'project_id, stage_id', 'sequence': 3}
if milestones_allowed:
values['milestone'] = {'label': _('Milestone'), 'order': 'milestone_id', 'sequence': 7}
return values
def _task_get_searchbar_groupby(self, milestones_allowed):
def _task_get_searchbar_groupby(self, milestones_allowed, project=False):
values = {
'none': {'input': 'none', 'label': _('None'), 'order': 1},
'project': {'input': 'project', 'label': _('Project'), 'order': 2},
'stage': {'input': 'stage', 'label': _('Stage'), 'order': 4},
'status': {'input': 'status', 'label': _('Status'), 'order': 5},
'priority': {'input': 'priority', 'label': _('Priority'), 'order': 7},
'customer': {'input': 'customer', 'label': _('Customer'), 'order': 10},
}
if not project:
values['project'] = {'input': 'project', 'label': _('Project'), 'order': 2}
if milestones_allowed:
values['milestone'] = {'input': 'milestone', 'label': _('Milestone'), 'order': 6}
return dict(sorted(values.items(), key=lambda item: item[1]["order"]))
@@ -310,18 +312,20 @@ class ProjectCustomerPortal(CustomerPortal):
return order
return '%s, %s' % (field_name, order)
def _task_get_searchbar_inputs(self, milestones_allowed):
def _task_get_searchbar_inputs(self, milestones_allowed, project=False):
values = {
'all': {'input': 'all', 'label': _('Search in All'), 'order': 1},
'content': {'input': 'content', 'label': Markup(_('Search <span class="nolabel"> (in Content)</span>')), 'order': 1},
'ref': {'input': 'ref', 'label': _('Search in Ref'), 'order': 1},
'project': {'input': 'project', 'label': _('Search in Project'), 'order': 2},
'users': {'input': 'users', 'label': _('Search in Assignees'), 'order': 3},
'stage': {'input': 'stage', 'label': _('Search in Stages'), 'order': 4},
'status': {'input': 'status', 'label': _('Search in Status'), 'order': 5},
'priority': {'input': 'priority', 'label': _('Search in Priority'), 'order': 7},
'customer': {'input': 'customer', 'label': _('Search in Customer'), 'order': 10},
'message': {'input': 'message', 'label': _('Search in Messages'), 'order': 11},
}
if not project:
values['project'] = {'input': 'project', 'label': _('Search in Project'), 'order': 2}
if milestones_allowed:
values['milestone'] = {'input': 'milestone', 'label': _('Search in Milestone'), 'order': 6}
@@ -354,16 +358,16 @@ class ProjectCustomerPortal(CustomerPortal):
search_domain.append([('state', 'ilike', state_dict.get(search, search))])
return OR(search_domain)
def _prepare_tasks_values(self, page, date_begin, date_end, sortby, search, search_in, groupby, url="/my/tasks", domain=None, su=False):
def _prepare_tasks_values(self, page, date_begin, date_end, sortby, search, search_in, groupby, url="/my/tasks", domain=None, su=False, project=False):
values = self._prepare_portal_layout_values()
Task = request.env['project.task']
milestone_domain = AND([domain, [('allow_milestones', '=', 'True')]])
milestones_allowed = Task.sudo().search_count(milestone_domain, limit=1) == 1
searchbar_sortings = dict(sorted(self._task_get_searchbar_sortings(milestones_allowed).items(),
searchbar_sortings = dict(sorted(self._task_get_searchbar_sortings(milestones_allowed, project).items(),
key=lambda item: item[1]["sequence"]))
searchbar_inputs = self._task_get_searchbar_inputs(milestones_allowed)
searchbar_groupby = self._task_get_searchbar_groupby(milestones_allowed)
searchbar_inputs = self._task_get_searchbar_inputs(milestones_allowed, project)
searchbar_groupby = self._task_get_searchbar_groupby(milestones_allowed, project)
if not domain:
domain = []
@@ -379,6 +383,8 @@ class ProjectCustomerPortal(CustomerPortal):
# default group by value
if not groupby or (groupby == 'milestone' and not milestones_allowed):
groupby = 'project'
if project:
groupby = 'stage'
if date_begin and date_end:
domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
+3
View File
@@ -771,6 +771,9 @@ class Project(models.Model):
# Business Methods
# ---------------------------------------------------
def _get_hide_partner(self):
return False
@api.model
def _create_analytic_account_from_values(self, values):
company = self.env['res.company'].browse(values.get('company_id')) if values.get('company_id') else self.env.company
-15
View File
@@ -179,10 +179,6 @@ class Task(models.Model):
string='Customer', recursive=True, tracking=True,
compute='_compute_partner_id', store=True, readonly=False,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
partner_phone = fields.Char(
compute='_compute_partner_phone', inverse='_inverse_partner_phone',
string="Phone", readonly=False, store=True, copy=False)
partner_city = fields.Char(related='partner_id.city', readonly=False)
email_cc = fields.Char(help='Email addresses that were in the CC of the incoming emails from this task and that are not currently linked to an existing customer.')
company_id = fields.Many2one(
'res.company', string='Company', compute='_compute_company_id', store=True, readonly=False, recursive=True,
@@ -469,17 +465,6 @@ class Task(models.Model):
for task in tasks_with_dependency:
task.dependent_tasks_count = dependent_tasks_count_dict.get(task.id, 0)
@api.depends('partner_id.phone')
def _compute_partner_phone(self):
for task in self:
if task.partner_phone != task.partner_id.phone:
task.partner_phone = task.partner_id.phone
def _inverse_partner_phone(self):
for task in self:
if task.partner_id and task.partner_phone != task.partner_id.phone:
task.partner_id.phone = task.partner_phone
@api.constrains('parent_id')
def _check_parent_id(self):
if not self._check_recursion():
@@ -48,7 +48,6 @@ class ProjectTaskRecurrence(models.Model):
'name',
'parent_id',
'partner_id',
'partner_phone',
'planned_hours',
'project_id',
'project_privacy_visibility',
@@ -175,7 +175,7 @@
<field name="company_id" invisible="1" />
<field name="is_closed" invisible="1" />
<field name="allow_subtasks" invisible="1" />
<field name="partner_id" options="{'no_open': True, 'no_create': True, 'no_edit': True}"/>
<field name="partner_id" options="{'no_open': True, 'no_create': True, 'no_edit': True}" attrs="{'invisible': [('project_id', '=', False)]}"/>
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}"/>
</group>
</group>
@@ -198,7 +198,7 @@
attrs="{'invisible': [('allow_milestones', '=', False)], 'column_invisible': [('parent.allow_milestones', '=', False)]}"
/>
<field name="company_id" invisible="1"/>
<field name="partner_id" options="{'no_open': True, 'no_create': True, 'no_edit': True}" optional="hide"/>
<field name="partner_id" options="{'no_open': True, 'no_create': True, 'no_edit': True}" optional="hide" attrs="{'invisible': [('project_id', '=', False)]}"/>
<field name="user_ids" invisible="1" />
<field name="portal_user_names" string="Assignees" optional="show"/>
<field name="date_deadline" attrs="{'invisible': [('is_closed', '=', True)]}" optional="show"/>
+5 -6
View File
@@ -52,7 +52,7 @@
<field name="priority">999</field>
<field name="arch" type="xml">
<field name="stage_id" position="after">
<field name="project_id" string="Project" invisible="context.get('default_project_id')"/>
<field name="project_id" string="Project"/>
</field>
<field name="partner_id" position="after">
<field name="company_id" groups="base.group_multi_company"/>
@@ -61,7 +61,7 @@
<filter string="My Tasks" name="my_tasks" domain="[('user_ids', 'in', uid)]"/>
</field>
<filter name="stage" position="after">
<filter string="Project" name="project" context="{'group_by': 'project_id'}" invisible="context.get('default_project_id')"/>
<filter string="Project" name="project" context="{'group_by': 'project_id'}"/>
</filter>
</field>
</record>
@@ -342,7 +342,6 @@
<group>
<field name="active" invisible="1"/>
<field name="partner_id" nolabel="0" widget="res_partner_many2one" class="o_task_customer_field" attrs="{'invisible': [('project_id', '=', False)]}"/>
<field name="partner_phone" widget="phone" attrs="{'invisible': True}"/>
<label for="date_deadline"/>
<div id="date_deadline_and_recurring_task" class="d-inline-flex w-100">
<field name="date_deadline" nolabel="1"/>
@@ -392,7 +391,7 @@
context="{'default_project_id': project_id}"
attrs="{'invisible': [('allow_milestones', '=', False)], 'column_invisible': [('parent.allow_milestones', '=', False)]}"
/>
<field name="partner_id" optional="hide"/>
<field name="partner_id" optional="hide" widget="res_partner_many2one" attrs="{'invisible': [('project_id', '=', False)]}"/>
<field name="user_ids" widget="many2many_avatar_user" optional="show" domain="[('share', '=', False), ('active', '=', True)]"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
<field name="company_id" invisible="1"/>
@@ -427,7 +426,7 @@
context="{'default_project_id': project_id}"
attrs="{'invisible': [('allow_milestones', '=', False)], 'column_invisible': [('parent.allow_milestones', '=', False)]}"
/>
<field name="partner_id" optional="hide"/>
<field name="partner_id" optional="hide" widget="res_partner_many2one" attrs="{'invisible': [('project_id', '=', False)]}"/>
<field name="parent_id" optional="hide" attrs="{'invisible': [('allow_subtasks', '=', False)]}" groups="base.group_no_one"/>
<field name="user_ids" widget="many2many_avatar_user" optional="show" domain="[('share', '=', False), ('active', '=', True)]"/>
<field name="company_id" optional="hide" groups="base.group_multi_company" />
@@ -625,7 +624,7 @@
<field name="name" string="Title" widget="name_with_subtask_count"/>
<field name="project_id" widget="project_private_task" optional="show" readonly="1" options="{'no_open': 1}" invisible="context.get('default_project_id')"/>
<field name="milestone_id" attrs="{'invisible': [('allow_milestones', '=', False)]}" context="{'default_project_id': project_id}" groups="project.group_project_milestone" optional="hide"/>
<field name="partner_id" optional="hide" widget="res_partner_many2one" context="{'res_partner_search_mode': 'customer'}"/>
<field name="partner_id" optional="hide" widget="res_partner_many2one" context="{'res_partner_search_mode': 'customer'}" attrs="{'invisible': [('project_id', '=', False)]}"/>
<field name="user_ids" optional="show" widget="many2many_avatar_user" domain="[('share', '=', False), ('active', '=', True)]"/>
<field name="company_id" groups="base.group_multi_company" optional="show" invisible="context.get('default_project_id')"/>
<field name="date_deadline" optional="hide" widget="remaining_days" attrs="{'invisible': [('is_closed', '=', True)]}"/>
+4
View File
@@ -4,3 +4,7 @@
from . import models
from . import controllers
from . import report
def _set_allow_billable_in_project(env):
projects = env['project.project'].search([('partner_id', '!=', False), ('allow_billable', '=', False)])
projects.allow_billable = True
+1
View File
@@ -26,5 +26,6 @@ This module allows to generate a project/task from sales orders.
'sale_project/static/src/components/project_right_side_panel/**/*',
],
},
'post_init_hook': '_set_allow_billable_in_project',
'license': 'LGPL-3',
}
+15 -9
View File
@@ -9,10 +9,13 @@ from odoo.addons.project.controllers.portal import ProjectCustomerPortal
class SaleProjectCustomerPortal(ProjectCustomerPortal):
def _task_get_searchbar_groupby(self, milestones_allowed):
values = super()._task_get_searchbar_groupby(milestones_allowed)
values['sale_order'] = {'input': 'sale_order', 'label': _lt('Sales Order'), 'order': 8}
values['sale_line'] = {'input': 'sale_line', 'label': _lt('Sales Order Item'), 'order': 9}
def _task_get_searchbar_groupby(self, milestones_allowed, project=False):
values = super()._task_get_searchbar_groupby(milestones_allowed, project)
if project and not project.sudo()._get_hide_partner():
del values['customer']
if not project or project.sudo().allow_billable:
values['sale_order'] = {'input': 'sale_order', 'label': _lt('Sales Order'), 'order': 8}
values['sale_line'] = {'input': 'sale_line', 'label': _lt('Sales Order Item'), 'order': 9}
return dict(sorted(values.items(), key=lambda item: item[1]["order"]))
def _task_get_groupby_mapping(self):
@@ -20,11 +23,14 @@ class SaleProjectCustomerPortal(ProjectCustomerPortal):
groupby_mapping.update(sale_order='sale_order_id', sale_line='sale_line_id')
return groupby_mapping
def _task_get_searchbar_inputs(self, milestones_allowed):
values = super()._task_get_searchbar_inputs(milestones_allowed)
values['sale_order'] = {'input': 'sale_order', 'label': _lt('Search in Sales Order'), 'order': 8}
values['sale_line'] = {'input': 'sale_line', 'label': _lt('Search in Sales Order Item'), 'order': 9}
values['invoice'] = {'input': 'invoice', 'label': _lt('Search in Invoice'), 'order': 10}
def _task_get_searchbar_inputs(self, milestones_allowed, project=False):
values = super()._task_get_searchbar_inputs(milestones_allowed, project)
if project and not project.sudo()._get_hide_partner():
del values['customer']
if not project or project.sudo().allow_billable:
values['sale_order'] = {'input': 'sale_order', 'label': _lt('Search in Sales Order'), 'order': 8}
values['sale_line'] = {'input': 'sale_line', 'label': _lt('Search in Sales Order Item'), 'order': 9}
values['invoice'] = {'input': 'invoice', 'label': _lt('Search in Invoice'), 'order': 10}
return dict(sorted(values.items(), key=lambda item: item[1]["order"]))
def _task_get_search_domain(self, search_in, search):
+24
View File
@@ -27,6 +27,7 @@ class Project(models.Model):
has_any_so_with_nothing_to_invoice = fields.Boolean('Has a SO with an invoice status of No', compute='_compute_has_any_so_with_nothing_to_invoice')
invoice_count = fields.Integer(related='analytic_account_id.invoice_count', groups='account.group_account_readonly')
vendor_bill_count = fields.Integer(related='analytic_account_id.vendor_bill_count', groups='account.group_account_readonly')
partner_id = fields.Many2one(compute="_compute_partner_id", store=True, readonly=False)
@api.model
def _map_tasks_default_valeus(self, task, project):
@@ -34,6 +35,10 @@ class Project(models.Model):
defaults['sale_line_id'] = False
return defaults
@api.depends('allow_billable')
def _compute_partner_id(self):
self.filtered(lambda project: not project.allow_billable).partner_id = False
@api.depends('partner_id')
def _compute_sale_line_id(self):
self.filtered(
@@ -537,6 +542,18 @@ class Project(models.Model):
})
return buttons
# ---------------------------------------------------
# Actions
# ---------------------------------------------------
def _get_hide_partner(self):
return not self.allow_billable
def action_view_tasks(self):
action = super().action_view_tasks()
action['context']['hide_partner'] = self._get_hide_partner()
return action
def action_open_project_vendor_bills(self):
query = self.env['account.move.line']._search([('move_id.move_type', 'in', ['in_invoice', 'in_refund'])])
query.add_where('analytic_distribution ? %s', [str(self.analytic_account_id.id)])
@@ -562,6 +579,7 @@ class Project(models.Model):
action = super().action_project_sharing()
action['context'].update({
'sale_show_partner_name': True,
'hide_partner': self._get_hide_partner(),
})
return action
@@ -605,6 +623,12 @@ class ProjectTask(models.Model):
task.partner_id = sale_order_id.partner_id
task.sale_order_id = sale_order_id
@api.depends('allow_billable')
def _compute_partner_id(self):
billable_task = self.filtered('allow_billable')
(self - billable_task).partner_id = False
super(ProjectTask, billable_task)._compute_partner_id()
@api.depends('partner_id.commercial_partner_id', 'sale_line_id.order_partner_id', 'parent_id.sale_line_id', 'project_id.sale_line_id', 'milestone_id.sale_line_id', 'allow_billable')
def _compute_sale_line(self):
for task in self:
@@ -47,7 +47,7 @@ class TestNestedTaskUpdate(TransactionCase):
self.assertEqual(parent.partner_id, self.project.partner_id, "The partner set on the parent task should the one set on the project linked")
self.assertFalse(child.project_id, "The project set on the subtask should be False by default")
self.assertFalse(child.allow_billable, "The subtask should not be billable as it is parent task since no project is set")
self.assertEqual(child.partner_id, parent.partner_id, "The partner set on the subtask should be the one of its parent task")
self.assertFalse(child.partner_id, "Subtask with no project should not have partner")
def test_creating_subtask_user_id_on_parent_dont_go_on_child(self):
parent = self.env['project.task'].create({'name': 'parent', 'user_ids': [(4, self.user.id)]})
@@ -56,12 +56,12 @@ class TestNestedTaskUpdate(TransactionCase):
def test_creating_subtask_partner_id_on_parent_goes_on_child(self):
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.user.partner_id.id, 'project_id': self.project.id})
child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id})
child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id, 'project_id': self.project.id})
child._compute_partner_id() # the compute will be triggered since the user set the parent_id.
self.assertEqual(child.partner_id, self.user.partner_id)
# Another case, it is the parent as a default value
child = self.env['project.task'].with_context(default_parent_id=parent.id).create({'name': 'child'})
child = self.env['project.task'].with_context(default_parent_id=parent.id, default_project_id=self.project.id).create({'name': 'child'})
self.assertEqual(child.partner_id, self.user.partner_id)
def test_creating_subtask_sale_line_id_on_parent_goes_on_child_if_same_partner_in_values(self):
@@ -157,10 +157,10 @@ class TestNestedTaskUpdate(TransactionCase):
self.assertFalse(child.user_ids)
def test_linking_partner_id_on_parent_write_on_child(self):
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.user.partner_id.id})
parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.user.partner_id.id, 'project_id': self.project.id})
child = self.env['project.task'].create({'name': 'child', 'partner_id': False})
self.assertFalse(child.partner_id)
child.write({'parent_id': parent.id})
child.write({'parent_id': parent.id, 'project_id': self.project.id})
self.assertEqual(child.partner_id, self.user.partner_id)
def test_linking_sale_line_id_on_parent_write_on_child_if_same_partner(self):
@@ -9,6 +9,7 @@
<field name="arch" type="xml">
<div name="button_box" position="inside">
<field name="display_sale_order_button" invisible="1"/>
<field name="allow_billable" invisible="1"/>
<button class="oe_stat_button"
type="object" name="action_project_sharing_view_so" icon="fa-dollar"
attrs="{'invisible': [('display_sale_order_button', '=', False)]}"
@@ -17,10 +18,17 @@
<xpath expr="//field[@name='partner_id']" position="attributes">
<attribute name="options">{'no_open': True, 'no_create': True, 'no_edit': True}</attribute>
<attribute name="context">{'res_partner_search_mode': 'customer'}</attribute>
<attribute name="attrs">{'invisible': [('allow_billable', '=', False)]}</attribute>
</xpath>
<xpath expr="//field[@name='partner_id']" position="after">
<field name="sale_line_id" string="Sales Order Item" attrs="{'invisible': [('partner_id', '=', False)]}" options='{"no_open": True}' readonly="1" context="{'create': False, 'edit': False, 'delete': False}"/>
</xpath>
<xpath expr="//field[@name='child_ids']/tree/field[@name='partner_id']" position="after">
<field name="allow_billable"/>
</xpath>
<xpath expr="//field[@name='child_ids']/tree/field[@name='partner_id']" position="attributes">
<attribute name="attrs">{'column_invisible': [('parent.allow_billable', '=', False)], 'invisible': [('allow_billable', '=', False)]}</attribute>
</xpath>
</field>
</record>
@@ -36,6 +36,10 @@
<field name="arch" type="xml">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="sale_line_id" optional="hide" readonly="1"/>
<field name="allow_billable" invisible="1"/>
</xpath>
<xpath expr="//field[@name='partner_id']" position="attributes">
<attribute name="attrs">{'invisible': [('allow_billable', '=', False)]}</attribute>
</xpath>
</field>
</record>
@@ -52,6 +56,7 @@
<xpath expr="//field[@name='partner_id']" position="attributes">
<attribute name="options">{'always_reload': True}</attribute>
<attribute name="context">{'res_partner_search_mode': 'customer'}</attribute>
<attribute name="attrs">{'invisible': [('allow_billable', '=', False)]}</attribute>
</xpath>
<xpath expr="//group[@name='group_time_managment']" position="after">
<group name="group_sales_invoicing" string="Sales &amp; Invoicing" col="1" class="row mt16 o_settings_container col-lg-6">
@@ -80,13 +85,41 @@
<xpath expr="//field[@name='partner_id']" position="attributes">
<attribute name="options">{'always_reload': True}</attribute>
<attribute name="context">{'res_partner_search_mode': 'customer'}</attribute>
<attribute name="attrs">{'invisible': [('allow_billable', '=', False)]}</attribute>
</xpath>
<xpath expr="//field[@name='partner_phone']" position="after">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="sale_order_id" attrs="{'invisible': True}" groups="sales_team.group_sale_salesman"/>
<field name="sale_line_id" groups="!sales_team.group_sale_salesman" string="Sales Order Item" options='{"no_open": True}' readonly="1" context="{'create': False, 'edit': False, 'delete': False}" attrs="{'invisible': ['|', ('allow_billable', '=', False), ('project_id', '=', False)]}"/>
<field name="sale_line_id" groups="sales_team.group_sale_salesman" string="Sales Order Item" options='{"no_create": True}' readonly="0" context="{'create': False, 'edit': False, 'delete': False, 'with_price_unit': True}" placeholder="Non-billable" attrs="{'invisible': ['|', ('allow_billable', '=', False), ('project_id', '=', False)]}"/>
<field name="allow_billable" invisible="1"/>
</xpath>
<xpath expr="//field[@name='child_ids']/tree/field[@name='partner_id']" position="after">
<field name="allow_billable" invisible="1"/>
</xpath>
<xpath expr="//field[@name='child_ids']/tree/field[@name='partner_id']" position="attributes">
<attribute name="attrs">{'column_invisible': [('parent.allow_billable', '=', False)], 'invisible': [('allow_billable', '=', False)]}</attribute>
</xpath>
<xpath expr="//field[@name='depend_on_ids']/tree/field[@name='partner_id']" position="after">
<field name="allow_billable" invisible="1"/>
</xpath>
<xpath expr="//field[@name='depend_on_ids']/tree/field[@name='partner_id']" position="attributes">
<attribute name="attrs">{'column_invisible': [('parent.allow_billable', '=', False)], 'invisible': [('allow_billable', '=', False)]}</attribute>
</xpath>
</field>
</record>
<record id="project_task_view_tree_main_base" model="ir.ui.view">
<field name="name">project.task.main.tree.inherit</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.project_task_view_tree_main_base"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="allow_billable" invisible="1"/>
</field>
<field name="partner_id" position="attributes">
<attribute name="attrs">{'invisible': [('allow_billable', '=', False)]}</attribute>
<attribute name="invisible">context.get('hide_partner')</attribute>
</field>
</field>
</record>
@@ -109,6 +142,12 @@
<field name="partner_id" position="after">
<field name="sale_order_id" filter_domain="['|', ('sale_order_id', 'ilike', self), ('sale_line_id', 'ilike', self)]"/>
</field>
<field name="partner_id" position="attributes">
<attribute name="invisible">context.get('hide_partner')</attribute>
</field>
<filter name="customer" position="attributes">
<attribute name="invisible">context.get('hide_partner')</attribute>
</filter>
</field>
</record>
+3 -1
View File
@@ -163,12 +163,14 @@ class Project(models.Model):
@api.depends('sale_line_employee_ids.sale_line_id', 'sale_line_id')
def _compute_partner_id(self):
for project in self:
billable_projects = self.filtered('allow_billable')
for project in billable_projects:
if project.partner_id:
continue
if project.allow_billable and project.allow_timesheets and project.pricing_type != 'task_rate':
sol = project.sale_line_id or project.sale_line_employee_ids.sale_line_id[:1]
project.partner_id = sol.order_partner_id
super(Project, self - billable_projects)._compute_partner_id()
@api.depends('partner_id')
def _compute_sale_line_id(self):
@@ -269,7 +269,7 @@ class TestProjectBilling(TestCommonSaleTimesheet):
self.assertFalse(subtask.allow_billable, "Subtask in non billable project should be non billable too")
self.assertFalse(subtask.project_id.allow_billable, "The subtask project is non billable even if the subtask is")
self.assertEqual(subtask.partner_id, subtask.parent_id.partner_id, "Subtask should have the same customer as the one from their mother")
self.assertFalse(subtask.partner_id, "Subtask in non billable project should not have a customer")
# log timesheet on subtask
timesheet2 = Timesheet.create({
@@ -367,7 +367,7 @@ class TestProjectBilling(TestCommonSaleTimesheet):
'project_id': self.project_subtask.id,
})
self.assertEqual(subtask.partner_id, subtask.parent_id.partner_id, "Subtask should have the same customer as the one from their mother")
self.assertFalse(subtask.partner_id, "Subtask should not have the customer if it's project is not billable")
# log timesheet on subtask
timesheet2 = Timesheet.create({
@@ -134,7 +134,7 @@
<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"/>
</xpath>
<xpath expr="//field[@name='partner_phone']" position="after">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="pricing_type" invisible="1" groups="hr_timesheet.group_hr_timesheet_user"/>
</xpath>
<xpath expr="//field[@name='timesheet_ids']" position="attributes">