zou 0.20.61__py3-none-any.whl → 0.20.63__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. zou/__init__.py +1 -1
  2. zou/app/api.py +2 -0
  3. zou/app/blueprints/crud/__init__.py +25 -1
  4. zou/app/blueprints/crud/hardware_item.py +12 -0
  5. zou/app/blueprints/crud/notification.py +0 -4
  6. zou/app/blueprints/crud/production_schedule_version.py +114 -0
  7. zou/app/blueprints/departments/__init__.py +35 -0
  8. zou/app/blueprints/departments/resources.py +262 -0
  9. zou/app/blueprints/index/resources.py +2 -9
  10. zou/app/blueprints/persons/resources.py +1 -3
  11. zou/app/blueprints/projects/__init__.py +20 -0
  12. zou/app/blueprints/projects/resources.py +200 -0
  13. zou/app/blueprints/tasks/resources.py +5 -2
  14. zou/app/blueprints/user/resources.py +1 -3
  15. zou/app/models/base.py +15 -4
  16. zou/app/models/department.py +62 -0
  17. zou/app/models/entity.py +3 -3
  18. zou/app/models/hardware_item.py +15 -0
  19. zou/app/models/person.py +1 -1
  20. zou/app/models/production_schedule_version.py +81 -0
  21. zou/app/models/project.py +17 -6
  22. zou/app/models/software.py +5 -1
  23. zou/app/models/task.py +7 -10
  24. zou/app/services/assets_service.py +5 -6
  25. zou/app/services/concepts_service.py +3 -4
  26. zou/app/services/deletion_service.py +10 -18
  27. zou/app/services/departments_service.py +153 -0
  28. zou/app/services/edits_service.py +3 -4
  29. zou/app/services/entities_service.py +5 -6
  30. zou/app/services/exception.py +8 -0
  31. zou/app/services/file_tree_service.py +2 -2
  32. zou/app/services/preview_files_service.py +6 -2
  33. zou/app/services/schedule_service.py +243 -1
  34. zou/app/services/shots_service.py +3 -4
  35. zou/app/services/tasks_service.py +5 -1
  36. zou/app/services/user_service.py +57 -23
  37. zou/migrations/alembic.ini +1 -1
  38. zou/migrations/versions/0bd1e89f2a6f_add_productionscheduleversiontasklink_uc.py +58 -0
  39. zou/migrations/versions/26f96f65cfa3_add_productionversionschedule_uc.py +40 -0
  40. zou/migrations/versions/3d8e68dffeee_fix_task_person_link.py +56 -0
  41. zou/migrations/versions/4368137b44e1_productionscheduleversion_add_on_delete_.py +188 -0
  42. zou/migrations/versions/4bd9bfb73f11_add_fields_to_the_software_table.py +49 -0
  43. zou/migrations/versions/5f715f2b6348_add_new_table_productionscheduleversion.py +128 -0
  44. zou/migrations/versions/7a16258f2fab_add_currency_field_to_budgets.py +2 -4
  45. zou/migrations/versions/9683bd840dee_add_archived_field_to_software.py +33 -0
  46. zou/migrations/versions/9af2df17a9d5_add_hardware_table.py +48 -0
  47. zou/migrations/versions/ce7f46f445dc_add_column_estimation_for_.py +37 -0
  48. zou/migrations/versions/d5665dca188b_add_version_field_to_software.py +35 -0
  49. zou/migrations/versions/d97f2730bf7b_add_.py +101 -0
  50. zou/migrations/versions/dde6be40f54f_add_departement_links_tables_for_.py +151 -0
  51. zou/migrations/versions/e4b48ca33539_add_project_production_schedule_version_.py +56 -0
  52. zou/migrations/versions/e8bc24998b34_remove_on_delete_cascade_for_.py +59 -0
  53. {zou-0.20.61.dist-info → zou-0.20.63.dist-info}/METADATA +3 -3
  54. {zou-0.20.61.dist-info → zou-0.20.63.dist-info}/RECORD +58 -37
  55. {zou-0.20.61.dist-info → zou-0.20.63.dist-info}/WHEEL +0 -0
  56. {zou-0.20.61.dist-info → zou-0.20.63.dist-info}/entry_points.txt +0 -0
  57. {zou-0.20.61.dist-info → zou-0.20.63.dist-info}/licenses/LICENSE +0 -0
  58. {zou-0.20.61.dist-info → zou-0.20.63.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,153 @@
1
+ from zou.app.models.department import (
2
+ Department,
3
+ SoftwareDepartmentLink,
4
+ HardwareItemDepartmentLink,
5
+ )
6
+ from zou.app.models.hardware_item import HardwareItem
7
+ from zou.app.models.software import Software
8
+ from zou.app.utils import fields
9
+
10
+ from zou.app.services.exception import (
11
+ DepartmentNotFoundException,
12
+ SoftwareNotFoundException,
13
+ HardwareItemNotFoundException,
14
+ )
15
+
16
+
17
+ def _check_department_exists(department_id):
18
+ department = Department.get(department_id)
19
+ if not department:
20
+ raise DepartmentNotFoundException
21
+ return department
22
+
23
+
24
+ def _check_software_exists(software_id):
25
+ software = Software.get(software_id)
26
+ if not software:
27
+ raise SoftwareNotFoundException
28
+ return software
29
+
30
+
31
+ def _check_hardware_item_exists(hardware_item_id):
32
+ hardware_item = HardwareItem.get(hardware_item_id)
33
+ if not hardware_item:
34
+ raise HardwareItemNotFoundException
35
+ return hardware_item
36
+
37
+
38
+ def get_all_software_for_departments():
39
+ """
40
+ Get all software items for all departments organized by department
41
+ in a dictionary where the key is the department id and the value is a
42
+ list of linked software items.
43
+ """
44
+ software_list = (
45
+ Software.query.join(SoftwareDepartmentLink)
46
+ .add_columns(SoftwareDepartmentLink.department_id)
47
+ .all()
48
+ )
49
+ department_map = {}
50
+ for software, department_id in software_list:
51
+ if department_id not in department_map:
52
+ department_map[department_id] = []
53
+ department_map[department_id].append(software.serialize())
54
+ return department_map
55
+
56
+
57
+ def get_all_hardware_items_for_departments():
58
+ """
59
+ Get all hardware items for all departments organized by department
60
+ in a dictionary where the key is the department id and the value is a
61
+ list of linked hardware items.
62
+ """
63
+ hardware_item_list = (
64
+ HardwareItem.query.join(HardwareItemDepartmentLink)
65
+ .add_columns(HardwareItemDepartmentLink.department_id)
66
+ .all()
67
+ )
68
+ department_map = {}
69
+ for hardware_item, department_id in hardware_item_list:
70
+ if department_id not in department_map:
71
+ department_map[department_id] = []
72
+ department_map[department_id].append(hardware_item.serialize())
73
+ return department_map
74
+
75
+
76
+ def get_software_for_department(department_id):
77
+ """
78
+ Get all software items for a given department.
79
+ """
80
+ _check_department_exists(department_id)
81
+ return fields.serialize_models(
82
+ Software.query.join(SoftwareDepartmentLink)
83
+ .filter(SoftwareDepartmentLink.department_id == department_id)
84
+ .all()
85
+ )
86
+
87
+
88
+ def get_hardware_items_for_department(department_id):
89
+ """
90
+ Get all hardware items for a given department.
91
+ """
92
+ _check_department_exists(department_id)
93
+ return fields.serialize_models(
94
+ HardwareItem.query.join(HardwareItemDepartmentLink)
95
+ .filter(HardwareItemDepartmentLink.department_id == department_id)
96
+ .all()
97
+ )
98
+
99
+
100
+ def add_software_to_department(department_id, software_id):
101
+ """
102
+ Add a software item to a department.
103
+ """
104
+ _check_department_exists(department_id)
105
+ _check_software_exists(software_id)
106
+ link = SoftwareDepartmentLink.get_or_create(
107
+ department_id=department_id, software_id=software_id
108
+ )
109
+ return link.serialize()
110
+
111
+
112
+ def remove_software_from_department(department_id, software_id):
113
+ """
114
+ Remove a software item from a department.
115
+ """
116
+ _check_department_exists(department_id)
117
+ _check_software_exists(software_id)
118
+ link = SoftwareDepartmentLink.get_by(
119
+ department_id=department_id, software_id=software_id
120
+ )
121
+ if not link:
122
+ return None
123
+ else:
124
+ link.delete()
125
+ return link.serialize()
126
+
127
+
128
+ def add_hardware_item_to_department(department_id, hardware_item_id):
129
+ """
130
+ Add a hardware item to a department.
131
+ """
132
+ _check_department_exists(department_id)
133
+ _check_hardware_item_exists(hardware_item_id)
134
+ link = HardwareItemDepartmentLink.get_or_create(
135
+ department_id=department_id, hardware_item_id=hardware_item_id
136
+ )
137
+ return link.serialize()
138
+
139
+
140
+ def remove_hardware_item_from_department(department_id, hardware_item_id):
141
+ """
142
+ Remove a hardware item from a department.
143
+ """
144
+ _check_department_exists(department_id)
145
+ _check_hardware_item_exists(hardware_item_id)
146
+ link = HardwareItemDepartmentLink.get_by(
147
+ department_id=department_id, hardware_item_id=hardware_item_id
148
+ )
149
+ if not link:
150
+ return None
151
+ else:
152
+ link.delete()
153
+ return link.serialize()
@@ -16,8 +16,7 @@ from zou.app.models.entity import (
16
16
  )
17
17
  from zou.app.models.project import Project
18
18
  from zou.app.models.subscription import Subscription
19
- from zou.app.models.task import Task
20
- from zou.app.models.task import assignees_table
19
+ from zou.app.models.task import Task, TaskPersonLink
21
20
 
22
21
  from zou.app.services import (
23
22
  deletion_service,
@@ -90,7 +89,7 @@ def get_edits_and_tasks(criterions={}):
90
89
  Entity.query.join(Project, Entity.project_id == Project.id)
91
90
  .outerjoin(Episode, Episode.id == Entity.parent_id)
92
91
  .outerjoin(Task, Task.entity_id == Entity.id)
93
- .outerjoin(assignees_table)
92
+ .outerjoin(TaskPersonLink)
94
93
  .add_columns(
95
94
  Episode.id,
96
95
  Episode.name,
@@ -108,7 +107,7 @@ def get_edits_and_tasks(criterions={}):
108
107
  Task.last_comment_date,
109
108
  Task.last_preview_file_id,
110
109
  Task.nb_assets_ready,
111
- assignees_table.columns.person,
110
+ TaskPersonLink.person_id,
112
111
  Project.id,
113
112
  Project.name,
114
113
  )
@@ -18,8 +18,7 @@ from zou.app.models.entity import Entity, EntityLink, EntityConceptLink
18
18
  from zou.app.models.entity_type import EntityType
19
19
  from zou.app.models.preview_file import PreviewFile
20
20
  from zou.app.models.project import Project
21
- from zou.app.models.task import assignees_table
22
- from zou.app.models.task import Task
21
+ from zou.app.models.task import Task, TaskPersonLink
23
22
 
24
23
  from zou.app import db
25
24
 
@@ -255,7 +254,7 @@ def get_entities_and_tasks(criterions={}):
255
254
 
256
255
  query = (
257
256
  Entity.query.outerjoin(Task, Task.entity_id == Entity.id)
258
- .outerjoin(assignees_table)
257
+ .outerjoin(TaskPersonLink)
259
258
  .add_columns(
260
259
  Task.id,
261
260
  Task.task_type_id,
@@ -272,7 +271,7 @@ def get_entities_and_tasks(criterions={}):
272
271
  Task.last_comment_date,
273
272
  Task.last_preview_file_id,
274
273
  Task.difficulty,
275
- assignees_table.columns.person,
274
+ TaskPersonLink.person_id,
276
275
  )
277
276
  )
278
277
 
@@ -416,7 +415,7 @@ def get_linked_entities_with_tasks(entity_id):
416
415
  query = (
417
416
  Entity.query.join(Project, Project.id == Entity.project_id)
418
417
  .outerjoin(Task, Task.entity_id == Entity.id)
419
- .outerjoin(assignees_table)
418
+ .outerjoin(TaskPersonLink)
420
419
  .join(EntityType)
421
420
  .add_columns(
422
421
  Task.id,
@@ -433,7 +432,7 @@ def get_linked_entities_with_tasks(entity_id):
433
432
  Task.last_comment_date,
434
433
  Task.nb_assets_ready,
435
434
  Task.assigner_id,
436
- assignees_table.columns.person,
435
+ TaskPersonLink.person_id,
437
436
  Project.id,
438
437
  Project.name,
439
438
  EntityType.name,
@@ -89,6 +89,10 @@ class SoftwareNotFoundException(NotFound):
89
89
  pass
90
90
 
91
91
 
92
+ class HardwareItemNotFoundException(NotFound):
93
+ pass
94
+
95
+
92
96
  class OutputTypeNotFoundException(NotFound):
93
97
  pass
94
98
 
@@ -287,3 +291,7 @@ class WrongTaskTypeForEntityException(Exception):
287
291
 
288
292
  class IsUserLimitReachedException(Exception):
289
293
  pass
294
+
295
+
296
+ class ProductionScheduleVersionNotFoundException(NotFound):
297
+ pass
@@ -789,7 +789,7 @@ def extract_variable_values_from_path(elements, template_elements):
789
789
  continue
790
790
  else:
791
791
  raise WrongPathFormatException(
792
- "{} doesn't match {}".format(elements, template_elements)
792
+ f"{elements} doesn't match {template_elements}"
793
793
  )
794
794
 
795
795
  data_type = token.group("token")
@@ -1131,7 +1131,7 @@ def guess_from_path(project_id, file_path, sep="/"):
1131
1131
  # Some template_element don't have a corresponding token,
1132
1132
  # like "05_publish" folder, for example.
1133
1133
  for token, token_value in tokens.items():
1134
- if "<{}>".format(token) in template_element:
1134
+ if f"<{token}>" in template_element:
1135
1135
  break
1136
1136
  else:
1137
1137
  continue
@@ -522,8 +522,12 @@ def _apply_annotation_deletions(annotations, deletions):
522
522
  if deletion["time"] in annotation_map:
523
523
  annotation = annotation_map[deletion["time"]]
524
524
  deleted_object_ids = deletion.get("objects", [])
525
- previous_objects = annotation.get("drawing", {}).get("objects", [])
526
- annotation.get("drawing", {})["objects"] = [
525
+ if "drawing" not in annotation or not isinstance(
526
+ annotation["drawing"], dict
527
+ ):
528
+ annotation["drawing"] = {}
529
+ previous_objects = annotation["drawing"].get("objects", [])
530
+ annotation["drawing"]["objects"] = [
527
531
  previous_object
528
532
  for previous_object in previous_objects
529
533
  if previous_object.get("id", "") not in deleted_object_ids
@@ -1,18 +1,43 @@
1
1
  from datetime import date, timedelta
2
+ from sqlalchemy.exc import StatementError
3
+ from sqlalchemy.dialects.postgresql import insert
4
+ from sqlalchemy import update
5
+
2
6
 
3
7
  from zou.app.models.entity import Entity
4
8
  from zou.app.models.entity_type import EntityType
5
9
  from zou.app.models.milestone import Milestone
6
10
  from zou.app.models.schedule_item import ScheduleItem
7
- from zou.app.utils import events, fields
11
+ from zou.app.models.task import Task
12
+ from zou.app.models.task_type import TaskType
13
+ from zou.app.models.production_schedule_version import (
14
+ ProductionScheduleVersion,
15
+ ProductionScheduleVersionTaskLink,
16
+ )
17
+ from zou.app.utils import events, fields, cache
8
18
  from zou.app.services import (
9
19
  assets_service,
10
20
  base_service,
11
21
  shots_service,
12
22
  tasks_service,
23
+ projects_service,
24
+ )
25
+ from zou.app import db
26
+
27
+ from zou.app.services.exception import (
28
+ ProductionScheduleVersionNotFoundException,
13
29
  )
14
30
 
15
31
 
32
+ def clear_production_schedule_version_cache(production_schedule_version_id):
33
+ cache.cache.delete_memoized(
34
+ get_production_schedule_version, production_schedule_version_id
35
+ )
36
+ cache.cache.delete_memoized(
37
+ get_production_schedule_version, production_schedule_version_id, True
38
+ )
39
+
40
+
16
41
  def get_schedule_items(project_id):
17
42
  """
18
43
  Get all project schedule items (mainly for sync purpose).
@@ -204,3 +229,220 @@ def get_milestones_for_project(project_id):
204
229
  """
205
230
  query = Milestone.query.filter_by(project_id=project_id)
206
231
  return [milestone.present() for milestone in query.all()]
232
+
233
+
234
+ def get_production_schedule_version_raw(production_schedule_version_id):
235
+ """
236
+ Get production schedule version matching given id.
237
+ """
238
+ try:
239
+ production_schedule_version = ProductionScheduleVersion.get(
240
+ production_schedule_version_id
241
+ )
242
+ except StatementError:
243
+ raise ProductionScheduleVersionNotFoundException
244
+
245
+ if production_schedule_version is None:
246
+ raise ProductionScheduleVersionNotFoundException
247
+
248
+ return production_schedule_version
249
+
250
+
251
+ @cache.memoize_function(120)
252
+ def get_production_schedule_version(
253
+ production_schedule_version_id, relations=False
254
+ ):
255
+ """
256
+ Get production schedule version matching given id and serialize it.
257
+ """
258
+ return get_production_schedule_version_raw(
259
+ production_schedule_version_id
260
+ ).serialize(relations=relations)
261
+
262
+
263
+ def get_production_schedule_version_task_links(
264
+ production_schedule_version_id, task_type_id=None, relations=False
265
+ ):
266
+ """
267
+ Get all task links for given production schedule version.
268
+ """
269
+ query = ProductionScheduleVersionTaskLink.query.filter_by(
270
+ production_schedule_version_id=production_schedule_version_id
271
+ )
272
+
273
+ if task_type_id is not None:
274
+ query = (
275
+ query.join(Task)
276
+ .join(TaskType)
277
+ .filter(Task.task_type_id == task_type_id)
278
+ )
279
+
280
+ return fields.serialize_models(query.all(), relations=relations)
281
+
282
+
283
+ def update_production_schedule_version(production_schedule_version_id, data):
284
+ """
285
+ Update production schedule version with given id with data.
286
+ """
287
+ production_schedule_version = get_production_schedule_version_raw(
288
+ production_schedule_version_id
289
+ )
290
+
291
+ production_schedule_version.update(data)
292
+ clear_production_schedule_version_cache(production_schedule_version_id)
293
+ events.emit(
294
+ "production_schedule_version:update",
295
+ {"production_schedule_version_id": production_schedule_version_id},
296
+ project_id=str(production_schedule_version.project_id),
297
+ )
298
+ return production_schedule_version.serialize()
299
+
300
+
301
+ def set_production_schedule_version_task_links_from_production(
302
+ production_schedule_version_id,
303
+ ):
304
+ """
305
+ Set task links for given production schedule version from tasks in the
306
+ production.
307
+ """
308
+ production_schedule_version = get_production_schedule_version(
309
+ production_schedule_version_id
310
+ )
311
+
312
+ tasks = (
313
+ db.session.query(Task)
314
+ .filter(Task.project_id == production_schedule_version["project_id"])
315
+ .all()
316
+ )
317
+
318
+ rows = [
319
+ {
320
+ "id": fields.gen_uuid(),
321
+ "production_schedule_version_id": production_schedule_version_id,
322
+ "task_id": task.id,
323
+ "start_date": task.start_date,
324
+ "due_date": task.due_date,
325
+ "estimation": task.estimation,
326
+ }
327
+ for task in tasks
328
+ ]
329
+ insert_stmt = insert(ProductionScheduleVersionTaskLink).values(rows)
330
+ insert_stmt = insert_stmt.on_conflict_do_update(
331
+ index_elements=["production_schedule_version_id", "task_id"],
332
+ set_={
333
+ "start_date": insert_stmt.excluded.start_date,
334
+ "due_date": insert_stmt.excluded.due_date,
335
+ "estimation": insert_stmt.excluded.estimation,
336
+ },
337
+ ).returning(ProductionScheduleVersionTaskLink)
338
+
339
+ results = db.session.execute(insert_stmt).scalars().all()
340
+
341
+ tasks_map = {task.id: task for task in tasks}
342
+
343
+ for task_link in results:
344
+ task_link.assignees = tasks_map[task_link.task_id].assignees
345
+
346
+ db.session.commit()
347
+
348
+ return fields.serialize_models(results, relations=True)
349
+
350
+
351
+ def set_production_schedule_version_task_links_from_production_schedule_version(
352
+ production_schedule_version_id, other_production_schedule_version_id
353
+ ):
354
+ """
355
+ Set task links for given production schedule version from another.
356
+ """
357
+
358
+ other_links = (
359
+ db.session.query(ProductionScheduleVersionTaskLink)
360
+ .filter(
361
+ ProductionScheduleVersionTaskLink.production_schedule_version_id
362
+ == other_production_schedule_version_id
363
+ )
364
+ .all()
365
+ )
366
+
367
+ rows = [
368
+ {
369
+ "id": fields.gen_uuid(),
370
+ "production_schedule_version_id": production_schedule_version_id,
371
+ "task_id": links.task_id,
372
+ "start_date": links.start_date,
373
+ "due_date": links.due_date,
374
+ "estimation": links.estimation,
375
+ }
376
+ for links in other_links
377
+ ]
378
+
379
+ insert_stmt = insert(ProductionScheduleVersionTaskLink).values(rows)
380
+ insert_stmt = insert_stmt.on_conflict_do_update(
381
+ index_elements=["production_schedule_version_id", "task_id"],
382
+ set_={
383
+ "start_date": insert_stmt.excluded.start_date,
384
+ "due_date": insert_stmt.excluded.due_date,
385
+ "estimation": insert_stmt.excluded.estimation,
386
+ },
387
+ ).returning(ProductionScheduleVersionTaskLink)
388
+
389
+ results = db.session.execute(insert_stmt).scalars().all()
390
+
391
+ tasks_map = {link.task_id: link for link in other_links}
392
+
393
+ for task_link in results:
394
+ task_link.assignees = tasks_map[task_link.task_id].assignees
395
+
396
+ db.session.commit()
397
+
398
+ update_production_schedule_version(
399
+ production_schedule_version_id,
400
+ {"production_schedule_from": other_production_schedule_version_id},
401
+ )
402
+
403
+ return fields.serialize_models(results, relations=True)
404
+
405
+
406
+ def apply_production_schedule_version_to_production(
407
+ production_schedule_version_id,
408
+ ):
409
+ """
410
+ Apply production schedule version to production.
411
+ """
412
+
413
+ stmt = (
414
+ update(Task)
415
+ .values(
416
+ start_date=ProductionScheduleVersionTaskLink.start_date,
417
+ due_date=ProductionScheduleVersionTaskLink.due_date,
418
+ estimation=ProductionScheduleVersionTaskLink.estimation,
419
+ )
420
+ .where(Task.id == ProductionScheduleVersionTaskLink.task_id)
421
+ .where(
422
+ ProductionScheduleVersionTaskLink.production_schedule_version_id
423
+ == production_schedule_version_id
424
+ )
425
+ .returning(Task)
426
+ )
427
+
428
+ results = db.session.execute(stmt).scalars().all()
429
+
430
+ db.session.commit()
431
+
432
+ for task in results:
433
+ events.emit(
434
+ "task:update",
435
+ {"task_id": str(task.id)},
436
+ project_id=str(task.project_id),
437
+ )
438
+
439
+ production_schedule_version = update_production_schedule_version(
440
+ production_schedule_version_id, {"locked": True}
441
+ )
442
+
443
+ projects_service.update_project(
444
+ production_schedule_version["project_id"],
445
+ {"from_schedule_version_id": production_schedule_version_id},
446
+ )
447
+
448
+ return fields.serialize_models(results, relations=True)
@@ -23,8 +23,7 @@ from zou.app.models.project import Project
23
23
  from zou.app.models.preview_file import PreviewFile
24
24
  from zou.app.models.schedule_item import ScheduleItem
25
25
  from zou.app.models.subscription import Subscription
26
- from zou.app.models.task import Task
27
- from zou.app.models.task import assignees_table
26
+ from zou.app.models.task import Task, TaskPersonLink
28
27
  from zou.app.models.time_spent import TimeSpent
29
28
 
30
29
  from zou.app.services import (
@@ -234,7 +233,7 @@ def get_shots_and_tasks(criterions={}):
234
233
  .join(Sequence, Sequence.id == Entity.parent_id)
235
234
  .outerjoin(Episode, Episode.id == Sequence.parent_id)
236
235
  .outerjoin(Task, Task.entity_id == Entity.id)
237
- .outerjoin(assignees_table)
236
+ .outerjoin(TaskPersonLink)
238
237
  .add_columns(
239
238
  Episode.name,
240
239
  Episode.id,
@@ -257,7 +256,7 @@ def get_shots_and_tasks(criterions={}):
257
256
  Task.nb_assets_ready,
258
257
  Task.difficulty,
259
258
  Task.nb_drawings,
260
- assignees_table.columns.person,
259
+ TaskPersonLink.person_id,
261
260
  Project.id,
262
261
  Project.name,
263
262
  )
@@ -1846,7 +1846,7 @@ def reset_task_data(task_id):
1846
1846
  return task.serialize(relations=True)
1847
1847
 
1848
1848
 
1849
- def get_persons_tasks_dates():
1849
+ def get_persons_tasks_dates(project_id=None):
1850
1850
  """
1851
1851
  For schedule usages, for each active person, it returns the first start
1852
1852
  date of all tasks of assigned to this person and the last end date.
@@ -1861,6 +1861,10 @@ def get_persons_tasks_dates():
1861
1861
  .group_by(Person.id)
1862
1862
  .join(Task.assignees)
1863
1863
  )
1864
+
1865
+ if project_id is not None:
1866
+ query = query.filter(Task.project_id == project_id)
1867
+
1864
1868
  return [
1865
1869
  {
1866
1870
  "person_id": str(person_id),