Newer
Older
# Copyright 2019 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
from odoo.exceptions import ValidationError
_inherit = "sale.order"
project_id_linked = fields.Many2one('project.project', string='Projet associé',
domain=[('allow_timesheets', '=', True), ('active', '=', True)])
project_name_to_create = fields.Char("Nom du projet")
project_tracking = fields.Selection([
('new', 'Créer un nouveau projet'),
('link', 'Associer un projet existant'),
])
no_create_task = fields.Boolean(string="Ne pas créer les tâches")
taux_horaire = fields.Integer(
'Taux horaire',
default=lambda self: self.env.user.company_id.taux_horaire)
@api.onchange("partner_id", "order_line")
def _project_name_to_create(self):
so_line_new_project_with_tasks = self.mapped('order_line').filtered(
lambda sol: sol.is_service and sol.product_id.service_tracking == 'task_new_project')
if not so_line_new_project_with_tasks:
self.project_tracking = False
elif so_line_new_project_with_tasks and self.partner_id:
if not self.project_id_linked:
self.project_tracking = "new"
if not self.project_name_to_create:
self.project_name_to_create = self.partner_id.name + str(' - ')
else:
self.project_tracking = "link"
@api.onchange("project_tracking")
def _erase_project_tracking(self):
if self.project_tracking == "link":
self.project_name_to_create = None
elif self.project_tracking == "new":
self.project_id_linked = None
if not self.project_name_to_create:
so_line_new_project_with_tasks = self.mapped('order_line').filtered(
lambda sol: sol.is_service and sol.product_id.service_tracking == 'task_new_project')
if so_line_new_project_with_tasks and self.partner_id:
self.project_name_to_create = self.partner_id.name + str(' - ')
def action_confirm(self):
# on différencie so_line_new_project (dans _timesheet_service_generation) de so_line_new_project_with_tasks
# car on laisse le fonctionnement natif pour les articles où on crée le projet sans les tâches
so_line_new_project_with_tasks = self.mapped('order_line').filtered(
lambda sol: sol.is_service and sol.product_id.service_tracking == 'task_new_project')
if (so_line_new_project_with_tasks and not self.project_id_linked) and (
so_line_new_project_with_tasks and not self.project_name_to_create):
raise ValidationError(
"Pas de projet associé à ce devis : merci de choisir un projet ou d'en créer un nouveau")
elif so_line_new_project_with_tasks and self.project_id_linked and self.project_name_to_create:
raise ValidationError(
"Vous ne pouvez pas créer un nouveau projet et associer à un projet existant en même temps : merci de "
"vérifier le projet que vous voulez associer à ce devis") # cas normalement géré par le xml
elif not so_line_new_project_with_tasks and (self.project_id_linked or self.project_name_to_create):
raise ValidationError(
"Vous ne pouvez pas associer de projet à ce devis : tous les articles sont déjà liés à des projets "
"existants") # cas normalement géré par le xml
else:
return super(SaleOrder, self).action_confirm()
class SaleOrderLine(models.Model):
def _convert_qty_company_hours(self):
""" Reprise de la fonction native pour changer le mode de calcul des heures planifiées dans timesheet
"""
company_time_uom_id = self.env.user.company_id.project_time_mode_id
taux_horaire = self.order_id.taux_horaire
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
if self.product_uom.id != company_time_uom_id.id \
and self.product_uom.category_id.id == company_time_uom_id.category_id.id:
planned_hours = super(SaleOrderLine, self)._convert_qty_company_hours
else:
planned_hours = (self.product_uom_qty * self.price_unit) / taux_horaire
return planned_hours
@api.multi
def _timesheet_create_task(self, project):
""" Pour gérer le stage_id et le nom des tâches pour les projets maintenance et support
"""
task = super(SaleOrderLine, self)._timesheet_create_task(project)
if self.product_id.service_tracking == 'task_global_project':
stage = self.product_id.project_linked_stage_id
client_name = self.order_id.partner_id.name
if stage:
task.stage_id = stage
task_name = str(client_name + " - " + stage.name)
task.write({'name': task_name})
else:
task.write({'name': client_name})
return task
@api.multi
def _timesheet_create_project(self, name_project):
""" Genère le projet de la même manière mais lui donne le nom choisi
"""
project = super(SaleOrderLine, self)._timesheet_create_project()
project.name = name_project # on réécrit le nom du projet
return project
@api.multi
def _timesheet_service_generation(self):
""" Réécriture de la fonction native de manière quasi-identique
mais qui permet d'associer chaque ligne du devis à un projet
précédemment créé ou choisi
"""
so_line_task_global_project = self.filtered(
lambda sol: sol.is_service and sol.product_id.service_tracking == 'task_global_project')
so_line_new_project = self.filtered(
lambda sol: sol.is_service and sol.product_id.service_tracking in ['project_only', 'task_new_project'])
# search so lines from SO of current so lines having their project generated, in order to check if the
# current one can create its own project, or reuse the one of its order.
map_so_project = {}
if so_line_new_project:
order_ids = self.mapped('order_id').ids
so_lines_with_project = self.search([('order_id', 'in', order_ids), ('project_id', '!=', False), (
'product_id.service_tracking', 'in', ['project_only', 'task_new_project']),
('product_id.project_template_id', '=', False)])
map_so_project = {sol.order_id.id: sol.project_id for sol in so_lines_with_project}
so_lines_with_project_templates = self.search([('order_id', 'in', order_ids), ('project_id', '!=', False), (
'product_id.service_tracking', 'in', ['project_only', 'task_new_project']),
('product_id.project_template_id', '!=', False)])
map_so_project_templates = {(sol.order_id.id, sol.product_id.project_template_id.id): sol.project_id for sol
in so_lines_with_project_templates}
# search the global project of current SO lines, in which create their task
map_sol_project = {}
if so_line_task_global_project:
map_sol_project = {sol.id: sol.product_id.with_context(force_company=sol.company_id.id).project_id for sol
in so_line_task_global_project}
def _can_create_project(sol):
if not sol.project_id:
if sol.product_id.project_template_id:
return (sol.order_id.id, sol.product_id.project_template_id.id) not in map_so_project_templates
elif sol.order_id.id not in map_so_project:
return True
return False
# task_global_project: create task in global project
for so_line in so_line_task_global_project:
no_create_task = so_line.order_id.no_create_task # on récupère la variable de classe créée
if not so_line.task_id and not no_create_task: # rajoute une conditon pour vérifier si on crée les tâches
if map_sol_project.get(so_line.id):
so_line._timesheet_create_task(project=map_sol_project[so_line.id])
# project_only, task_new_project: use the project linked to the sale_order. May be create a task too.
for so_line in so_line_new_project:
project = so_line.project_id
# on récupère les variables de classes créées
no_create_task = so_line.order_id.no_create_task
project_linked = so_line.order_id.project_id_linked
new_project_name = so_line.order_id.project_name_to_create
if not project and _can_create_project(so_line):
if project_linked: # si on a choisi un projet ..
project = project_linked # .. on associe la so_line au projet associé au devis
so_line.write({'project_id': project_linked.id})
elif new_project_name: # si on a choisi un nom de nouveau projet, on crée ce projet
project = so_line._timesheet_create_project(new_project_name)
elif so_line.product_id.service_tracking == 'project_only': # on est dans le cas "project_only"
project = super(SaleOrderLine, self)._timesheet_create_project()
if so_line.product_id.project_template_id:
map_so_project_templates[(so_line.order_id.id, so_line.product_id.project_template_id.id)] = project
else:
map_so_project[so_line.order_id.id] = project
elif not project:
# Attach subsequent SO lines to the created project
so_line.project_id = (
map_so_project_templates.get((so_line.order_id.id, so_line.product_id.project_template_id.id))
or map_so_project.get(so_line.order_id.id)
if so_line.product_id.service_tracking == 'task_new_project':
if not project:
if so_line.product_id.project_template_id:
project = map_so_project_templates[
(so_line.order_id.id, so_line.product_id.project_template_id.id)]
else:
project = map_so_project[so_line.order_id.id]
if not so_line.task_id and not no_create_task: # rajoute conditon pour vérifier si on crée les tâches
so_line._timesheet_create_task(project=project)