Commit e71d522eb435d6f4323b251b8962f70494d78ec0
1 parent
b5550200
Sprint 1 Release:
- Transition de d3js à Hightchartjs - Modification des affectations des agents - Modification des projets
Showing
14 changed files
with
833 additions
and
403 deletions
Show diff stats
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=','): |
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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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é'} | ... | ... |