Commit e71d522eb435d6f4323b251b8962f70494d78ec0

Authored by Thibault Ardhuin
1 parent b5550200

Sprint 1 Release:

- Transition de d3js à Hightchartjs
- Modification des affectations des agents
- Modification des projets
app/db_mgr.py
1 1 from app.models import db, Period, Project, Category, ProjectStatus
  2 +from flask import flash
2 3  
3 4 # TODO: make this configurable, and choose another place to use it,
4 5 # in 'routes.py' maybe
... ... @@ -108,15 +109,19 @@ def projects():
108 109 # we build the labels.name list of the intersection of current category's labels with project's labels
109 110 # Comprehensive shortcut is:
110 111 # labels = [_cl.label.name for _cl in category.labels if _cl.label in [_pl.label for _pl in project.labels]]
111   - #
  112 +
112 113 category_labels = [_cl.label for _cl in category.category_labels]
113 114 project_labels = [_pl.label for _pl in project.project_labels]
114 115 intersection_labels = list(set.intersection(set(project_labels), set(category_labels)))
115 116 labels = [label.name for label in intersection_labels]
116 117 project_row.append(labels)
  118 +
  119 + if not isinstance(project.name, type(None)):
  120 + all_projects.append(project_row)
  121 +
117 122 # then add total charge
118 123 project_row.append(_pc[1])
119   - all_projects.append(project_row)
  124 +
120 125 return all_projects
121 126  
122 127  
... ... @@ -402,7 +407,8 @@ def charges_by_agent_tabled(agent_id):
402 407 p2.name as project_name,
403 408 s.name as service_name,
404 409 c2.name as capacity_name,
405   - c.charge_rate as charge_rate
  410 + c.charge_rate as charge_rate,
  411 + c.id as charge_id
406 412 from charge as c
407 413 join period p on c.period_id = p.id
408 414 join project p2 on c.project_id = p2.id
... ... @@ -440,6 +446,15 @@ def charges_by_agent(agent_id):
440 446 all_charges.append([p, 0])
441 447 return all_charges
442 448  
  449 +def complete_charges_by_agent(agent_id):
  450 + sql_cmd = f"SELECT * from charge WHERE agent_id = {agent_id}"
  451 + request = db.session.execute(sql_cmd)
  452 + all_charges = []
  453 + for item in request.fetchall():
  454 + all_charges.append(item)
  455 + return all_charges
  456 +
  457 +
