Skip to content

Commit 8ae6ec1

Browse files
kthe-odoodeneuvillemppr-odoo
committed
[IMP] project,*: added project templates mechanism
* = hr_timesheet, sale_project, project_purchase, project_mrp, project_stock, sale_timesheet, website_project, analytic, project_timesheet_holidays In this PR we add Project templates which will contribute towards template system in the Project app. Reusable project templates to work. task-4700744 Co-authored-by: Maxime de Neuville <[email protected]> Co-authored-by: Prakash Prajapati <[email protected]>
1 parent f7b359d commit 8ae6ec1

File tree

63 files changed

+1077
-150
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1077
-150
lines changed

addons/hr_timesheet/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def _uninstall_hook(env):
3636
def update_action_window(xmlid):
3737
act_window = env.ref(xmlid, raise_if_not_found=False)
3838
if act_window and act_window.domain and 'is_internal_project' in act_window.domain:
39-
act_window.domain = []
39+
act_window.domain = [("is_template", "=", False)]
4040

4141
update_action_window('project.open_view_project_all')
4242
update_action_window('project.open_view_project_all_group_stage')

addons/hr_timesheet/models/hr_timesheet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def default_get(self, field_list):
4545
return result
4646

4747
def _domain_project_id(self):
48-
domain = [('allow_timesheets', '=', True)]
48+
domain = [('allow_timesheets', '=', True), ('is_template', '=', False)]
4949
if not self.env.user.has_group('hr_timesheet.group_timesheet_manager'):
5050
return expression.AND([domain,
5151
['|', ('privacy_visibility', '!=', 'followers'), ('message_partner_ids', 'in', [self.env.user.partner_id.id])]

addons/hr_timesheet/models/project_project.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def _search_is_project_overtime(self, operator, value):
100100
@api.constrains('allow_timesheets', 'account_id')
101101
def _check_allow_timesheet(self):
102102
for project in self:
103-
if project.allow_timesheets and not project.account_id:
103+
if project.allow_timesheets and not project.account_id and not project.is_template:
104104
project_plan, _other_plans = self.env['account.analytic.plan']._get_all_plans()
105105
raise ValidationError(_(
106106
"To use the timesheets feature, you need an analytic account for your project. Please set one up in the plan '%(plan_name)s' or turn off the timesheets feature.",
@@ -135,12 +135,12 @@ def create(self, vals_list):
135135
""" Create an analytic account if project allow timesheet and don't provide one
136136
Note: create it before calling super() to avoid raising the ValidationError from _check_allow_timesheet
137137
"""
138-
defaults = self.default_get(['allow_timesheets', 'account_id'])
138+
defaults = self.default_get(['allow_timesheets', 'account_id', 'is_template'])
139139
analytic_accounts_vals = [
140140
vals for vals in vals_list
141141
if (
142142
vals.get('allow_timesheets', defaults.get('allow_timesheets')) and
143-
not vals.get('account_id', defaults.get('account_id'))
143+
not vals.get('account_id', defaults.get('account_id')) and not vals.get('is_template', defaults.get('is_template'))
144144
)
145145
]
146146

@@ -153,7 +153,7 @@ def create(self, vals_list):
153153
def write(self, values):
154154
# create the AA for project still allowing timesheet
155155
if values.get('allow_timesheets') and not values.get('account_id'):
156-
project_wo_account = self.filtered(lambda project: not project.account_id)
156+
project_wo_account = self.filtered(lambda project: not project.account_id and not project.is_template)
157157
if project_wo_account:
158158
project_wo_account._create_analytic_account()
159159
return super().write(values)
@@ -171,7 +171,7 @@ def _compute_display_name(self):
171171

172172
@api.model
173173
def _init_data_analytic_account(self):
174-
self.search([('account_id', '=', False), ('allow_timesheets', '=', True)])._create_analytic_account()
174+
self.search([('account_id', '=', False), ('allow_timesheets', '=', True), ('is_template', '=', False)])._create_analytic_account()
175175

176176
@api.ondelete(at_uninstall=False)
177177
def _unlink_except_contains_entries(self):
@@ -283,3 +283,8 @@ def action_view_tasks(self):
283283
action = super().action_view_tasks()
284284
action['context']['allow_timesheets'] = self.allow_timesheets
285285
return action
286+
287+
def action_create_from_template(self, values=None):
288+
project = super().action_create_from_template(values)
289+
project._create_analytic_account()
290+
return project

addons/hr_timesheet/models/project_task.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
class ProjectTask(models.Model):
3030
_inherit = "project.task"
3131

32-
project_id = fields.Many2one(domain="['|', ('company_id', '=', False), ('company_id', '=?', company_id), ('is_internal_project', '=', False)]")
32+
project_id = fields.Many2one(domain="['|', ('company_id', '=', False), ('company_id', '=?', company_id), ('is_internal_project', '=', False), ('is_template', 'in', [is_template, False])]")
3333
analytic_account_active = fields.Boolean("Active Analytic Account", related='project_id.analytic_account_active', export_string_translation=False)
3434
allow_timesheets = fields.Boolean(
3535
"Allow timesheets",

addons/hr_timesheet/models/res_company.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ def _default_timesheet_encode_uom_id(self):
2424
timesheet_encode_uom_id = fields.Many2one('uom.uom', string="Timesheet Encoding Unit",
2525
default=_default_timesheet_encode_uom_id)
2626
internal_project_id = fields.Many2one(
27-
'project.project', string="Internal Project",
28-
help="Default project value for timesheet generated from time off type.")
27+
"project.project", string="Internal Project",
28+
domain=[("is_template", "=", False)],
29+
help="Default project value for timesheet generated from time off type.",
30+
)
2931

3032
@api.constrains('internal_project_id')
3133
def _check_internal_project_id_company(self):

addons/hr_timesheet/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
from . import test_project_task_quick_create
66
from . import test_portal_timesheet
77
from . import test_project_project
8+
from . import test_project_template
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from odoo.tests import TransactionCase, tagged
2+
3+
4+
@tagged('post_install', '-at_install')
5+
class TestProjectProjectTemplate(TransactionCase):
6+
7+
def test_template_created_not_having_analytic_account(self):
8+
template_project, normal_project = self.env["project.project"].create(
9+
[
10+
{
11+
"name": "Template Project",
12+
"is_template": True,
13+
"allow_timesheets": True,
14+
},
15+
{
16+
"name": "Normal Project",
17+
"is_template": False,
18+
"allow_timesheets": True,
19+
},
20+
]
21+
)
22+
23+
self.assertFalse(template_project.account_id, "The template project shouldn't have analytic account")
24+
self.assertTrue(normal_project.account_id, "A normal project should have a analytic account")
25+
26+
def test_project_created_from_template_to_have_analytic_account(self):
27+
template_project = self.env['project.project'].create({
28+
"name": "Template Project",
29+
"is_template": True,
30+
"allow_timesheets": True,
31+
})
32+
33+
new_project = template_project.action_create_from_template()
34+
self.assertTrue(new_project.account_id, "A project created from template should have a analytic account")

addons/hr_timesheet/views/project_project_views.xml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<field name="arch" type="xml">
2424
<xpath expr="//header" position="after">
2525
<field name="analytic_account_active" invisible="1"/>
26-
<t name="timesheet_error" invisible="not allow_timesheets">
26+
<t name="timesheet_error" invisible="not allow_timesheets or is_template">
2727
<div class="alert alert-warning mb-1 text-center" role="alert" colspan="2" invisible="not account_id or analytic_account_active">
2828
You cannot log timesheets on this project since it is linked to an inactive analytic account.<br/>
2929
Please switch to another account, or reactivate the current one to timesheet on the project.
@@ -77,7 +77,7 @@
7777
<field name="allocated_hours"/>
7878
</xpath>
7979
<xpath expr="//div[@name='card_menu_view']" position="inside">
80-
<div role="menuitem" t-if="record.allow_timesheets.raw_value" groups="hr_timesheet.group_hr_timesheet_user">
80+
<div role="menuitem" t-if="record.allow_timesheets.raw_value and !record.is_template.raw_value" groups="hr_timesheet.group_hr_timesheet_user">
8181
<a name="action_project_timesheets" type="object">Timesheets</a>
8282
</div>
8383
</xpath>
@@ -86,7 +86,7 @@
8686
<t t-set="badgeColor" t-value="'border-danger'" t-if="record.remaining_hours.raw_value &lt; 0"/>
8787
<t t-set="title" t-if="record.encode_uom_in_days.raw_value">Days Remaining</t>
8888
<t t-set="title" t-else="">Time Remaining</t>
89-
<div t-if="record.allow_timesheets.raw_value and record.allocated_hours.raw_value &gt; 0"
89+
<div t-if="!record.is_template.raw_value and record.allow_timesheets.raw_value and record.allocated_hours.raw_value &gt; 0"
9090
t-attf-class="me-1 ms-1 bg-transparent badge border {{ badgeColor }}" t-att-title="title" groups="hr_timesheet.group_hr_timesheet_user">
9191
<field name="remaining_hours" widget="timesheet_uom" class="p-0"/>
9292
</div>
@@ -100,17 +100,28 @@
100100
<field name="inherit_id" ref="project.view_project_project_filter"/>
101101
<field name="arch" type="xml">
102102
<filter name="late_milestones" position="before">
103-
<filter string="Timesheets &gt;100%" name="projects_in_overtime" domain="[('is_project_overtime', '=', True)]" groups="project.group_project_manager"/>
103+
<filter string="Timesheets &gt;100%" name="projects_in_overtime" domain="[('is_project_overtime', '=', True)]" groups="project.group_project_manager" invisible="context.get('default_is_template')"/>
104104
</filter>
105105
</field>
106106
</record>
107107

108108
<record id="project.open_view_project_all" model="ir.actions.act_window">
109-
<field name="domain">[('is_internal_project', '=', False)]</field>
109+
<field name="domain">[('is_internal_project', '=', False), ("is_template", "=", False)]</field>
110110
</record>
111111

112112
<record id="project.open_view_project_all_group_stage" model="ir.actions.act_window">
113-
<field name="domain">[('is_internal_project', '=', False)]</field>
113+
<field name="domain">[('is_internal_project', '=', False), ("is_template", "=", False)]</field>
114+
</record>
115+
116+
<!-- Project Template -->
117+
<record id="project_templates_view_list_inherit_timesheet" model="ir.ui.view">
118+
<field name="name">project.project.template.list.inherit.timesheet</field>
119+
<field name="model">project.project</field>
120+
<field name="inherit_id" ref="project.project_templates_view_list"/>
121+
<field name="arch" type="xml">
122+
<field name="effective_hours" position="replace"/>
123+
<field name="remaining_hours" position="replace"/>
124+
</field>
114125
</record>
115126
</data>
116127
</odoo>

0 commit comments

Comments
 (0)