summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorKevin Baptiste <kba@odoo.com>2019-11-28 10:52:44 +0000
committerYannick Tivisse <yti@odoo.com>2020-01-31 15:35:54 +0000
commit2ac06038dd08be863c27cc7d1d7aec9a70ec2105 (patch)
tree443ed8ae03a06f554cee594ada23a019c423a050 /addons
parentb5902c3fa23c444565eaf0b1aa6d8f956ce14453 (diff)
[IMP] project, industry_fsm: Improve subtasks and configuration UX
Purpose ======= In Project, the configuration has to be done twice: in the settings of the app and on each project. During the onboarding, the user won't understand that a feature has to be enabled at both places and will think that the system just doesn't work. If the user enabled a feature, he most likely intends on using it for most of his projects. Therefore, having to do a double configuration for each new project is annoying. By the way, sub-tasks is the only global feature (it can't be enabled or disabled by project), so we are breaking it down by project to remain consistent with the rest. Specification ============= 1/ Enable subtasks per project Before this commit, sub-tasks were a global feature (i.e. once enabled they were enabled for all projects). This commit gives the ability to enable/disable sub-tasks per project. 2/ Make project_id on planning required 3/ Change default rating status on new projects 4/ Default value for allow_subtasks 5/ Enable timesheets on existing projects 6/ Hide Total Hours if subtasks are disabled 7/ Create project in the company context. Otherwise some records generation could use a default company which is not the company for which we want to create a project. 8/ Default value for allow_forecast, is_fsm 9/ Change reference to timesheet product 10/ Fix tracback on industry_fsm_report. Before this commit, there was a traceback when enabling "Worksheets" (industry_fsm_report) after "Time and Material" (industry_fsm_sale). 11/ Convert some onchange method into computed editable stored fields closes odoo/odoo#41218 Taskid: 2146474 Related: odoo/enterprise#6998 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com> Co-authored-by: Yannick Tivisse <yti@odoo.com>
Diffstat (limited to 'addons')
-rw-r--r--addons/hr_timesheet/models/project.py12
-rw-r--r--addons/hr_timesheet/tests/test_timesheet.py1
-rw-r--r--addons/hr_timesheet/views/project_views.xml11
-rw-r--r--addons/project/models/project.py24
-rw-r--r--addons/project/models/res_config_settings.py14
-rw-r--r--addons/project/tests/test_multicompany.py6
-rw-r--r--addons/project/views/project_views.xml35
-rw-r--r--addons/project_timesheet_holidays/models/res_company.py1
-rw-r--r--addons/website_sale/tests/test_website_sale_pricelist.py1
9 files changed, 82 insertions, 23 deletions
diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py
index 329c242e0d3..c4404ae1592 100644
--- a/addons/hr_timesheet/models/project.py
+++ b/addons/hr_timesheet/models/project.py
@@ -17,7 +17,9 @@ class Project(models.Model):
('partner_id', '=?', partner_id),
]"""
)
- allow_timesheet_timer = fields.Boolean('Timesheet Timer', default=False, help="Use a timer to record timesheets on tasks")
+ allow_timesheet_timer = fields.Boolean(
+ 'Timesheet Timer', compute='_compute_allow_timesheet_timer', readonly=False, store=True,
+ default=True, help="Use a timer to record timesheets on tasks")
_sql_constraints = [
('timer_only_when_timesheet', "CHECK((allow_timesheets = 'f' AND allow_timesheet_timer = 'f') OR (allow_timesheets = 't'))", 'The timesheet timer can only be activated on project allowing timesheets.'),
@@ -34,10 +36,10 @@ class Project(models.Model):
if project.allow_timesheets and not project.analytic_account_id:
raise ValidationError(_('To allow timesheet, your project %s should have an analytic account set.' % (project.name,)))
- @api.onchange('allow_timesheets')
- def _onchange_allow_timesheets(self):
- if not self.allow_timesheets:
- self.allow_timesheet_timer = False
+ @api.depends('allow_timesheets')
+ def _compute_allow_timesheet_timer(self):
+ for project in self:
+ project.allow_timesheet_timer = project.allow_timesheets
@api.model
def name_create(self, name):
diff --git a/addons/hr_timesheet/tests/test_timesheet.py b/addons/hr_timesheet/tests/test_timesheet.py
index 8fd1bde9d0e..eb7e71fddd7 100644
--- a/addons/hr_timesheet/tests/test_timesheet.py
+++ b/addons/hr_timesheet/tests/test_timesheet.py
@@ -170,6 +170,7 @@ class TestTimesheet(TestCommonTimesheet):
non_tracked_project = self.env['project.project'].create({
'name': 'Project without timesheet',
'allow_timesheets': False,
+ 'allow_timesheet_timer': False,
'partner_id': self.partner.id,
})
self.assertFalse(non_tracked_project.analytic_account_id, "A non time-tracked project shouldn't generate an analytic account")
diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml
index a50c6dfb58b..3d1530d7378 100644
--- a/addons/hr_timesheet/views/project_views.xml
+++ b/addons/hr_timesheet/views/project_views.xml
@@ -101,12 +101,13 @@
<xpath expr="//notebook/page[@name='description_page']" position="after">
<field name="analytic_account_active" invisible="1"/>
<field name="allow_timesheets" invisible="1"/>
+ <field name="allow_subtasks" invisible="1"/>
<page string="Timesheets" id="timesheets_tab" attrs="{'invisible': [('allow_timesheets', '=', False)]}">
<group>
<group>
<field name="planned_hours" widget="float_time"/>
- <label for="subtask_planned_hours" groups="project.group_subtask_project" attrs="{'invisible': [('subtask_count', '=', 0)]}"/>
- <div class="o_row" groups="project.group_subtask_project" attrs="{'invisible': [('subtask_count', '=', 0)]}">
+ <label for="subtask_planned_hours" groups="project.group_subtask_project" attrs="{'invisible': ['|', ('allow_subtasks', '=', False), ('subtask_count', '=', 0)]}"/>
+ <div class="o_row" groups="project.group_subtask_project" attrs="{'invisible': ['|', ('allow_subtasks', '=', False), ('subtask_count', '=', 0)]}">
<field name="subtask_planned_hours" widget="float_time"/><span> planned hours</span>
</div>
</group>
@@ -178,9 +179,9 @@
<group attrs="{'invisible': [('analytic_account_active', '=', False)]}">
<group class="oe_subtotal_footer oe_right" name="project_hours">
<field name="effective_hours" widget="float_time" />
- <button name="action_view_subtask_timesheet" string="Sub-tasks Hours Spent:" type="object" class="oe_inline oe_link pl-0" attrs="{'invisible' : [('subtask_effective_hours', '=', 0.0)]}"/>
- <field name="subtask_effective_hours" widget="float_time" attrs="{'invisible' : [('subtask_effective_hours', '=', 0.0)]}" nolabel="1"/>
- <field name="total_hours_spent" widget="float_time" class="oe_subtotal_footer_separator" attrs="{'invisible' : [('subtask_effective_hours', '=', 0.0)]}" />
+ <button name="action_view_subtask_timesheet" string="Sub-tasks Hours Spent:" type="object" class="oe_inline oe_link pl-0" attrs="{'invisible' : ['|', ('allow_subtasks', '=', False), ('subtask_effective_hours', '=', 0.0)]}"/>
+ <field name="subtask_effective_hours" widget="float_time" attrs="{'invisible' : ['|', ('allow_subtasks', '=', False), ('subtask_effective_hours', '=', 0.0)]}" nolabel="1"/>
+ <field name="total_hours_spent" widget="float_time" class="oe_subtotal_footer_separator" attrs="{'invisible' : ['|', ('allow_subtasks', '=', False), ('subtask_effective_hours', '=', 0.0)]}" />
<field name="remaining_hours" widget="float_time" class="oe_subtotal_footer_separator" attrs="{'invisible' : [('planned_hours', '=', 0.0)]}"/>
</group>
</group>
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 565bd736b95..b5e6039a5a3 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -155,6 +155,11 @@ class Project(models.Model):
def _get_default_favorite_user_ids(self):
return [(6, 0, [self.env.uid])]
+ def _get_default_rating_status(self):
+ if self.user_has_groups('project.group_project_rating'):
+ return 'stage'
+ return 'no'
+
name = fields.Char("Name", index=True, required=True, tracking=True)
active = fields.Boolean(default=True,
help="If the active field is set to False, it will allow you to hide the project without removing it.")
@@ -212,13 +217,18 @@ class Project(models.Model):
date = fields.Date(string='Expiration Date', index=True, tracking=True)
subtask_project_id = fields.Many2one('project.project', string='Sub-task Project', ondelete="restrict",
help="Project in which sub-tasks of the current project will be created. It can be the current project itself.")
+ allow_subtasks = fields.Boolean('Sub-tasks', compute='_compute_allow_subtasks')
# rating fields
rating_request_deadline = fields.Datetime(compute='_compute_rating_request_deadline', store=True)
- rating_status = fields.Selection([('stage', 'Rating when changing stage'), ('periodic', 'Periodical Rating'), ('no','No rating')], 'Customer Ratings', help="How to get customer feedback?\n"
- "- Rating when changing stage: an email will be sent when a task is pulled in another stage.\n"
- "- Periodical Rating: email will be sent periodically.\n\n"
- "Don't forget to set up the mail templates on the stages for which you want to get the customer's feedbacks.", default="no", required=True)
+ rating_status = fields.Selection([
+ ('stage', 'Rating when changing stage'),
+ ('periodic', 'Periodical Rating'),
+ ('no', 'No rating')], string='Customer Ratings', default=_get_default_rating_status, required=True,
+ help="How to get customer feedback?\n"
+ "- Rating when changing stage: an email will be sent when a task is pulled in another stage.\n"
+ "- Periodical Rating: email will be sent periodically.\n\n"
+ "Don't forget to set up the mail templates on the stages for which you want to get the customer's feedbacks.")
rating_status_period = fields.Selection([
('daily', 'Daily'), ('weekly', 'Weekly'), ('bimonthly', 'Twice a Month'),
('monthly', 'Once a Month'), ('quarterly', 'Quarterly'), ('yearly', 'Yearly')
@@ -230,6 +240,11 @@ class Project(models.Model):
('project_date_greater', 'check(date >= date_start)', 'Error! project start-date must be lower than project end-date.')
]
+ def _compute_allow_subtasks(self):
+ subtask_enabled = self.user_has_groups('project.group_subtask_project')
+ for project in self:
+ project.allow_subtasks = subtask_enabled
+
@api.depends('allowed_internal_user_ids', 'allowed_portal_user_ids')
def _compute_allowed_users(self):
for project in self:
@@ -540,6 +555,7 @@ class Task(models.Model):
parent_id = fields.Many2one('project.task', string='Parent Task', index=True)
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks", context={'active_test': False})
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project', readonly=True)
+ allow_subtasks = fields.Boolean('project.project', related="project_id.allow_subtasks", readonly=True)
subtask_count = fields.Integer("Sub-task count", compute='_compute_subtask_count')
email_from = fields.Char(string='Email', help="These people will receive email.", index=True,
compute='_compute_email_from', store="True", readonly=False)
diff --git a/addons/project/models/res_config_settings.py b/addons/project/models/res_config_settings.py
index be36ad46d67..439e09d7705 100644
--- a/addons/project/models/res_config_settings.py
+++ b/addons/project/models/res_config_settings.py
@@ -11,3 +11,17 @@ class ResConfigSettings(models.TransientModel):
module_hr_timesheet = fields.Boolean(string="Task Logs")
group_subtask_project = fields.Boolean("Sub-tasks", implied_group="project.group_subtask_project")
group_project_rating = fields.Boolean("Use Rating on Project", implied_group='project.group_project_rating')
+
+ def _get_subtasks_projects_domain(self):
+ return []
+
+ def execute(self):
+ res = super(ResConfigSettings, self).execute()
+ if self.group_project_rating:
+ # Change the rating status on existing projects from 'no' to 'stage'
+ self.env['project.project'].search([('rating_status', '=', 'no')]).write({
+ 'rating_status': 'stage'})
+ if self.group_subtask_project:
+ domain = self._get_subtasks_projects_domain()
+ self.env['project.project'].search(domain).write({'allow_subtasks': True})
+ return res
diff --git a/addons/project/tests/test_multicompany.py b/addons/project/tests/test_multicompany.py
index 414738802e2..3c807ae3e29 100644
--- a/addons/project/tests/test_multicompany.py
+++ b/addons/project/tests/test_multicompany.py
@@ -243,10 +243,10 @@ class TestMultiCompanyProject(TestMultiCompanyCommon):
self.assertEqual(task.company_id, self.company_b, "The company of the task should be the one from its project.")
with Form(self.task_1) as task_form:
- task_form.project_id = self.env['project.project'] # False is not accepted by the form
+ task_form.project_id = self.project_company_a
task = task_form.save()
- self.assertEqual(task.company_id, self.company_b, "Making a task orphan does not change its company.")
+ self.assertEqual(task.company_id, self.company_a, "Moving a task should change its company.")
def test_create_subtask(self):
with self.sudo('employee-a'):
@@ -272,7 +272,7 @@ class TestMultiCompanyProject(TestMultiCompanyCommon):
def test_cross_subtask_project(self):
# set up default subtask project
- self.project_company_a.write({'subtask_project_id': self.project_company_b.id})
+ self.project_company_a.write({'allow_subtasks': True, 'subtask_project_id': self.project_company_b.id})
with self.sudo('employee-a'):
with self.allow_companies([self.company_a.id, self.company_b.id]):
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index 9346f0de6e4..77e59bd9d45 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -79,6 +79,14 @@
<field name="res_model">project.task</field>
<field name="view_mode">tree,kanban,form,calendar,pivot,graph,activity</field>
<field name="search_view_id" ref="project.view_task_search_form"/>
+ <field name="help" type="html">
+ <p class="o_view_nocontent_smiling_face">
+ Click <i>Create</i> to start a new task.
+ </p><p>
+ To get things done, use activities and status on tasks.<br/>
+ Chat in real time or by email to collaborate efficiently.
+ </p>
+ </field>
</record>
<!-- Project -->
@@ -136,7 +144,6 @@
<field name="allowed_internal_user_ids" widget="many2many_tags" attrs="{'invisible': [('privacy_visibility', '!=', 'followers')]}"/>
<field name="allowed_portal_user_ids" widget="many2many_tags" attrs="{'invisible': [('privacy_visibility', '!=', 'portal')]}"/>
- <field name="subtask_project_id" groups="project.group_subtask_project"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group name="extra_settings">
@@ -165,6 +172,21 @@
</div>
</div>
</div>
+ <div class="col-lg-6 o_setting_box" id="subtask_settings" groups="project.group_subtask_project">
+ <div class="o_setting_left_pane">
+ <field name="allow_subtasks" />
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="allow_subtasks" />
+ <div class="text-muted">
+ Split your tasks to organize your work into sub-milestones
+ </div>
+ <div class="mt8" attrs="{'invisible': [('allow_subtasks', '=', False)]}">
+ <label for="subtask_project_id" />
+ <field name="subtask_project_id" />
+ </div>
+ </div>
+ </div>
</div>
<group name="misc">
<group string="Time Scheduling">
@@ -448,6 +470,7 @@
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<form string="Task" class="o_form_project_tasks">
+ <field name="allow_subtasks" invisible="1" />
<header>
<button name="action_assign_to_me" string="Assign to Me" type="object" class="oe_highlight"
attrs="{'invisible' : [('user_id', '!=', False)]}"/>
@@ -456,7 +479,7 @@
<sheet string="Task">
<div class="oe_button_box" name="button_box">
<button name="action_subtask" type="object" class="oe_stat_button" icon="fa-tasks"
- attrs="{'invisible' : [('id', '=', False)]}" context="{'default_user_id': user_id, 'default_parent_id': id, 'default_project_id': subtask_project_id}" groups="project.group_subtask_project">
+ attrs="{'invisible' : ['|', ('allow_subtasks', '=', False), ('id', '=', False)]}" context="{'default_user_id': user_id, 'default_parent_id': id, 'default_project_id': subtask_project_id}">
<field string="Sub-tasks" name="subtask_count" widget="statinfo"/>
</button>
<button name="%(rating_rating_action_task)d" type="action" attrs="{'invisible': [('rating_count', '=', 0)]}" class="oe_stat_button" icon="fa-smile-o" groups="project.group_project_rating">
@@ -482,14 +505,14 @@
<field name="legend_done" invisible="1"/>
</group>
<group>
- <field name="project_id" domain="[('active', '=', True), ('company_id', '=', company_id)]"/>
+ <field name="project_id" required="1" domain="[('active', '=', True), ('company_id', '=', company_id)]"/>
<field name="user_id"
class="o_task_user_field"
options='{"no_open": True}'/>
<field
name="parent_id"
domain="[('parent_id', '=', False)]"
- groups="project.group_subtask_project"
+ attrs="{'invisible' : [('allow_subtasks', '=', False)]}"
/>
</group>
<!-- YTI FIXME: Clean these fake groups -->
@@ -561,7 +584,7 @@
<group>
<field name="name" string = "Task Title"/>
<field name="user_id" options="{'no_open': True,'no_create': True}"/>
- <field name="project_id" invisible="context.get('all_task', 1)"/>
+ <field name="project_id" required="1" invisible="context.get('all_task', 1)"/>
<field name="company_id" invisible="1"/>
<field name="parent_id" invisible="1"/>
</group>
@@ -602,7 +625,7 @@
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title"><field name="name"/></strong>
- <span invisible="context.get('default_project_id', False) or context.get('fsm_mode', False)"><br/><field name="project_id"/></span>
+ <span invisible="context.get('default_project_id', False) or context.get('fsm_mode', False)"><br/><field name="project_id" required="1"/></span>
<br />
<t t-if="record.partner_id.value">
<span t-if="!record.partner_is_company.raw_value">
diff --git a/addons/project_timesheet_holidays/models/res_company.py b/addons/project_timesheet_holidays/models/res_company.py
index c6cd0495d5e..091284afe59 100644
--- a/addons/project_timesheet_holidays/models/res_company.py
+++ b/addons/project_timesheet_holidays/models/res_company.py
@@ -36,6 +36,7 @@ class Company(models.Model):
def _create_leave_project_task(self):
for company in self:
+ company = company.with_company(company)
if not company.leave_timesheet_project_id:
project = self.env['project.project'].sudo().create({
'name': _('Internal Project'),
diff --git a/addons/website_sale/tests/test_website_sale_pricelist.py b/addons/website_sale/tests/test_website_sale_pricelist.py
index 3782e51244f..d0087b2b1b0 100644
--- a/addons/website_sale/tests/test_website_sale_pricelist.py
+++ b/addons/website_sale/tests/test_website_sale_pricelist.py
@@ -383,6 +383,7 @@ class TestWebsitePriceListHttp(HttpCaseWithUserPortal):
reading that `property_product_pricelist`.
'''
test_company = self.env['res.company'].create({'name': 'Test Company'})
+ test_company.flush()
self.env['product.pricelist'].create({
'name': 'Backend Pricelist For "Test Company"',
'website_id': False,