[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:
@@ -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
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 & 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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user