443 458  
444 459 def count_agents_by_status(period_id):
445 460 sql_txt = f"""
... ...
app/main/routes.py
1   -
2 1 import xlsxwriter
3 2 import io
4 3  
5 4 from flask import render_template, make_response, current_app, redirect, url_for, request, flash, abort, send_file, \
6 5 jsonify
7 6 from flask_login import login_required, current_user
8   -
9 7 from . import bp
10 8  
11 9 from app.models import Agent, Project, Service, Capacity, Period, db, Company, AgentGrade, AgentStatus, AgentBap, \
... ... @@ -16,7 +14,6 @@ from app.auth.routes import role_required
16 14 updated_message = "mis à jour"
17 15 added_message = "ajouté"
18 16  
19   -
20 17 def form_manager(object_class,
21 18 object_id_key,
22 19 object_id,
... ... @@ -24,6 +21,7 @@ def form_manager(object_class,
24 21 form_struct,
25 22 urlfor_exist_dict,
26 23 urlfor_done_dict):
  24 +
27 25 """
28 26 Generic form manager, holds etheir POST or GET method.
29 27 Allows to modify existing object, or create new one.
... ... @@ -39,13 +37,25 @@ def form_manager(object_class,
39 37 :param urlfor_done_dict: Where do redirect when done.
40 38 :return:
41 39 """
  40 +
42 41 if request.method == 'GET':
  42 +
43 43 if object_id:
44 44 this_object = object_class.query.get(int(object_id))
45 45 else:
46 46 this_object = object_class()
  47 +
47 48 object_struct = this_object.to_struct()
48   - return render_template(form_template, object_struct=object_struct,
  49 + name_object = object_struct['name']
  50 +
  51 + if type(name_object) != str:
  52 + flash(f">{name_object}< .", 'warning')
  53 + else:
  54 + #flash(f"> attention pas d'informations renseignées < .", 'warning')
  55 + pass
  56 +
  57 + return render_template(form_template,
  58 + object_struct=object_struct,
49 59 **form_struct)
50 60 elif request.method == 'POST':
51 61 try:
... ... @@ -58,9 +68,10 @@ def form_manager(object_class,
58 68 done_string = updated_message + "."
59 69 else:
60 70 # check name doesnt exist yet
61   - existing_object = object_class.query.filter(object_class.name == request.form.get('name')).one_or_none()
  71 + existing_object = object_class.query.filter(
  72 + object_class.name == request.form.get('name')).one_or_none()
62 73 if existing_object:
63   - flash(f">{existing_object.name}< existe déjà.", 'warning')
  74 + flash(f">le projet {existing_object.name}< existe déjà.", 'warning')
64 75 return redirect(url_for(**urlfor_exist_dict))
65 76 # or create from scratch
66 77 this_object = object_class()
... ... @@ -72,8 +83,64 @@ def form_manager(object_class,
72 83 # we're done
73 84 flash(f"{this_object.name} (#{this_object.id}) " + done_string)
74 85 urlfor_done_dict[object_id_key] = this_object.id
  86 +
75 87 return redirect(url_for(**urlfor_done_dict))
76 88  
  89 +def form_delete_project(object_class,
  90 + form_template,
  91 + object_id_key,
  92 + object_id,
  93 + form_struct,
  94 + form_template_two):
  95 +
  96 + list_project = form_struct['projects']
  97 + List_project = []
  98 +
  99 + if request.method == 'GET':
  100 + if object_id:
  101 + this_object = object_class.query.get(int(object_id))
  102 + else:
  103 + this_object = object_class()
  104 + object_struct = this_object.to_struct()
  105 +
  106 + for j in enumerate(list_project):
  107 + List_project.append(j[1][1])
  108 +
  109 + return render_template(form_template,
  110 + object_struct=object_struct,
  111 + **form_struct)
  112 +
  113 + elif request.method == 'POST':
  114 +
  115 + name_project = request.form.get('name')
  116 +
  117 + for j in enumerate(list_project):
  118 + List_project.append(j[1][1])
  119 +
  120 + if name_project in List_project :
  121 +
  122 + existing_object = object_class.query.filter(object_class.name == name_project).one_or_none()
  123 +
  124 + flash(f">projet {existing_object.name}< supprimé")
  125 +
  126 + object_struct = existing_object.to_struct()
  127 +
  128 + db.session.delete(existing_object)
  129 + db.session.commit()
  130 +
  131 + return render_template(form_template_two,
  132 + object_struct=object_struct)
  133 + else:
  134 +
  135 + flash(f">projet {name_project}< déjà supprimé.", 'warning')
  136 +
  137 + this_object = object_class()
  138 + _object_struct = this_object.to_struct()
  139 +
  140 + return render_template(form_template,
  141 + object_struct=_object_struct,
  142 + **form_struct)
  143 +
77 144  
78 145 @bp.before_request
79 146 def site_login():
... ... @@ -85,24 +152,20 @@ def site_login():
85 152 # no such config, juste ignore
86 153 pass
87 154  
88   -
89 155 @bp.before_request
90 156 def catch_all_route():
91 157 current_app.logger.debug(f"{request.method} {request.path}")
92 158  
93   -
94 159 @bp.route('/')
95 160 def index():
96 161 return render_template('index.html', subtitle="Page d'accueil")
97 162  
98   -
99 163 @bp.route('/total_charge')
100 164 @role_required('service')
101 165 def total_charge():
102 166 return render_template('total_charge.html', subtitle="Charge des agents",
103 167 total_charges=db_mgr.charges_for_all_agents())
104 168  
105   -
106 169 @bp.route('/services')
107 170 @role_required('service')
108 171 def services():
... ... @@ -113,7 +176,6 @@ def services():
113 176 return render_template('services.html', subtitle="Liste des services ({})".format(num_services),
114 177 services=all_services)
115 178  
116   -
117 179 @bp.route('/service/<service_id>')
118 180 @role_required('service')
119 181 def service(service_id):
... ... @@ -121,7 +183,6 @@ def service(service_id):
121 183 return render_template('service.html', service=this_service.to_struct(),
122 184 subtitle=f"{this_service.name}")
123 185  
124   -
125 186 @bp.route('/service/create', methods=('POST', 'GET'))
126 187 @bp.route('/service/<service_id>/edit', methods=('POST', 'GET'))
127 188 @role_required('service')
... ... @@ -135,7 +196,6 @@ def service_edit(service_id=None):
135 196 urlfor_exist_dict={'endpoint': 'main.service_edit'},
136 197 urlfor_done_dict={'endpoint': 'main.service', 'service_id': None})
137 198  
138   -
139 199 @bp.route('/service/<service_id>/delete', methods=('POST', 'GET'))
140 200 @role_required('admin')
141 201 def service_delete(service_id=None):
... ... @@ -144,7 +204,6 @@ def service_delete(service_id=None):
144 204 # flash(f" {this_service.name} effacé")
145 205 return redirect(url_for('main.service', service_id=this_service.id))
146 206  
147   -
148 207 @bp.route('/projects')
149 208 @role_required('project')
150 209 def projects():
... ... @@ -155,7 +214,6 @@ def projects():
155 214 return render_template('projects.html', subtitle="Liste des projets ({})".format(num_projects),
156 215 projects=all_projects)
157 216  
158   -
159 217 @bp.route('/project/<int:project_id>')
160 218 @role_required('project')
161 219 def project(project_id):
... ... @@ -167,7 +225,6 @@ def project(project_id):
167 225 charges=charges_table,
168 226 subtitle="{}".format(this_project.name))
169 227  
170   -
171 228 @bp.route('/project/create', methods=('POST', 'GET'))
172 229 @bp.route('/project/<int:project_id>/edit', methods=('POST', 'GET'))
173 230 @role_required('service')
... ... @@ -182,15 +239,20 @@ def project_edit(project_id=None):
182 239 urlfor_exist_dict={'endpoint': 'main.project_edit'},
183 240 urlfor_done_dict={'endpoint': 'main.project', 'project_id': None})
184 241  
185   -
186   -@bp.route('/project/<project_id>/delete', methods=('POST', 'GET'))
  242 +@bp.route('/project/delete', methods=('POST', 'GET'))
  243 +@bp.route('/project/<int:project_id>/delete', methods=('POST', 'GET'))
187 244 @role_required('admin')
188 245 def project_delete(project_id=None):
189   - this_project = Project.query.get(int(project_id))
190   - flash("Suppression du Projet pas encore implémentée", 'warning')
191   - # flash(f" {this_project.name} effacé")
192   - return redirect(url_for('main.project', project_id=this_project.id))
193 246  
  247 + _form_struct = {'projects': db_mgr.projects()}
  248 + _form_template_delete='project_delete_form.html',
  249 + _form_template_res='project_delete_res.html',
  250 + return form_delete_project(object_class=Project,
  251 + object_id_key='project_id',
  252 + object_id=project_id,
  253 + form_template=_form_template_delete,
  254 + form_struct=_form_struct,
  255 + form_template_two=_form_template_res)
194 256  
195 257 @bp.route('/projects/stats')
196 258 @role_required('project')
... ... @@ -200,18 +262,16 @@ def projects_stats():
200 262 return render_template('projects_stats.html', subtitle="Statistiques des projets ({})".format(num_projects),
201 263 categories=this_categories)
202 264  
203   -
204 265 @bp.route('/agents')
205 266 @role_required('project')
206 267 def agents():
207 268 # get agents list
208   - all_agents = db_mgr.agents()
  269 + all_agents = db_mgr.agents()
209 270 num_agents = len(all_agents)
210 271 # pass to template
211 272 return render_template('agents.html', subtitle="Liste des agents ({})".format(num_agents),
212 273 agents=all_agents)
213 274  
214   -
215 275 @bp.route('/agent/<agent_id>')
216 276 @role_required('agent')
217 277 def agent(agent_id):
... ... @@ -223,7 +283,6 @@ def agent(agent_id):
223 283 charges=charges_table,
224 284 subtitle=f"{this_agent.fullname}")
225 285  
226   -
227 286 @bp.route('/agent/create', methods=('POST', 'GET'))
228 287 @bp.route('/agent/<agent_id>/edit', methods=('POST', 'GET'))
229 288 @role_required('service')
... ... @@ -240,7 +299,6 @@ def agent_edit(agent_id=None):
240 299 urlfor_exist_dict={'endpoint': 'main.agent_edit'},
241 300 urlfor_done_dict={'endpoint': 'main.agent', 'agent_id': None})
242 301  
243   -
244 302 @bp.route('/agent/<agent_id>/delete', methods=('POST', 'GET'))
245 303 @role_required('admin')
246 304 def agent_delete(agent_id=None):
... ... @@ -249,7 +307,6 @@ def agent_delete(agent_id=None):
249 307 # flash(f"Agent {this_agent.name} effacé")
250 308 return redirect(url_for('main.agent', agent_id=this_agent.id))
251 309  
252   -
253 310 @bp.route('/agents/stats')
254 311 @role_required('project')
255 312 def agents_stats():
... ... @@ -258,16 +315,15 @@ def agents_stats():
258 315 return render_template('agents_stats.html', subtitle="Statistiques des agents ({})".format(num_agents),
259 316 periods=all_periods)
260 317  
261   -
262 318 @bp.route('/agents/responsabilities')
263 319 @role_required('agent')
264 320 def responsabilities():
265 321 num_resp = len(AgentResponsability.query.all())
266   - all_resp = AgentResponsability.query.order_by(AgentResponsability.name).all()
  322 + all_resp = AgentResponsability.query.order_by(
  323 + AgentResponsability.name).all()
267 324 return render_template('responsabilities.html', subtitle="Liste des Responsabilités ({})".format(num_resp),
268 325 responsabilities=all_resp)
269 326  
270   -
271 327 @bp.route('/responsability/<responsability_id>')
272 328 @role_required('agent')
273 329 def responsability(responsability_id):
... ... @@ -275,7 +331,6 @@ def responsability(responsability_id):
275 331 return render_template('responsability.html', subtitle=f"{this_responsability.name}",
276 332 responsability=this_responsability)
277 333  
278   -
279 334 @bp.route('/responsability/create', methods=('POST', 'GET'))
280 335 @bp.route('/responsability/<responsability_id>/edit', methods=('POST', 'GET'))
281 336 @role_required('admin')
... ... @@ -285,10 +340,10 @@ def responsability_edit(responsability_id=None):
285 340 object_id=responsability_id,
286 341 form_template='responsability_form.html',
287 342 form_struct={},
288   - urlfor_exist_dict={'endpoint': 'main.responsability_edit'},
  343 + urlfor_exist_dict={
  344 + 'endpoint': 'main.responsability_edit'},
289 345 urlfor_done_dict={'endpoint': 'main.responsability', 'responsability_id': None})
290 346  
291   -
292 347 @bp.route('/responsability/<responsability_id>/delete', methods=('POST', 'GET'))
293 348 @role_required('admin')
294 349 def responsability_delete(responsability_id=None):
... ... @@ -297,7 +352,6 @@ def responsability_delete(responsability_id=None):
297 352 # flash(f"Statut de Projet {this_status.name} effacé")
298 353 return redirect(url_for('main.responsability', responsability_id=this_responsability.id))
299 354  
300   -
301 355 @bp.route('/employments')
302 356 @login_required
303 357 def employments():
... ... @@ -308,7 +362,6 @@ def employments():
308 362 return render_template('employments.html', subtitle="Liste des Emplois Types ({})".format(num_employments),
309 363 employments=all_employments)
310 364  
311   -
312 365 @bp.route('/skills')
313 366 @login_required
314 367 def skills():
... ... @@ -319,7 +372,6 @@ def skills():
319 372 return render_template('skills.html', subtitle="Liste des compétences ({})".format(num_skills),
320 373 skills=all_skills)
321 374  
322   -
323 375 @bp.route('/skill/<skill_id>')
324 376 @role_required('agent')
325 377 def skill(skill_id):
... ... @@ -327,7 +379,6 @@ def skill(skill_id):
327 379 return render_template('skill.html', subtitle=f"{this_skill.name}",
328 380 skill=this_skill)
329 381  
330   -
331 382 @bp.route('/skill/create', methods=('POST', 'GET'))
332 383 @bp.route('/skill/<skill_id>/edit', methods=('POST', 'GET'))
333 384 @role_required('admin')
... ... @@ -340,7 +391,6 @@ def skill_edit(skill_id=None):
340 391 urlfor_exist_dict={'endpoint': 'main.skill_edit'},
341 392 urlfor_done_dict={'endpoint': 'main.skill', 'skill_id': None})
342 393  
343   -
344 394 @bp.route('/skill/<skill_id>/delete', methods=('POST', 'GET'))
345 395 @role_required('admin')
346 396 def skill_delete(skill_id=None):
... ... @@ -348,18 +398,17 @@ def skill_delete(skill_id=None):
348 398 flash("Suppression de la Compétence pas encore implémentée", 'warning')
349 399 return redirect(url_for('main.skill', skill_id=this_skill.id))
350 400  
351   -
352 401 @bp.route('/project_statuses')
353 402 @login_required
354 403 def project_statuses():
355 404 # get project_statuses list
356   - all_project_statuses = ProjectStatus.query.order_by(ProjectStatus.name).all()
  405 + all_project_statuses = ProjectStatus.query.order_by(
  406 + ProjectStatus.name).all()
357 407 num_project_statuses = len(all_project_statuses)
358 408 # pass to template
359 409 return render_template('project_statuses.html', subtitle="Statuts de projets ({})".format(num_project_statuses),
360 410 project_statuses=all_project_statuses)
361 411  
362   -
363 412 @bp.route('/project_status/<status_id>')
364 413 @role_required('admin')
365 414 def project_status(status_id):
... ... @@ -369,7 +418,6 @@ def project_status(status_id):
369 418 return render_template('project_status.html', subtitle=f"{this_status.name}",
370 419 status=this_status)
371 420  
372   -
373 421 @bp.route('/project_status/create', methods=('POST', 'GET'))
374 422 @bp.route('/project_status/<status_id>/edit', methods=('POST', 'GET'))
375 423 @role_required('admin')
... ... @@ -379,10 +427,10 @@ def project_status_edit(status_id=None):
379 427 object_id=status_id,
380 428 form_template='project_status_form.html',
381 429 form_struct={},
382   - urlfor_exist_dict={'endpoint': 'main.project_status_edit'},
  430 + urlfor_exist_dict={
  431 + 'endpoint': 'main.project_status_edit'},
383 432 urlfor_done_dict={'endpoint': 'main.project_status', 'status_id': None})
384 433  
385   -
386 434 @bp.route('/project_status/<status_id>/delete', methods=('POST', 'GET'))
387 435 @role_required('admin')
388 436 def project_status_delete(status_id=None):
... ... @@ -391,7 +439,6 @@ def project_status_delete(status_id=None):
391 439 # flash(f"Statut de Projet {this_status.name} effacé")
392 440 return redirect(url_for('main.project_status', status_id=this_status.id))
393 441  
394   -
395 442 @bp.route('/capacities')
396 443 @login_required
397 444 def capacities():
... ... @@ -402,7 +449,6 @@ def capacities():
402 449 return render_template('capacities.html', subtitle="Liste des fonctions ({})".format(num_capacities),
403 450 capacities=all_capacities)
404 451  
405   -
406 452 @bp.route('/capacity/<capacity_id>')
407 453 @role_required('admin')
408 454 def capacity(capacity_id):
... ... @@ -412,7 +458,6 @@ def capacity(capacity_id):
412 458 return render_template('capacity.html', subtitle=f"{this_capacity.name}",
413 459 capacity=this_capacity)
414 460  
415   -
416 461 @bp.route('/capacity/create', methods=('POST', 'GET'))
417 462 @bp.route('/capacity/<capacity_id>/edit', methods=('POST', 'GET'))
418 463 @role_required('admin')
... ... @@ -425,7 +470,6 @@ def capacity_edit(capacity_id=None):
425 470 urlfor_exist_dict={'endpoint': 'main.capacity_edit'},
426 471 urlfor_done_dict={'endpoint': 'main.capacity', 'capacity_id': None})
427 472  
428   -
429 473 @bp.route('/capacity/<capacity_id>/delete', methods=('POST', 'GET'))
430 474 @role_required('admin')
431 475 def capacity_delete(capacity_id=None):
... ... @@ -434,7 +478,6 @@ def capacity_delete(capacity_id=None):
434 478 # flash(f"Label {this_label.name} effacé")
435 479 return redirect(url_for('main.capacities'))
436 480  
437   -
438 481 @bp.route('/labels')
439 482 @login_required
440 483 def labels():
... ... @@ -445,7 +488,6 @@ def labels():
445 488 return render_template('labels.html', subtitle="Liste des labels ({})".format(num_labels),
446 489 labels=all_labels)
447 490  
448   -
449 491 @bp.route('/label/<label_id>')
450 492 @role_required('admin')
451 493 def label(label_id):
... ... @@ -455,7 +497,6 @@ def label(label_id):
455 497 return render_template('label.html', subtitle=f"{this_label.name}",
456 498 label=this_label)
457 499  
458   -
459 500 @bp.route('/label/create', methods=('POST', 'GET'))
460 501 @bp.route('/label/<label_id>/edit', methods=('POST', 'GET'))
461 502 @role_required('admin')
... ... @@ -469,7 +510,6 @@ def label_edit(label_id=None):
469 510 urlfor_exist_dict={'endpoint': 'main.label_edit'},
470 511 urlfor_done_dict={'endpoint': 'main.label', 'label_id': None})
471 512  
472   -
473 513 @bp.route('/label/<label_id>/delete', methods=('POST', 'GET'))
474 514 @role_required('admin')
475 515 def label_delete(label_id=None):
... ... @@ -478,7 +518,6 @@ def label_delete(label_id=None):
478 518 # flash(f"Label {this_label.name} effacé")
479 519 return redirect(url_for('main.labels'))
480 520  
481   -
482 521 @bp.route('/categories')
483 522 @login_required
484 523 def categories():
... ... @@ -489,7 +528,6 @@ def categories():
489 528 return render_template('categories.html', subtitle="Liste des categories ({})".format(num_categories),
490 529 categories=all_categories)
491 530  
492   -
493 531 @bp.route('/category/<category_id>')
494 532 @role_required('admin')
495 533 def category(category_id):
... ... @@ -499,7 +537,6 @@ def category(category_id):
499 537 return render_template('category.html', subtitle=f"{this_category.name}",
500 538 category=this_category)
501 539  
502   -
503 540 @bp.route('/category/create', methods=('POST', 'GET'))
504 541 @bp.route('/category/<category_id>/edit', methods=('POST', 'GET'))
505 542 @role_required('admin')
... ... @@ -513,7 +550,6 @@ def category_edit(category_id=None):
513 550 urlfor_exist_dict={'endpoint': 'main.category_edit'},
514 551 urlfor_done_dict={'endpoint': 'main.category', 'category_id': None})
515 552  
516   -
517 553 @bp.route('/category/<category_id>/delete', methods=('POST', 'GET'))
518 554 @role_required('admin')
519 555 def category_delete(category_id=None):
... ... @@ -522,7 +558,6 @@ def category_delete(category_id=None):
522 558 # flash(f"Category {this_category.name} effacé")
523 559 return redirect(url_for('main.categories'))
524 560  
525   -
526 561 @bp.route('/periods')
527 562 @login_required
528 563 def periods():
... ... @@ -533,7 +568,6 @@ def periods():
533 568 return render_template('periods.html', subtitle="Liste des périodes ({})".format(num_periods),
534 569 periods=all_periods)
535 570  
536   -
537 571 @bp.route('/charge/add', methods=('POST', 'GET'))
538 572 @role_required('service')
539 573 def charge_add():
... ... @@ -568,7 +602,6 @@ def charge_add():
568 602 flash(f"Nouvelle charge pour l'agent {this_agent.fullname}")
569 603 return redirect(url_for('main.agent', agent_id=this_agent.id))
570 604  
571   -
572 605 # - - - - - - - - - - - - - - - - - - - - REST API - - - - - - - - - - - - - - - - - - - -
573 606  
574 607 def array_to_any(array, format='csv'):
... ... @@ -624,7 +657,8 @@ def array_to_jsonhs(categorised_array):
624 657 project_charges = []
625 658 for charge_line in categorised_array[1:-1]:
626 659 period = charge_line[0]
627   - project_charge = charge_line[project_index]
  660 + if (project_index < len(charge_line)):
  661 + project_charge = charge_line[project_index]
628 662 project_charges.append([period, float(project_charge)])
629 663 # add project_charges only if non zero
630 664 total_project_charge = 0
... ... @@ -671,7 +705,6 @@ def array_to_xls(array):
671 705  
672 706 return file_to_return
673 707  
674   -
675 708 def array_to_csv(array, sep=','):
676 709 csv_table = []
677 710 for line in array:
... ... @@ -682,37 +715,123 @@ def array_to_csv(array, sep=&#39;,&#39;):
682 715 resp.headers['Content-Type'] = 'text/plain;charset=utf8'
683 716 return resp
684 717  
685   -
686 718 @bp.route('/charge/project/<project_id>/<sort_key>/<any_format>')
687 719 @bp.route('/charge/project/<project_id>/<sort_key>')
688 720 @role_required('project')
689 721 def rest_charge_project(project_id, sort_key, any_format='csv'):
690 722 return array_to_any(db_mgr.charges_by_project_stacked(project_id, sort_key), any_format)
691 723  
692   -
693 724 @bp.route('/charge/agent/<agent_id>/<any_format>')
694 725 @bp.route('/charge/agent/<agent_id>')
695 726 @role_required('service')
696 727 def rest_charge_agent(agent_id, any_format='csv'):
697 728 return array_to_any(db_mgr.charges_by_agent_stacked(agent_id), any_format)
698 729  
  730 +@bp.route('/charge/edit/<charge_id>')
  731 +def edit_charge_page(charge_id):
  732 + this_charge = Charge.query.get(charge_id)
699 733  
700   -@bp.route('/charge/labels/<category_id>/<any_format>')
701   -@bp.route('/charge/labels/<category_id>')
702   -@role_required('project')
  734 + if (this_charge):
  735 + this_agents = Agent.query.order_by(Agent.firstname).all()
  736 + this_projects = Project.query.order_by(Project.name).all()
  737 + this_services = Service.query.order_by(Service.name).all()
  738 + this_periods = Period.query.order_by(Period.id).all()
  739 + this_capacities = Capacity.query.order_by(Capacity.name).all()
  740 +
  741 + # Get current params for this charge
  742 + current_agent = None
  743 + current_project = None
  744 + current_service = None
  745 + current_period = None
  746 + current_capacity = None
  747 + for a in this_agents:
  748 + if (a.id == this_charge.agent_id):
  749 + current_agent = a
  750 + break
  751 +
  752 + for p in this_projects:
  753 + if (p.id == this_charge.project_id):
  754 + current_project = p
  755 + break
  756 +
  757 + for s in this_services:
  758 + if (s.id == this_charge.service_id):
  759 + current_service = s
  760 + break
  761 +
  762 + for p in this_periods:
  763 + if (p.id == this_charge.period_id):
  764 + current_period = p
  765 + break
  766 +
  767 + for c in this_capacities:
  768 + if (c.id == this_charge.capacity_id):
  769 + current_capacity = c
  770 + break
  771 +
  772 + return render_template('edit_charge.html', subtitle="Modifier une charge",
  773 + agents=this_agents,
  774 + projects=this_projects,
  775 + services=this_services,
  776 + periods=this_periods,
  777 + capacities=this_capacities,
  778 + charge=this_charge,
  779 + cur_agent=current_agent,
  780 + cur_project=current_project,
  781 + cur_service=current_service,
  782 + cur_period=current_period,
  783 + cur_capacity=current_capacity)
  784 + else:
  785 + flash(f"Charge avec l'id {charge_id} n'existe pas")
  786 + return redirect(url_for('main.agents'))
  787 +
  788 +
  789 +@bp.route('/charge/update', methods=['PUT', 'POST'])
  790 +def update_charge_values():
  791 + # TODO: set role required
  792 + this_charge = Charge.query.get(request.form.get('charge_id'))
  793 + if (this_charge == None):
  794 + return 'This charge doesn\'t exist. (charge_id unknown)', 400
  795 +
  796 + this_charge.from_request(request)
  797 + db.session.commit()
  798 + flash(f"Charge {request.form.get('charge_id')} a été mis à jour")
  799 + return redirect(url_for('main.edit_charge_page', charge_id=request.form.get('charge_id')))
  800 + # return 'Charge has been updated', 200
  801 +
  802 +
  803 +@bp.route('/charge/delete', methods=['DELETE', 'POST'])
  804 +def remove_charge_values():
  805 + # TODO: set role required
  806 +
  807 + this_charge = Charge.query.get(request.form.get('charge_id'))
  808 + if (this_charge == None):
  809 + return 'This charge doesn\'t exist. (charge_id unknown)', 400
  810 +
  811 + db.session.delete(this_charge)
  812 + db.session.commit()
  813 +
  814 + flash(f"charge {request.form.get('charge_id')} supprimée")
  815 + # redirect on agent
  816 + return redirect(url_for('main.agent', agent_id=request.form.get('agent_id')))
  817 +
  818 +
  819 +@ bp.route('/charge/labels/<category_id>/<any_format>')
  820 +@ bp.route('/charge/labels/<category_id>')
  821 +@ role_required('project')
703 822 def rest_labels_stats(category_id, any_format='csv'):
704 823 return array_to_any(db_mgr.charges_by_labels_stacked(category_id), any_format)
705 824  
706 825  
707   -@bp.route('/charge/projects/<any_format>')
708   -@bp.route('/charge/projects')
709   -@role_required('project')
  826 +@ bp.route('/charge/projects/<any_format>')
  827 +@ bp.route('/charge/projects')
  828 +@ role_required('project')
710 829 def rest_projects_stats(any_format='csv'):
711 830 return array_to_any(db_mgr.charges_for_projects_stacked(), any_format)
712 831  
713 832  
714   -@bp.route('/count/agents/by_status/<period_id>/<any_format>')
715   -@bp.route('/count/agents/by_status/<period_id>')
716   -@role_required('project')
  833 +@ bp.route('/count/agents/by_status/<period_id>/<any_format>')
  834 +@ bp.route('/count/agents/by_status/<period_id>')
  835 +@ role_required('project')
717 836 def rest_agents_status_count(period_id, any_format='csv'):
718 837 return array_to_any(db_mgr.count_agents_by_status(period_id), any_format)
... ...
app/main/templates/agent.html
1 1 {% extends "base_page.html" %}
2 2  
3 3 {% block more_heads %}
4   - <link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
5   - type="text/css"/>
  4 +<link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
  5 + type="text/css" />
6 6 {% endblock %}
7 7  
8 8 {% block content %}
9 9  
10   - <!-- Invisible span to definte wich ul and a in the navbar are actived -->
11   - <span id="nav_actived" style="display: none">agent,agents_list</span>
  10 +<!-- Invisible span to definte wich ul and a in the navbar are actived -->
  11 +<span id="nav_actived" style="display: none">agent,agents_list</span>
12 12  
13   - <div class="card">
14   - <div class="card-header">
15   - Fiche Agent
16   - </div>
17   - <div class="card-body">
18   - <dl class="row agent">
19   - <dt class="col-sm-2 text-right">ID :</dt>
20   - <dd class="col-sm-10 text-left">{{ agent.id }}</dd>
21   - <dt class="col-sm-2 text-right">Genre :</dt>
22   - <dd class="col-sm-10 text-left"></dd>
23   - <dt class="col-sm-2 text-right">Date de naissance :</dt>
24   - <dd class="col-sm-10 text-left"></dd>
25   - <dt class="col-sm-2 text-right">E-mail :</dt>
26   - <dd class="col-sm-10 text-left"></dd>
27   - {# TODO: put different spacing #}
28   - <dt class="col-sm-2 text-right"></dt>
29   - <dd class="col-sm-10 text-left"></dd>
30   - <dt class="col-sm-2 text-right">Statut :</dt>
31   - <dd class="col-sm-10 text-left">{{ agent.status.name }}</dd>
32   - <dt class="col-sm-2 text-right">Organisme :</dt>
33   - <dd class="col-sm-10 text-left">{{ agent.company.name }}</dd>
34   - <dt class="col-sm-2 text-right">Corps :</dt>
35   - <dd class="col-sm-10 text-left"></dd>
36   - <dt class="col-sm-2 text-right">Grade :</dt>
37   - <dd class="col-sm-10 text-left">{{ agent.grade.name }}</dd>
38   - <dt class="col-sm-2 text-right">BAP :</dt>
39   - <dd class="col-sm-10 text-left">{{ agent.bap.name }}</dd>
40   - {# TODO: puth different spacing #}
41   - <dt class="col-sm-2 text-right"></dt>
42   - <dd class="col-sm-10 text-left"></dd>
43   - <dt class="col-sm-2 text-right">Quotité :</dt>
44   - <dd class="col-sm-10 text-left"></dd>
45   - <dt class="col-sm-2 text-right">Date arrivée :</dt>
46   - <dd class="col-sm-10 text-left"></dd>
47   - <dt class="col-sm-2 text-right">Date départ :</dt>
48   - <dd class="col-sm-10 text-left"></dd>
49   - {# TODO: put different spacing #}
50   - <dt class="col-sm-2 text-right"></dt>
51   - <dd class="col-sm-10 text-left"></dd>
52   - <dt class="col-sm-2 text-right">Commentaires :</dt>
53   - <dd class="col-sm-10 text-left"></dd>
54   - {# TODO: put different spacing #}
55   - <dt class="col-sm-2 text-right"></dt>
56   - <dd class="col-sm-10 text-left"></dd>
57   - <dt class="col-sm-2 text-right">Fiche de poste :</dt>
58   - <dd class="col-sm-10 text-left"></dd>
59   - {# TODO: put different spacing #}
60   - <dt class="col-sm-2 text-right"></dt>
61   - <dd class="col-sm-10 text-left"></dd>
62   - <dt class="col-sm-2 text-right">Compétences :</dt>
63   - <dd class="col-sm-10 text-left"></dd>
64   - <dt class="col-sm-2 text-right">Besoin en formation :</dt>
65   - <dd class="col-sm-10 text-left"></dd>
66   - </dl>
67   - {{ commons.charge_link( url_for('main.charge_add', agent_id=agent.id), 25) }}
68   - {{ commons.edit_link(url_for('main.agent_edit', agent_id=agent.id), 25) }}
69   - {{ commons.delete_link( url_for('main.agent_delete', agent_id=agent.id), 25) }}
70   - </div>
  13 +<div class="card">
  14 + <div class="card-header">
  15 + Fiche Agent
71 16 </div>
  17 + <div class="card-body">
  18 + <dl class="row agent">
  19 + <dt class="col-sm-2 text-right">ID :</dt>
  20 + <dd class="col-sm-10 text-left">{{ agent.id }}</dd>
  21 + <dt class="col-sm-2 text-right">Genre :</dt>
  22 + <dd class="col-sm-10 text-left"></dd>
  23 + <dt class="col-sm-2 text-right">Date de naissance :</dt>
  24 + <dd class="col-sm-10 text-left"></dd>
  25 + <dt class="col-sm-2 text-right">E-mail :</dt>
  26 + <dd class="col-sm-10 text-left"></dd>
  27 + {# TODO: put different spacing #}
  28 + <dt class="col-sm-2 text-right"></dt>
  29 + <dd class="col-sm-10 text-left"></dd>
  30 + <dt class="col-sm-2 text-right">Statut :</dt>
  31 + <dd class="col-sm-10 text-left">{{ agent.status.name }}</dd>
  32 + <dt class="col-sm-2 text-right">Organisme :</dt>
  33 + <dd class="col-sm-10 text-left">{{ agent.company.name }}</dd>
  34 + <dt class="col-sm-2 text-right">Corps :</dt>
  35 + <dd class="col-sm-10 text-left"></dd>
  36 + <dt class="col-sm-2 text-right">Grade :</dt>
  37 + <dd class="col-sm-10 text-left">{{ agent.grade.name }}</dd>
  38 + <dt class="col-sm-2 text-right">BAP :</dt>
  39 + <dd class="col-sm-10 text-left">{{ agent.bap.name }}</dd>
  40 + {# TODO: puth different spacing #}
  41 + <dt class="col-sm-2 text-right"></dt>
  42 + <dd class="col-sm-10 text-left"></dd>
  43 + <dt class="col-sm-2 text-right">Quotité :</dt>
  44 + <dd class="col-sm-10 text-left"></dd>
  45 + <dt class="col-sm-2 text-right">Date arrivée :</dt>
  46 + <dd class="col-sm-10 text-left"></dd>
  47 + <dt class="col-sm-2 text-right">Date départ :</dt>
  48 + <dd class="col-sm-10 text-left"></dd>
  49 + {# TODO: put different spacing #}
  50 + <dt class="col-sm-2 text-right"></dt>
  51 + <dd class="col-sm-10 text-left"></dd>
  52 + <dt class="col-sm-2 text-right">Commentaires :</dt>
  53 + <dd class="col-sm-10 text-left"></dd>
  54 + {# TODO: put different spacing #}
  55 + <dt class="col-sm-2 text-right"></dt>
  56 + <dd class="col-sm-10 text-left"></dd>
  57 + <dt class="col-sm-2 text-right">Fiche de poste :</dt>
  58 + <dd class="col-sm-10 text-left"></dd>
  59 + {# TODO: put different spacing #}
  60 + <dt class="col-sm-2 text-right"></dt>
  61 + <dd class="col-sm-10 text-left"></dd>
  62 + <dt class="col-sm-2 text-right">Compétences :</dt>
  63 + <dd class="col-sm-10 text-left"></dd>
  64 + <dt class="col-sm-2 text-right">Besoin en formation :</dt>
  65 + <dd class="col-sm-10 text-left"></dd>
  66 + </dl>
  67 + {{ commons.charge_link( url_for('main.charge_add', agent_id=agent.id), 25) }}
  68 + {{ commons.edit_link(url_for('main.agent_edit', agent_id=agent.id), 25) }}
  69 + {{ commons.delete_link( url_for('main.agent_delete', agent_id=agent.id), 25) }}
  70 + </div>
  71 +</div>
72 72  
73   - <div class="charge_chart" id="projects_chart"></div>
  73 +<div class="charge_chart" id="projects_chart"></div>
74 74  
75   - <table class="table_datatables table table-hover">
76   - <thead>
  75 +<table class="table_datatables table table-hover">
  76 + <thead>
77 77 <tr>
78 78 {% for header in charges[0] %}
79   - <th>{{ header }}</th>
  79 + <th>{{ header }}</th>
80 80 {% endfor %}
  81 + <th>Edit</th>
81 82 </tr>
82   - </thead>
83   - <tbody>
  83 + </thead>
  84 + <tbody>
84 85 {% for line in charges[1:] %}
85   - <tr>
86   - {% for i in range(line|length) %}
87   - {% if 'Charge' in charges[0][i] %}
88   - {% set charge = line[i] | int %}
89   - <td>{{ charge / 100 }}</td>
90   - {% else %}
91   - <td>{{ line[i] }}</td>
92   - {% endif %}
93   - {% endfor %}
94   - </tr>
  86 + <tr>
  87 + {% for i in range(line|length - 1) %}
  88 + {% if 'Charge' in charges[0][i] %}
  89 + {% set charge = line[i] | int %}
  90 + <td>{{ charge / 100 }}</td>
  91 + {% else %}
  92 + <td>{{ line[i] }}</td>
  93 + {% endif %}
  94 + {% endfor %}
  95 + <td>
  96 + {{ commons.edit_link(url_for('main.edit_charge_page', charge_id=line[-1])) }}
  97 + </td>
  98 + </tr>
95 99 {% endfor %}
96 100  
97   - </tbody>
98   - </table>
  101 + </tbody>
  102 +</table>
99 103 {% endblock %}
100 104  
101 105  
102 106 {% block more_scripts %}
103   - {% include 'hg-includes.html' %}
104   -{# {% include 'd3js-includes.html' %}#}
105   - {% include 'charges-includes.html' %}
106   -
107   - <script>
108   - document.addEventListener("DOMContentLoaded", function () {
109   - var options = {
110   - chart: {
111   - renderTo: 'projects_chart',
112   - type: 'column'
113   - },
114   - title: {
115   - text: 'Charge de l\'agent'
116   - },
117   - subtitle: {
118   - text: '{{ agent.fullname }}'
119   - },
120   - xAxis: {
121   - title: {
122   - text: 'Semestre'
123   - },
124   - type: 'category',
125   - },
126   - yAxis: {
127   - title: {
128   - text: 'Charge (ETP)'
129   - }
130   - },
131   - tooltip: {
132   - pointFormat: '{series.name}: <b>{point.y} ETP </b>'
133   - },
134   - series: [],
135   - plotOptions: {
136   - column: {
137   - stacking: 'normal'
138   - }
139   - }
140   - };
141   - var url = "{{url_for('main.rest_charge_agent', agent_id=agent.id, any_format='json_hs')}}";
142   - $.getJSON(url, function (data) {
143   - console.log(data);
144   - options.series = data;
145   - var chart = new Highcharts.Chart(options);
146   - });
147   - });
  107 +{% include 'd3js-includes.html' %}
  108 +{% include 'charges-includes.html' %}
148 109  
149   - {#build_chart("#projects_chart",#}
150   - {# "{{url_for('main.rest_charge_agent', agent_id=agent.id)}}",#}
151   - {# "{{agent.fullname}}",#}
152   - {# "project");#}
153   - </script>
  110 +<script>
  111 + build_chart("#projects_chart",
  112 + "{{url_for('main.rest_charge_agent', agent_id=agent.id)}}",
  113 + "{{agent.fullname}}",
  114 + "project");
  115 +</script>
154 116  
155 117 -{% endblock %}
  118 +{% endblock %}
156 119 \ No newline at end of file
... ...
app/main/templates/agents_stats.html
1 1 {% extends "base_page.html" %}
2 2 {% block more_heads %}
3   - <link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
4   - type="text/css"/>
  3 +<link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
  4 + type="text/css" />
5 5 {% endblock %}
6 6  
7 7 {% block content %}
8 8  
9   - <!-- Invisible span to definte wich ul and a in the navbar are actived -->
10   - <span id="nav_actived" style="display: none">agent,agents_stats</span>
11   -
12   - <div class="pdc-controls">
13   - <select title="Choisir la période" id="period_id_select" name="period_id_select">
14   - {% for p in periods %}
15   - <option value="{{ p.id }}" {{ "selected" if p.id == 1 }}>{{ p.name }}</option>
16   - {% endfor %}
17   - </select>
18   - <button id='prev_period' title="Période précédente"><span data-feather="chevron-left"></span></button>
19   - <button id='next_period' title="Période suivante"><span data-feather="chevron-right"></span></button>
20   - </div>
  9 +<!-- Invisible span to definte wich ul and a in the navbar are actived -->
  10 +<span id="nav_actived" style="display: none">agent,agents_stats</span>
21 11  
22   - <h3 class="sub-header">Tout le laboratoire</h3>
  12 +<div class="pdc-controls">
  13 + <select title="Choisir la période" id="period_id_select" name="period_id_select">
  14 + {% for p in periods %}
  15 + <option value="{{ p.id }}" {{ "selected" if p.id == 1 }}>{{ p.name }}</option>
  16 + {% endfor %}
  17 + </select>
  18 + <button id='prev_period' title="Période précédente"><span data-feather="chevron-left"></span></button>
  19 + <button id='next_period' title="Période suivante"><span data-feather="chevron-right"></span></button>
  20 +</div>
23 21  
24   - <div class="row">
25   - <div class="col-4">
26   - <div class="charge_chart" id="agents_by_status_chart"></div>
27   - </div>
28   - <div class="col-8">
29   - <div class="charge_chart" id="charge_by_status_chart"></div>
30   - </div>
31   - </div>
  22 +<h3 class="sub-header">Tout le laboratoire</h3>
32 23  
33   - {# {% for c in categories %}#}
34   - {# <h3 class="sub-header">Charge pour la catégorie {{ c.name }}</h3>#}
35   - {# <div class="charge_chart" id="labels_stats_chart_{{ c.id }}"></div>#}
36   - {# {% endfor %}#}
  24 +<div class="row">
  25 + <div class="col-4">
  26 + <div class="charge_chart" id="agents_by_status_chart"></div>
  27 + </div>
  28 + <div class="col-8">
  29 + <div class="charge_chart" id="charge_by_status_chart"></div>
  30 + </div>
  31 +</div>
37 32  
38 33 {% endblock %}
39 34  
40 35 {% block more_scripts %}
41   - {% include 'd3js-includes.html' %}
42   - {% include 'charges-includes.html' %}
43   -
44   - <script>
45   - $('#period_id_select').on('change', function () {
46   - let period_id = $(this).find(':selected').val();
47   - let period_name = $(this).find(':selected').text();
48   - build_all(period_id, period_name);
49   - });
50   - document.getElementById('next_period').onclick = function () {
51   - period_select_update(1);
52   - };
53   - document.getElementById('prev_period').onclick = function () {
54   - period_select_update(-1);
55   - };
56   -
57   - let period_select_update = function (step) {
58   - let select_elemnt = $('#period_id_select');
59   - let selectoptions_length = $('#period_id_select option').length;
60   - let next_step = +select_elemnt.val() + step;
61   - if (next_step <= 0 || next_step > selectoptions_length) {
62   - return;
  36 +{% include 'hg-includes.html' %}
  37 +{% include 'charges-includes.html' %}
  38 +
  39 +<script>
  40 +
  41 + // time period selection
  42 + let period_select_update = function (step) {
  43 + let select_elemnt = $('#period_id_select');
  44 + let selectoptions_length = $('#period_id_select option').length;
  45 + let next_step = +select_elemnt.val() + step;
  46 +
  47 + if (next_step <= 0 || next_step > selectoptions_length) {
  48 + return;
  49 + }
  50 +
  51 + select_elemnt.val(next_step);
  52 + select_elemnt.trigger('change');
  53 + };
  54 +
  55 +
  56 + document.getElementById('next_period').onclick = function () {
  57 + period_select_update(1);
  58 + };
  59 +
  60 + document.getElementById('prev_period').onclick = function () {
  61 + period_select_update(-1);
  62 + };
  63 +
  64 + // event handling
  65 + $('#period_id_select').on('change', function () {
  66 + let period_id = $(this).find(':selected').val();
  67 + let period_name = $(this).find(':selected').text();
  68 + update_chart(period_id, period_name);
  69 + });
  70 +
  71 + let update_chart = function (period_id, period_name) {
  72 + let curr_p_id = $('#period_id_select').val();
  73 + let curr_p_name = $('#period_id_select').find(":selected").text();
  74 +
  75 + let url = "{{url_for('main.rest_agents_status_count', period_id='PERIOD_ID', any_format='json_hs')}}"
  76 + .replace("PERIOD_ID", period_id);
  77 +
  78 + $.getJSON(url, function (data) {
  79 +
  80 + var options = {
  81 + chart: {
  82 + renderTo: 'agents_by_status_chart',
  83 + type: 'pie'
  84 + },
  85 + title: {
  86 + text: 'Charge pour la catégorie'
  87 + },
  88 + subtitle: {
  89 + text: 'subtitle'
  90 + },
  91 + xAxis: {
  92 + title: {
  93 + text: 'Semestre'
  94 + },
  95 + type: 'category',
  96 + },
  97 + yAxis: {
  98 + title: {
  99 + text: 'Charge (ETP)'
63 100 }
64   - select_elemnt.val(next_step);
65   - select_elemnt.trigger('change');
66   - };
67   -
68   - let build_all = function (period_id, period_name) {
69   - let agent_status_url = "{{url_for('main.rest_agents_status_count', period_id='PERIOD_ID')}}"
70   - .replace("PERIOD_ID", period_id);
71   - let height = 300;
72   -
73   - build_pie_chart("#agents_by_status_chart",
74   - height,
75   - agent_status_url,
76   - period_name);
77   -
78   - build_line_chart("#charge_by_status_chart",
79   - height,
80   - agent_status_url,
81   - "Charge des agents par statut");
82   -
  101 + },
  102 + tooltip: {
  103 + pointFormat: '{series.name}: <b>{point.y} ETP </b>'
  104 + },
  105 + series: [],
  106 + plotOptions: {
  107 + column: {
  108 + stacking: 'normal'
  109 + }
  110 + }
83 111 };
84 112  
85   - {#TODO: get current period id from session#}
  113 + options.series = data;
  114 + var chart = new Highcharts.Chart(options);
86 115  
87   - let curr_p_id = $('#period_id_select').val();
88   - let curr_p_name = $('#period_id_select').find(":selected").text();
  116 + });
  117 + };
89 118  
90   - build_all(curr_p_id, curr_p_name);
  119 + document.addEventListener("DOMContentLoaded", function () {
  120 + let period_id = $(this).find(':selected').val();
  121 + let period_name = $(this).find(':selected').text();
  122 + update_chart(period_id, period_name);
  123 + });
91 124  
92   - {# {% for c in categories %}#}
93   - {# build_chart("#labels_stats_chart_{{ c.id }}",#}
94   - {# "{{url_for('main.rest_labels_stats', category_id=c.id)}}",#}
95   - {# "Charge pour la catégorie {{c.name}}",#}
96   - {# "area");#}
97   - {# {% endfor %}#}
98   - </script>
99   -{% endblock %}
  125 +</script>
  126 +{% endblock %}
100 127 \ No newline at end of file
... ...
app/main/templates/edit_charge.html 0 → 100644
... ... @@ -0,0 +1,84 @@
  1 +{% extends "base_page.html" %}
  2 +
  3 +{% block more_heads %}
  4 +<link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
  5 + type="text/css" />
  6 +{% endblock %}
  7 +
  8 +{% block content %}
  9 +
  10 +{% if charge %}
  11 +
  12 +<form id="update_charge_form" class="pdc-form" action="{{url_for('main.update_charge_values')}}" method="POST">
  13 + <div class="form-group">
  14 + <input type="hidden" name="charge_id" value="{{charge.id}}" />
  15 + <label for="agent_id">Nom de l'agent</label>
  16 + <select id="agent_id" name="agent_id" class="form-control">
  17 + <option selected value="{{charge.agent_id}}">{{cur_agent.name}}</option>
  18 + {% for a in agents %}
  19 + <option value="{{ a.id }}">{{ a.namefull }}</option>
  20 + {% endfor %}
  21 + </select>
  22 + </div>
  23 + <div class="form-group">
  24 + <label for="project_id">Choisir le projet</label>
  25 + <select id="project_id" name="project_id" class="form-control">
  26 + <option selected value="{{charge.project_id}}">{{cur_project.name}}</option>
  27 + {% for p in projects %}
  28 + <option value="{{ p.id }}">{{ p.name }}</option>
  29 + {% endfor %}
  30 + </select>
  31 + </div>
  32 + <div class="form-group">
  33 + <label for="service_id">Choisir le service</label>
  34 + <select id="service_id" name="service_id" class="form-control">
  35 + <option selected value="{{charge.service_id}}">{{cur_service.name}}</option>
  36 + {% for s in services %}
  37 + <option value="{{ s.id }}">{{ s.name }}</option>
  38 + {% endfor %}
  39 + </select>
  40 + </div>
  41 + <div class="form-group">
  42 + <label for="capacity_id">Choisir la fonction</label>
  43 + <select id="capacity_id" name="capacity_id" class="form-control">
  44 + <option selected value="{{charge.capacity_id}}">{{cur_capacity.name}}</option>
  45 + {% for c in capacities %}
  46 + <option value="{{ c.id }}">{{ c.name }}</option>
  47 + {% endfor %}
  48 + </select>
  49 + </div>
  50 + <div class="form-group">
  51 + <label for="period_id">Choisir la période</label>
  52 + <select id="period_id" name="period_id" class="form-control">
  53 + <option selected value="{{charge.period_id}}">{{cur_period.name}}</option>
  54 + {% for p in periods %}
  55 + <option value="{{ p.id }}">{{ p.name }}</option>
  56 + {% endfor %}
  57 + </select>
  58 + </div>
  59 + <div class="form-group">
  60 + <label for="charge_rate">Charge (ETP)</label>
  61 + <input class="form-control col-2" id="charge_rate" name="charge_rate" type="text"
  62 + value="{{charge.charge_rate / 100}}">
  63 + </div>
  64 + <input class="pdc-form-submit" type="submit" value="Mettre à jour">
  65 +
  66 +</form>
  67 +
  68 +<br />
  69 +
  70 +<form id="delete_charge_form" class="pdc-form" action="{{url_for('main.remove_charge_values')}}" method="POST"
  71 + onSubmit="return confirm('Etes vous sur de vouloir supprimer cette charge ?');">
  72 + <input class="btn btn-danger" type="submit" value="Supprimer">
  73 + <input type="hidden" name="charge_id" value="{{charge.id}}" />
  74 + <input type="hidden" name="agent_id" value="{{charge.agent_id}}" />
  75 +</form>
  76 +
  77 +
  78 +{% else %}
  79 +
  80 +<p>Charge does not exist. (charge id not found)</p>
  81 +
  82 +{% endif %}
  83 +
  84 +{% endblock %}
0 85 \ No newline at end of file
... ...
app/main/templates/project.html
1 1 {% extends "base_page.html" %}
2 2  
3 3 {% block more_heads %}
4   - <link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
5   - type="text/css"/>
  4 +<link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
  5 + type="text/css" />
6 6 {% endblock %}
7 7  
8 8 {% block content %}
9   - <!-- Invisible span to definte wich ul and a in the navbar are actived -->
10   - <span id="nav_actived" style="display: none">projet,projects_list</span>
  9 +<!-- Invisible span to definte wich ul and a in the navbar are actived -->
  10 +<span id="nav_actived" style="display: none">projet,projects_list</span>
11 11  
12   - <div class="card">
13   - <div class="card-header">
14   - Fiche Projet
15   - </div>
16   - <div class="card-body">
17   - <dl class="row">
18   - <dt class="col-sm-2 text-right">ID :</dt>
19   - <dd class="col-sm-10 text-left">{{ project['id'] }}</dd>
20   - {% for category, labels in project['category_labels'].items() %}
21   - <dt class="col-sm-2 text-right">{{ category }} :</dt>
22   - <dd class="col-sm-10 text-left">{{ labels|join(",") }}</dd>
23   - {% endfor %}
24   - <dt class="col-sm-2 text-right">Etat :</dt>
25   - <dd class="col-sm-10 text-left">{{ project['status_name'] }}</dd>
26   - <dt class="col-sm-2 text-right">Date RSP :</dt>
27   - <dd class="col-sm-10 text-left">{{ project['rsp_date'] }}</dd>
28   - <dt class="col-sm-2 text-right">Fiche projet :</dt>
29   - <dd class="col-sm-10 text-left"></dd>
30   - <dt class="col-sm-2 text-right">Date MAJ de la fiche :</dt>
31   - <dd class="col-sm-10 text-left">{{ project['update_date'] }}</dd>
32   - <dt class="col-sm-2 text-right">Commentaire :</dt>
33   - <dd class="col-sm-10 text-left"></dd>
34   - </dl>
35   - {{ commons.charge_link( url_for('main.charge_add', project_id=project.id), 25) }}
36   - {{ commons.edit_link(url_for('main.project_edit', project_id=project.id), 25) }}
37   - {{ commons.delete_link( url_for('main.project_delete', project_id=project.id), 25) }}
38   - </div>
39   - </div>
  12 +<div class="card">
  13 + <div class="card-header">
  14 + Fiche Projet
  15 + </div>
  16 + <div class="card-body">
  17 + <dl class="row">
  18 + <dt class="col-sm-2 text-right">ID :</dt>
  19 + <dd class="col-sm-10 text-left">{{ project['id'] }}</dd>
  20 + {% for category, labels in project['category_labels'].items() %}
  21 + <dt class="col-sm-2 text-right">{{ category }} :</dt>
  22 + <dd class="col-sm-10 text-left">{{ labels|join(",") }}</dd>
  23 + {% endfor %}
  24 + <dt class="col-sm-2 text-right">Etat :</dt>
  25 + <dd class="col-sm-10 text-left">{{ project['status_name'] }}</dd>
  26 + <dt class="col-sm-2 text-right">Date RSP :</dt>
  27 + <dd class="col-sm-10 text-left">{{ project['rsp_date'] }}</dd>
  28 + <dt class="col-sm-2 text-right">Fiche projet :</dt>
  29 + <dd class="col-sm-10 text-left"></dd>
  30 + <dt class="col-sm-2 text-right">Date MAJ de la fiche :</dt>
  31 + <dd class="col-sm-10 text-left">{{ project['update_date'] }}</dd>
  32 + <dt class="col-sm-2 text-right">Commentaire :</dt>
  33 + <dd class="col-sm-10 text-left"></dd>
  34 + </dl>
  35 + {{ commons.charge_link( url_for('main.charge_add', project_id=project.id), 25) }}
  36 + {{ commons.edit_link(url_for('main.project_edit', project_id=project.id), 25) }}
  37 + {{ commons.delete_link( url_for('main.project_delete', project_id=project.id), 25) }}
  38 + </div>
  39 +</div>
40 40  
41   - <div class="charge_chart" id="project_services_chart"></div>
42   - <hr/>
43   - <div class="charge_chart" id="project_capacities_chart"></div>
44   - <hr/>
45   - <table class="table_datatables table table-hover">
46   - <thead>
47   - <tr>
48   - {% for header in charges[0] %}
49   - <th>{{ header }}</th>
50   - {% endfor %}
51   - </tr>
52   - </thead>
53   - <tbody>
54   - {% for line in charges[1:] %}
55   - <tr>
56   - {% for cell in line %}
57   - <td>{{ cell }}</td>
58   - {% endfor %}
59   - </tr>
60   - {% endfor %}
61   - </tbody>
62   - </table>
  41 +<div class="charge_chart" id="project_services_chart"></div>
  42 +<hr />
  43 +<div class="charge_chart" id="project_capacities_chart"></div>
  44 +<hr />
  45 +<table class="table_datatables table table-hover">
  46 + <thead>
  47 + <tr>
  48 + {% for header in charges[0] %}
  49 + <th>{{ header }}</th>
  50 + {% endfor %}
  51 + </tr>
  52 + </thead>
  53 + <tbody>
  54 + {% for line in charges[1:] %}
  55 + <tr>
  56 + {% for cell in line %}
  57 + <td>{{ cell }}</td>
  58 + {% endfor %}
  59 + </tr>
  60 + {% endfor %}
  61 + </tbody>
  62 +</table>
63 63 {% endblock %}
64 64  
65 65 {% block more_scripts %}
66   - {% include 'd3js-includes.html' %}
67   - {% include 'charges-includes.html' %}
  66 +{% include 'hg-includes.html' %}
  67 +{% include 'charges-includes.html' %}
68 68  
69   - <script>
70   - build_chart("#project_services_chart",
71   - "{{url_for('main.rest_charge_project', project_id=project.id, sort_key='service')}}",
72   - "{{project.name}}",
73   - "service");
74   - build_chart("#project_capacities_chart",
75   - "{{url_for('main.rest_charge_project', project_id=project.id, sort_key='capacity')}}",
76   - "{{project.name}}",
77   - "capacity");
78   - </script>
79   -{% endblock %}
  69 +<script>
  70 + document.addEventListener("DOMContentLoaded", function () {
  71 + var optionsService = {
  72 + chart: {
  73 + renderTo: 'project_services_chart',
  74 + type: 'column'
  75 + },
  76 + title: {
  77 + text: 'Charge du projet'
  78 + },
  79 + subtitle: {
  80 + text: '{{ project.fullname }}'
  81 + },
  82 + xAxis: {
  83 + title: {
  84 + text: 'Semestre'
  85 + },
  86 + type: 'category',
  87 + },
  88 + yAxis: {
  89 + title: {
  90 + text: 'Charge (ETP)'
  91 + }
  92 + },
  93 + tooltip: {
  94 + pointFormat: '{series.name}: <b>{point.y} ETP </b>'
  95 + },
  96 + series: [],
  97 + plotOptions: {
  98 + column: {
  99 + stacking: 'normal'
  100 + }
  101 + }
  102 + };
  103 +
  104 + var urlService = "{{url_for('main.rest_charge_project', project_id=project.id, sort_key='service', any_format='json_hs')}}";
  105 + $.getJSON(urlService, function (data) {
  106 + optionsService.series = data;
  107 + var chart = new Highcharts.Chart(optionsService);
  108 + });
  109 +
  110 + var optionsCapacities = {
  111 + chart: {
  112 + renderTo: 'project_capacities_chart',
  113 + type: 'column'
  114 + },
  115 + title: {
  116 + text: 'Charge du projet'
  117 + },
  118 + subtitle: {
  119 + text: '{{ project.fullname }}'
  120 + },
  121 + xAxis: {
  122 + title: {
  123 + text: 'Semestre'
  124 + },
  125 + type: 'category',
  126 + },
  127 + yAxis: {
  128 + title: {
  129 + text: 'Charge (ETP)'
  130 + }
  131 + },
  132 + tooltip: {
  133 + pointFormat: '{series.name}: <b>{point.y} ETP </b>'
  134 + },
  135 + series: [],
  136 + plotOptions: {
  137 + column: {
  138 + stacking: 'normal'
  139 + }
  140 + }
  141 + };
  142 +
  143 +
  144 + var urlCapacities = "{{url_for('main.rest_charge_project', project_id=project.id, sort_key='capacity', any_format='json_hs')}}";
  145 + $.getJSON(urlCapacities, function (data) {
  146 + optionsCapacities.series = data;
  147 + var chart = new Highcharts.Chart();
  148 + });
  149 + });
  150 +</script>
  151 +{% endblock %}
80 152 \ No newline at end of file
... ...
app/main/templates/project_delete_form.html 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +{% extends "base_page.html" %}
  2 +
  3 +{# Make link with form_manager #}
  4 +{% set project = object_struct %}
  5 +
  6 +{# Set the title that will be used in base_page #}
  7 +
  8 +{% if project['id'] and project['id'] != '' and project['name'] != None %}
  9 +{% set subtitle = "Supprimer le projet " + project['name'] %}
  10 +{% else %}
  11 +{% set subtitle = "Suppression pour nom de projet None" %}
  12 +{% endif %}
  13 +
  14 +{% block content %}
  15 +
  16 +<!-- Invisible span to define wich ul and a in the navbar are actived -->
  17 +<span id="nav_actived" style="display: none">admin,project_delete</span>
  18 +
  19 +<form id="project_form" class="pdc-form" action="{{ url_for('main.project_delete') }}" method="post"
  20 + onSubmit="return confirm('Etes vous sur de vouloir supprimer ce projet ?');">
  21 + {% if project['id'] and project['id'] != '' %}
  22 + <input class="form-control" id="project_id" name="project_id" type="hidden" value="{{ project['id'] }}">
  23 + {% endif %}
  24 +
  25 + <div class="form-group">
  26 + <label for="name">Nom</label>
  27 + <input class="form-control" id="name" name="name" type="text" placeholder="Nom de Projet"
  28 + value="{{ project['name'] if project['name'] }}">
  29 + </div>
  30 + <input id="deleteButton" class="pdc-form-submit" type="submit" value="Valider">
  31 +</form>
  32 +{% endblock %}
0 33 \ No newline at end of file
... ...
app/main/templates/project_delete_res.html 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +{% extends "base_page.html" %}
  2 +
  3 +{# Make link with form_manager #}
  4 +{% set project = object_struct %}
  5 +
  6 +{# Set the title that will be used in base_page #}
  7 +{% if project['id'] and project['id'] != '' %}
  8 + {% set subtitle = "Suppresion du projet " + project['name'] %}
  9 +{% else %}
  10 + {% set subtitle = "Projet " + project['name'] + "supprimé" %}
  11 +{% endif %}
  12 +
  13 +{% block content %}
  14 +
  15 + <!-- Invisible span to define wich ul and a in the navbar are actived -->
  16 + <span id="nav_actived" style="display: none">admin,project_delete</span>
  17 +
  18 + {% if project['id'] and project['id'] != '' %}
  19 + <input class="form-control" id="project_id" name="project_id" type="hidden" value="{{ project['id'] }}">
  20 + {% endif %}
  21 +
  22 + </div>
  23 + </form>
  24 +{% endblock %}
... ...
app/main/templates/project_form.html
... ... @@ -4,8 +4,9 @@
4 4 {% set project = object_struct %}
5 5  
6 6 {# Set the title that will be used in base_page #}
7   -{% if project['id'] and project['id'] != '' %}
8   - {% set subtitle = "Modifier le projet "+ project['name'] %}
  7 +
  8 +{% if project['id'] and project['id'] != '' and project['name'] != None %}
  9 + {% set subtitle = "Modifier le projet " + project['name'] %}
9 10 {% else %}
10 11 {% set subtitle = "Ajouter un nouveau projet" %}
11 12 {% endif %}
... ... @@ -19,6 +20,7 @@
19 20 {% if project['id'] and project['id'] != '' %}
20 21 <input class="form-control" id="project_id" name="project_id" type="hidden" value="{{ project['id'] }}">
21 22 {% endif %}
  23 +
22 24 <div class="form-group">
23 25 <label for="name">Nom</label>
24 26 <input class="form-control" id="name" name="name" type="text" placeholder="Nom de Projet"
... ...
app/main/templates/projects_stats.html
1 1 {% extends "base_page.html" %}
2 2 {% block more_heads %}
3   - <link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
4   - type="text/css"/>
  3 +<link href="{{ url_for('main.static', filename='css/charges.css', version=config.VERSION) }}" rel="stylesheet"
  4 + type="text/css" />
5 5 {% endblock %}
6 6  
7 7 {% block content %}
8 8  
9   - <!-- Invisible span to definte wich ul and a in the navbar are actived -->
10   - <span id="nav_actived" style="display: none">project,projects_stats</span>
  9 +<!-- Invisible span to definte wich ul and a in the navbar are actived -->
  10 +<span id="nav_actived" style="display: none">project,projects_stats</span>
11 11  
12   - <h3 class="sub-header">Charge pour tous les projets</h3>
13   - <div class="charge_chart" id="projects_stats_chart"></div>
  12 +<h3 class="sub-header">Charge pour tous les projets</h3>
  13 +<div class="charge_chart" id="projects_stats_chart"></div>
14 14  
15   - {% for c in categories %}
16   - <h3 class="sub-header">Charge pour la catégorie {{ c.name }}</h3>
17   - <div class="charge_chart" id="labels_stats_chart_{{ c.id }}"></div>
18   - {% endfor %}
  15 +{% for c in categories %}
  16 +<h3 class="sub-header">Charge pour la catégorie {{ c.name }}</h3>
  17 +<div class="charge_chart" id="labels_stats_chart_{{ c.id }}"></div>
  18 +{% endfor %}
19 19  
20 20 {% endblock %}
21 21  
22 22 {% block more_scripts %}
23   - {% include 'd3js-includes.html' %}
24   - {% include 'charges-includes.html' %}
25   -
26   - <script>
27   - build_chart("#projects_stats_chart",
28   - "{{url_for('main.rest_projects_stats')}}",
29   - "Charge des projets",
30   - "area");
31   -
32   - {% for c in categories %}
33   - build_chart("#labels_stats_chart_{{ c.id }}",
34   - "{{url_for('main.rest_labels_stats', category_id=c.id)}}",
35   - "Charge pour la catégorie {{c.name}}",
36   - "area");
37   - {% endfor %}
38   - </script>
39   -{% endblock %}
  23 +{% include 'hg-includes.html' %}
  24 +{# {% include 'd3js-includes.html' %}#}
  25 +{% include 'charges-includes.html' %}
  26 +
  27 +<script>
  28 + document.addEventListener("DOMContentLoaded", function () {
  29 + //def rest_projects_stats(any_format='csv'):
  30 + var url = "{{url_for('main.rest_projects_stats', any_format='json_hs')}}";
  31 + $.getJSON(url, function (data) {
  32 + var options = {
  33 + chart: {
  34 + renderTo: 'projects_stats_chart',
  35 + type: 'streamgraph'
  36 + },
  37 + title: {
  38 + text: 'Charge pour tous les projets'
  39 + },
  40 + subtitle: {
  41 + text: '{{ categories.name }}'
  42 + },
  43 + xAxis: {
  44 + title: {
  45 + text: 'Semestre'
  46 + },
  47 + type: 'category',
  48 + },
  49 + yAxis: {
  50 + title: {
  51 + text: 'Charge (ETP)'
  52 + }
  53 + },
  54 + tooltip: {
  55 + pointFormat: '{series.name}: <b>{point.y} ETP </b>'
  56 + },
  57 + series: [],
  58 + plotOptions: {
  59 + column: {
  60 + stacking: 'normal'
  61 + }
  62 + }
  63 + };
  64 +
  65 + options.series = data;
  66 + var chart = new Highcharts.Chart(options);
  67 + });
  68 +
  69 + {% for c in categories %}
  70 +
  71 + var url = "{{url_for('main.rest_labels_stats', category_id=c.id, any_format='json_hs')}}";
  72 + $.getJSON(url, function (data) {
  73 +
  74 + var options = {
  75 + chart: {
  76 + renderTo: 'labels_stats_chart_{{c.id}}',
  77 + type: 'streamgraph'
  78 + },
  79 + title: {
  80 + text: 'Charge pour la catégorie {{c.name}}'
  81 + },
  82 + subtitle: {
  83 + text: '{{ c.name }}'
  84 + },
  85 + xAxis: {
  86 + title: {
  87 + text: 'Semestre'
  88 + },
  89 + type: 'category',
  90 + },
  91 + yAxis: {
  92 + title: {
  93 + text: 'Charge (ETP)'
  94 + }
  95 + },
  96 + tooltip: {
  97 + pointFormat: '{series.name}: <b>{point.y} ETP </b>'
  98 + },
  99 + series: [],
  100 + plotOptions: {
  101 + column: {
  102 + stacking: 'normal'
  103 + }
  104 + }
  105 + };
  106 +
  107 + options.series = data;
  108 + var chart = new Highcharts.Chart(options);
  109 + });
  110 +
  111 + {% endfor %}
  112 +
  113 +
  114 + });
  115 +
  116 +
  117 +
  118 +</script>
  119 +{% endblock %}
40 120 \ No newline at end of file
... ...
app/models.py
... ... @@ -12,7 +12,7 @@ db = SQLAlchemy()
12 12  
13 13 class Formable:
14 14 """
15   - Parent class allowing some html form facilities
  15 + Parent class allowing some html form facilities
16 16  
17 17 """
18 18 export_keys = []
... ... @@ -32,7 +32,7 @@ class Formable:
32 32 """
33 33 Export the orm object to a structure easily used in jinja
34 34  
35   - :return: nothing
  35 + :return: nothing
36 36 """
37 37  
38 38 # first set default keys
... ... @@ -43,21 +43,19 @@ class Formable:
43 43 _value = getattr(self, key)
44 44 _struct[key] = '' if _value is None else _value
45 45 return _struct
  46 +"""
  47 + Categorized projects
46 48  
  49 + There is one label list,
  50 + each label belongs to one or more categories.
  51 +
  52 + The projects are labelled by one or more label.
  53 +
  54 + Thus this is modeled with classes
  55 + Project, Label and Category
  56 + And many_to_many association are done through
  57 + ProjectLabel and CategoryLabel """
47 58  
48   -#
49   -# Categorized projects
50   -#
51   -# There is one label list,
52   -# each label belongs to one or more categories.
53   -#
54   -# The projects are labelled by one or more label.
55   -#
56   -# Thus this is modeled with classes
57   -# Project, Label and Category
58   -# And many_to_many association are done through
59   -# ProjectLabel and CategoryLabel
60   -#
61 59 class ProjectStatus(db.Model, Formable):
62 60 id = db.Column(db.Integer, primary_key=True)
63 61 name = db.Column(db.String(100), unique=True)
... ...
app/templates/base_page.html
... ... @@ -153,9 +153,12 @@
153 153 <li class="nav-item"><a id="periods_list" class="sub_link nav-link "
154 154 href="{{ url_for('main.periods') }}">Liste des
155 155 périodes</a></li>
156   - <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Ajouter une période</a>
  156 + <li class="nav-item"><a class="sub_link nav-link disabled" href="#">Ajouter une période</a>
  157 + </li>
  158 +
  159 + </li>
  160 +
157 161 </li>
158   -
159 162 </ul>
160 163 </li>
161 164 </ul>
... ...
app/templates/hg-includes.html
1 1 <script src="{{ url_for('static', filename='js/hg/highcharts.js') }}" type="text/javascript"></script>
2 2 <script src="{{ url_for('static', filename='js/hg/modules/exporting.js') }}" type="text/javascript"></script>
  3 +
  4 +
  5 +<script src="https://code.highcharts.com/modules/streamgraph.js"></script>
  6 +<script src="https://code.highcharts.com/modules/export-data.js"></script>
3 7 <script src="{{ url_for('main.static', filename='js/hg-charges.js', version=config.VERSION) }}"
4 8 - type="text/javascript"></script>
  9 + type="text/javascript"></script>
5 10 \ No newline at end of file
... ...
tests/frontend_tests.py
... ... @@ -7,6 +7,7 @@ from flask_testing import LiveServerTestCase
7 7 from selenium import webdriver
8 8 from selenium.webdriver.common.keys import Keys
9 9 from selenium.webdriver.support.select import Select
  10 +from selenium.webdriver.common.alert import Alert
10 11  
11 12 from app import create_app
12 13 from app.models import Agent, Charge, Project, AgentResponsability, ProjectStatus, Label, Category, Capacity, \
... ... @@ -124,7 +125,7 @@ class AccessTestCase(BaseFrontTestCase):
124 125 self.driver.get(target_url)
125 126 self.assertEqual(self.driver.current_url, f'http://localhost:8943/agent/{agent_id}')
126 127 td_elmts = self.driver.find_elements_by_xpath("//table[contains(@class,'table_datatables')]/tbody/tr/td")
127   - self.assertEqual(260, len(td_elmts))
  128 + self.assertEqual(312, len(td_elmts))
128 129  
129 130 def test_projects_page(self):
130 131 target_url = self.get_server_url() + url_for('main.projects')
... ... @@ -360,7 +361,14 @@ class FormsTestCase(BaseFrontTestCase):
360 361 self.assertTrue(project_name + "< existe déjà" in message.text)
361 362  
362 363 def test_project_delete(self):
363   - self.check_delete(url_for('main.project_delete', project_id=1))
  364 + delete_url = url_for('main.project_delete', project_id=1)
  365 + target_url = self.get_server_url() + delete_url
  366 + self.driver.get(target_url)
  367 +
  368 + self.driver.find_element_by_xpath("//input[@id='deleteButton']").click()
  369 + Alert(self.driver).accept()
  370 + message = self.driver.find_element_by_xpath("//div[@id='messages']")
  371 + self.assertTrue("supprimé" in message.text)
364 372  
365 373 def test_project_status_edit(self):
366 374 init_dict = {'name': 'Abandonné'}
... ...