trueppm-api 0.4.0b2__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 (759) hide show
  1. trueppm_api/__init__.py +14 -0
  2. trueppm_api/apps/access/__init__.py +0 -0
  3. trueppm_api/apps/access/apps.py +19 -0
  4. trueppm_api/apps/access/groups.py +518 -0
  5. trueppm_api/apps/access/management/__init__.py +0 -0
  6. trueppm_api/apps/access/management/commands/__init__.py +0 -0
  7. trueppm_api/apps/access/management/commands/create_admin.py +164 -0
  8. trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
  9. trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
  10. trueppm_api/apps/access/migrations/0001_initial.py +61 -0
  11. trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
  12. trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
  13. trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
  14. trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
  15. trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
  16. trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
  17. trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
  18. trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
  19. trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
  20. trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
  21. trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
  22. trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
  23. trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
  24. trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
  25. trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
  26. trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
  27. trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
  28. trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
  29. trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
  30. trueppm_api/apps/access/migrations/__init__.py +0 -0
  31. trueppm_api/apps/access/models.py +588 -0
  32. trueppm_api/apps/access/permissions.py +2837 -0
  33. trueppm_api/apps/access/serializers.py +946 -0
  34. trueppm_api/apps/access/services.py +667 -0
  35. trueppm_api/apps/access/signals.py +103 -0
  36. trueppm_api/apps/access/tasks.py +50 -0
  37. trueppm_api/apps/access/throttles.py +95 -0
  38. trueppm_api/apps/access/urls.py +191 -0
  39. trueppm_api/apps/access/views.py +1931 -0
  40. trueppm_api/apps/agents/__init__.py +6 -0
  41. trueppm_api/apps/agents/apps.py +11 -0
  42. trueppm_api/apps/agents/canonical.py +73 -0
  43. trueppm_api/apps/agents/deferred.py +108 -0
  44. trueppm_api/apps/agents/management/__init__.py +0 -0
  45. trueppm_api/apps/agents/management/commands/__init__.py +0 -0
  46. trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
  47. trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
  48. trueppm_api/apps/agents/middleware.py +44 -0
  49. trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
  50. trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
  51. trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
  52. trueppm_api/apps/agents/migrations/__init__.py +0 -0
  53. trueppm_api/apps/agents/models.py +371 -0
  54. trueppm_api/apps/agents/refusal.py +103 -0
  55. trueppm_api/apps/agents/serializers.py +85 -0
  56. trueppm_api/apps/agents/services.py +397 -0
  57. trueppm_api/apps/agents/signals.py +30 -0
  58. trueppm_api/apps/agents/urls.py +15 -0
  59. trueppm_api/apps/agents/views.py +108 -0
  60. trueppm_api/apps/csvimport/__init__.py +12 -0
  61. trueppm_api/apps/csvimport/apps.py +11 -0
  62. trueppm_api/apps/csvimport/mapping.py +363 -0
  63. trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
  64. trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
  65. trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
  66. trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
  67. trueppm_api/apps/csvimport/models.py +111 -0
  68. trueppm_api/apps/csvimport/parser.py +1825 -0
  69. trueppm_api/apps/csvimport/services.py +48 -0
  70. trueppm_api/apps/csvimport/tasks.py +418 -0
  71. trueppm_api/apps/csvimport/template.py +37 -0
  72. trueppm_api/apps/csvimport/urls.py +50 -0
  73. trueppm_api/apps/csvimport/views.py +793 -0
  74. trueppm_api/apps/history/__init__.py +0 -0
  75. trueppm_api/apps/history/apps.py +12 -0
  76. trueppm_api/apps/history/changelog.py +359 -0
  77. trueppm_api/apps/history/diff_policy.py +177 -0
  78. trueppm_api/apps/history/serializers.py +104 -0
  79. trueppm_api/apps/history/signals.py +82 -0
  80. trueppm_api/apps/history/tasks.py +105 -0
  81. trueppm_api/apps/history/urls.py +38 -0
  82. trueppm_api/apps/history/views.py +443 -0
  83. trueppm_api/apps/idempotency/__init__.py +0 -0
  84. trueppm_api/apps/idempotency/apps.py +11 -0
  85. trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
  86. trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
  87. trueppm_api/apps/idempotency/mixins.py +226 -0
  88. trueppm_api/apps/idempotency/models.py +75 -0
  89. trueppm_api/apps/idempotency/tasks.py +61 -0
  90. trueppm_api/apps/integrations/__init__.py +13 -0
  91. trueppm_api/apps/integrations/apps.py +59 -0
  92. trueppm_api/apps/integrations/connections.py +806 -0
  93. trueppm_api/apps/integrations/encryption.py +92 -0
  94. trueppm_api/apps/integrations/external_sources.py +837 -0
  95. trueppm_api/apps/integrations/git_automation_services.py +231 -0
  96. trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
  97. trueppm_api/apps/integrations/http.py +403 -0
  98. trueppm_api/apps/integrations/me_work.py +218 -0
  99. trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
  100. trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
  101. trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
  102. trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
  103. trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
  104. trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
  105. trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
  106. trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
  107. trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
  108. trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
  109. trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
  110. trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
  111. trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
  112. trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
  113. trueppm_api/apps/integrations/migrations/__init__.py +0 -0
  114. trueppm_api/apps/integrations/models.py +622 -0
  115. trueppm_api/apps/integrations/notification_channels.py +57 -0
  116. trueppm_api/apps/integrations/opengraph.py +229 -0
  117. trueppm_api/apps/integrations/outgoing.py +201 -0
  118. trueppm_api/apps/integrations/providers.py +519 -0
  119. trueppm_api/apps/integrations/registry.py +345 -0
  120. trueppm_api/apps/integrations/serializers.py +453 -0
  121. trueppm_api/apps/integrations/services.py +171 -0
  122. trueppm_api/apps/integrations/tasks.py +731 -0
  123. trueppm_api/apps/integrations/throttles.py +165 -0
  124. trueppm_api/apps/integrations/urls.py +77 -0
  125. trueppm_api/apps/integrations/views.py +1106 -0
  126. trueppm_api/apps/jiraimport/__init__.py +8 -0
  127. trueppm_api/apps/jiraimport/apps.py +11 -0
  128. trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
  129. trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
  130. trueppm_api/apps/jiraimport/models.py +70 -0
  131. trueppm_api/apps/jiraimport/parser.py +350 -0
  132. trueppm_api/apps/jiraimport/services.py +46 -0
  133. trueppm_api/apps/jiraimport/tasks.py +277 -0
  134. trueppm_api/apps/jiraimport/urls.py +15 -0
  135. trueppm_api/apps/jiraimport/views.py +153 -0
  136. trueppm_api/apps/msproject/__init__.py +0 -0
  137. trueppm_api/apps/msproject/apps.py +11 -0
  138. trueppm_api/apps/msproject/dataclasses.py +153 -0
  139. trueppm_api/apps/msproject/exporter.py +500 -0
  140. trueppm_api/apps/msproject/extended_attributes.py +77 -0
  141. trueppm_api/apps/msproject/importer.py +1095 -0
  142. trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
  143. trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
  144. trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
  145. trueppm_api/apps/msproject/migrations/__init__.py +0 -0
  146. trueppm_api/apps/msproject/models.py +81 -0
  147. trueppm_api/apps/msproject/parser.py +1231 -0
  148. trueppm_api/apps/msproject/serializers.py +136 -0
  149. trueppm_api/apps/msproject/services.py +58 -0
  150. trueppm_api/apps/msproject/tasks.py +436 -0
  151. trueppm_api/apps/msproject/urls.py +39 -0
  152. trueppm_api/apps/msproject/views.py +531 -0
  153. trueppm_api/apps/notifications/__init__.py +7 -0
  154. trueppm_api/apps/notifications/apps.py +15 -0
  155. trueppm_api/apps/notifications/backfill.py +79 -0
  156. trueppm_api/apps/notifications/categories.py +112 -0
  157. trueppm_api/apps/notifications/delivery_limits.py +258 -0
  158. trueppm_api/apps/notifications/digests.py +447 -0
  159. trueppm_api/apps/notifications/email_backend.py +330 -0
  160. trueppm_api/apps/notifications/email_health.py +128 -0
  161. trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
  162. trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
  163. trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
  164. trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
  165. trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
  166. trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
  167. trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
  168. trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
  169. trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
  170. trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
  171. trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
  172. trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
  173. trueppm_api/apps/notifications/migrations/__init__.py +0 -0
  174. trueppm_api/apps/notifications/models.py +1197 -0
  175. trueppm_api/apps/notifications/receivers.py +155 -0
  176. trueppm_api/apps/notifications/schema_migrations.py +56 -0
  177. trueppm_api/apps/notifications/serializers.py +961 -0
  178. trueppm_api/apps/notifications/services.py +1451 -0
  179. trueppm_api/apps/notifications/tasks.py +711 -0
  180. trueppm_api/apps/notifications/throttles.py +98 -0
  181. trueppm_api/apps/notifications/urls.py +97 -0
  182. trueppm_api/apps/notifications/views.py +904 -0
  183. trueppm_api/apps/observability/__init__.py +0 -0
  184. trueppm_api/apps/observability/apps.py +31 -0
  185. trueppm_api/apps/observability/logging.py +213 -0
  186. trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
  187. trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
  188. trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
  189. trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
  190. trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
  191. trueppm_api/apps/observability/migrations/__init__.py +0 -0
  192. trueppm_api/apps/observability/models.py +141 -0
  193. trueppm_api/apps/observability/otel/__init__.py +45 -0
  194. trueppm_api/apps/observability/otel/attributes.py +247 -0
  195. trueppm_api/apps/observability/otel/export_health.py +533 -0
  196. trueppm_api/apps/observability/otel/instrumentation.py +351 -0
  197. trueppm_api/apps/observability/otel/metrics.py +551 -0
  198. trueppm_api/apps/observability/otel/provider.py +511 -0
  199. trueppm_api/apps/observability/otel/request_attributes.py +204 -0
  200. trueppm_api/apps/observability/purge_registry.py +124 -0
  201. trueppm_api/apps/observability/retention.py +139 -0
  202. trueppm_api/apps/observability/selectors.py +969 -0
  203. trueppm_api/apps/observability/serializers.py +142 -0
  204. trueppm_api/apps/observability/services.py +407 -0
  205. trueppm_api/apps/observability/tasks.py +266 -0
  206. trueppm_api/apps/observability/urls.py +27 -0
  207. trueppm_api/apps/observability/views.py +523 -0
  208. trueppm_api/apps/profiles/__init__.py +0 -0
  209. trueppm_api/apps/profiles/apps.py +15 -0
  210. trueppm_api/apps/profiles/constants.py +54 -0
  211. trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
  212. trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
  213. trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
  214. trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
  215. trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
  216. trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
  217. trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
  218. trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
  219. trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
  220. trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
  221. trueppm_api/apps/profiles/migrations/__init__.py +0 -0
  222. trueppm_api/apps/profiles/models.py +276 -0
  223. trueppm_api/apps/profiles/serializers.py +199 -0
  224. trueppm_api/apps/profiles/services.py +466 -0
  225. trueppm_api/apps/profiles/views.py +124 -0
  226. trueppm_api/apps/projects/__init__.py +0 -0
  227. trueppm_api/apps/projects/actual_date_rules.py +163 -0
  228. trueppm_api/apps/projects/amend.py +133 -0
  229. trueppm_api/apps/projects/apps.py +38 -0
  230. trueppm_api/apps/projects/asset_feed.py +459 -0
  231. trueppm_api/apps/projects/asset_views.py +317 -0
  232. trueppm_api/apps/projects/attachment_policy.py +278 -0
  233. trueppm_api/apps/projects/authentication.py +449 -0
  234. trueppm_api/apps/projects/backfill.py +287 -0
  235. trueppm_api/apps/projects/backlog_services.py +324 -0
  236. trueppm_api/apps/projects/backlog_views.py +302 -0
  237. trueppm_api/apps/projects/batch_operation_views.py +264 -0
  238. trueppm_api/apps/projects/blocker_services.py +510 -0
  239. trueppm_api/apps/projects/board_activity.py +462 -0
  240. trueppm_api/apps/projects/board_activity_views.py +186 -0
  241. trueppm_api/apps/projects/board_lanes.py +134 -0
  242. trueppm_api/apps/projects/bulk_settings.py +194 -0
  243. trueppm_api/apps/projects/bundled_templates.py +215 -0
  244. trueppm_api/apps/projects/calendar_settings.py +180 -0
  245. trueppm_api/apps/projects/ceremony_views.py +244 -0
  246. trueppm_api/apps/projects/commit_moment.py +204 -0
  247. trueppm_api/apps/projects/config_notice.py +1325 -0
  248. trueppm_api/apps/projects/custom_field_values.py +266 -0
  249. trueppm_api/apps/projects/decisions_services.py +93 -0
  250. trueppm_api/apps/projects/decisions_views.py +94 -0
  251. trueppm_api/apps/projects/estimation_scale.py +126 -0
  252. trueppm_api/apps/projects/export_bundle.py +353 -0
  253. trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
  254. trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
  255. trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
  256. trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
  257. trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
  258. trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
  259. trueppm_api/apps/projects/inbound_sync.py +555 -0
  260. trueppm_api/apps/projects/iteration_label.py +85 -0
  261. trueppm_api/apps/projects/lifecycle.py +191 -0
  262. trueppm_api/apps/projects/management/__init__.py +0 -0
  263. trueppm_api/apps/projects/management/commands/__init__.py +0 -0
  264. trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
  265. trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
  266. trueppm_api/apps/projects/management/commands/export_program.py +50 -0
  267. trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
  268. trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
  269. trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
  270. trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
  271. trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
  272. trueppm_api/apps/projects/mcp_settings.py +305 -0
  273. trueppm_api/apps/projects/methodology.py +178 -0
  274. trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
  275. trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
  276. trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
  277. trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
  278. trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
  279. trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
  280. trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
  281. trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
  282. trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
  283. trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
  284. trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
  285. trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
  286. trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
  287. trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
  288. trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
  289. trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
  290. trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
  291. trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
  292. trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
  293. trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
  294. trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
  295. trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
  296. trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
  297. trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
  298. trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
  299. trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
  300. trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
  301. trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
  302. trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
  303. trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
  304. trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
  305. trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
  306. trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
  307. trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
  308. trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
  309. trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
  310. trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
  311. trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
  312. trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
  313. trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
  314. trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
  315. trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
  316. trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
  317. trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
  318. trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
  319. trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
  320. trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
  321. trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
  322. trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
  323. trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
  324. trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
  325. trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
  326. trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
  327. trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
  328. trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
  329. trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
  330. trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
  331. trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
  332. trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
  333. trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
  334. trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
  335. trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
  336. trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
  337. trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
  338. trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
  339. trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
  340. trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
  341. trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
  342. trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
  343. trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
  344. trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
  345. trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
  346. trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
  347. trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
  348. trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
  349. trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
  350. trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
  351. trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
  352. trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
  353. trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
  354. trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
  355. trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
  356. trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
  357. trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
  358. trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
  359. trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
  360. trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
  361. trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
  362. trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
  363. trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
  364. trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
  365. trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
  366. trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
  367. trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
  368. trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
  369. trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
  370. trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
  371. trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
  372. trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
  373. trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
  374. trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
  375. trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
  376. trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
  377. trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
  378. trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
  379. trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
  380. trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
  381. trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
  382. trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
  383. trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
  384. trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
  385. trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
  386. trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
  387. trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
  388. trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
  389. trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
  390. trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
  391. trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
  392. trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
  393. trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
  394. trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
  395. trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
  396. trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
  397. trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
  398. trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
  399. trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
  400. trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
  401. trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
  402. trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
  403. trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
  404. trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
  405. trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
  406. trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
  407. trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
  408. trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
  409. trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
  410. trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
  411. trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
  412. trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
  413. trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
  414. trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
  415. trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
  416. trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
  417. trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
  418. trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
  419. trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
  420. trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
  421. trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
  422. trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
  423. trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
  424. trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
  425. trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
  426. trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
  427. trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
  428. trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
  429. trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
  430. trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
  431. trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
  432. trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
  433. trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
  434. trueppm_api/apps/projects/migrations/__init__.py +0 -0
  435. trueppm_api/apps/projects/models.py +8946 -0
  436. trueppm_api/apps/projects/poker_services.py +154 -0
  437. trueppm_api/apps/projects/poker_views.py +398 -0
  438. trueppm_api/apps/projects/product_backlog_services.py +450 -0
  439. trueppm_api/apps/projects/program_label_services.py +94 -0
  440. trueppm_api/apps/projects/program_label_views.py +189 -0
  441. trueppm_api/apps/projects/program_rollup.py +575 -0
  442. trueppm_api/apps/projects/program_schedule.py +745 -0
  443. trueppm_api/apps/projects/program_views.py +3220 -0
  444. trueppm_api/apps/projects/project_templates.py +422 -0
  445. trueppm_api/apps/projects/receivers.py +234 -0
  446. trueppm_api/apps/projects/refusal_codes.py +140 -0
  447. trueppm_api/apps/projects/reorder_services.py +145 -0
  448. trueppm_api/apps/projects/restructure_hooks.py +54 -0
  449. trueppm_api/apps/projects/retro_board_services.py +368 -0
  450. trueppm_api/apps/projects/retro_services.py +285 -0
  451. trueppm_api/apps/projects/risk_import.py +396 -0
  452. trueppm_api/apps/projects/row_identity.py +113 -0
  453. trueppm_api/apps/projects/schema_migrations.py +228 -0
  454. trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
  455. trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
  456. trueppm_api/apps/projects/seed/__init__.py +61 -0
  457. trueppm_api/apps/projects/seed/exporter.py +1644 -0
  458. trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
  459. trueppm_api/apps/projects/seed/importer.py +2390 -0
  460. trueppm_api/apps/projects/seed/reanchor.py +551 -0
  461. trueppm_api/apps/projects/seed/reldates.py +126 -0
  462. trueppm_api/apps/projects/seed/replace.py +126 -0
  463. trueppm_api/apps/projects/seed/replay.py +1691 -0
  464. trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
  465. trueppm_api/apps/projects/seed/samples.py +427 -0
  466. trueppm_api/apps/projects/seed/validation.py +1149 -0
  467. trueppm_api/apps/projects/serializers.py +12310 -0
  468. trueppm_api/apps/projects/services.py +6966 -0
  469. trueppm_api/apps/projects/share_serializers.py +132 -0
  470. trueppm_api/apps/projects/share_services.py +325 -0
  471. trueppm_api/apps/projects/share_views.py +374 -0
  472. trueppm_api/apps/projects/sharing_settings.py +187 -0
  473. trueppm_api/apps/projects/signal_privacy_services.py +909 -0
  474. trueppm_api/apps/projects/signal_privacy_views.py +476 -0
  475. trueppm_api/apps/projects/signals.py +132 -0
  476. trueppm_api/apps/projects/slip_conflict.py +266 -0
  477. trueppm_api/apps/projects/sprint_cadence.py +453 -0
  478. trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
  479. trueppm_api/apps/projects/standup.py +304 -0
  480. trueppm_api/apps/projects/standup_views.py +99 -0
  481. trueppm_api/apps/projects/structural_operation_services.py +727 -0
  482. trueppm_api/apps/projects/structural_operation_views.py +311 -0
  483. trueppm_api/apps/projects/surface_visibility.py +126 -0
  484. trueppm_api/apps/projects/task_batch_services.py +379 -0
  485. trueppm_api/apps/projects/task_bulk.py +1152 -0
  486. trueppm_api/apps/projects/task_classification.py +561 -0
  487. trueppm_api/apps/projects/task_classification_defaults.py +95 -0
  488. trueppm_api/apps/projects/task_duration_settings.py +142 -0
  489. trueppm_api/apps/projects/task_grouping.py +754 -0
  490. trueppm_api/apps/projects/tasks.py +1917 -0
  491. trueppm_api/apps/projects/template_divergence.py +168 -0
  492. trueppm_api/apps/projects/template_services.py +160 -0
  493. trueppm_api/apps/projects/template_tasks.py +258 -0
  494. trueppm_api/apps/projects/template_views.py +799 -0
  495. trueppm_api/apps/projects/throttles.py +311 -0
  496. trueppm_api/apps/projects/urls.py +1011 -0
  497. trueppm_api/apps/projects/utilization.py +885 -0
  498. trueppm_api/apps/projects/views.py +20572 -0
  499. trueppm_api/apps/projects/wbs_paths.py +119 -0
  500. trueppm_api/apps/resources/__init__.py +0 -0
  501. trueppm_api/apps/resources/apps.py +8 -0
  502. trueppm_api/apps/resources/capacity.py +121 -0
  503. trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
  504. trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
  505. trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
  506. trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
  507. trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
  508. trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
  509. trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
  510. trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
  511. trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
  512. trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
  513. trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
  514. trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
  515. trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
  516. trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
  517. trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
  518. trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
  519. trueppm_api/apps/resources/migrations/__init__.py +0 -0
  520. trueppm_api/apps/resources/models.py +298 -0
  521. trueppm_api/apps/resources/serializers.py +426 -0
  522. trueppm_api/apps/resources/services.py +465 -0
  523. trueppm_api/apps/resources/urls.py +28 -0
  524. trueppm_api/apps/resources/views.py +1749 -0
  525. trueppm_api/apps/scheduling/__init__.py +0 -0
  526. trueppm_api/apps/scheduling/apps.py +153 -0
  527. trueppm_api/apps/scheduling/calendars.py +119 -0
  528. trueppm_api/apps/scheduling/deadletter.py +91 -0
  529. trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
  530. trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
  531. trueppm_api/apps/scheduling/graph_guard.py +263 -0
  532. trueppm_api/apps/scheduling/management/__init__.py +0 -0
  533. trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
  534. trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
  535. trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
  536. trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
  537. trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
  538. trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
  539. trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
  540. trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
  541. trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
  542. trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
  543. trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
  544. trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
  545. trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
  546. trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
  547. trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
  548. trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
  549. trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
  550. trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
  551. trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
  552. trueppm_api/apps/scheduling/models.py +516 -0
  553. trueppm_api/apps/scheduling/receivers.py +160 -0
  554. trueppm_api/apps/scheduling/risk_premium.py +283 -0
  555. trueppm_api/apps/scheduling/serializers.py +564 -0
  556. trueppm_api/apps/scheduling/services.py +1182 -0
  557. trueppm_api/apps/scheduling/signals.py +56 -0
  558. trueppm_api/apps/scheduling/tasks.py +1984 -0
  559. trueppm_api/apps/scheduling/telemetry.py +80 -0
  560. trueppm_api/apps/scheduling/units.py +35 -0
  561. trueppm_api/apps/scheduling/urls.py +57 -0
  562. trueppm_api/apps/scheduling/views.py +2564 -0
  563. trueppm_api/apps/sso/__init__.py +0 -0
  564. trueppm_api/apps/sso/apps.py +19 -0
  565. trueppm_api/apps/sso/extensions.py +130 -0
  566. trueppm_api/apps/sso/management/__init__.py +0 -0
  567. trueppm_api/apps/sso/management/commands/__init__.py +0 -0
  568. trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
  569. trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
  570. trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
  571. trueppm_api/apps/sso/migrations/__init__.py +0 -0
  572. trueppm_api/apps/sso/models.py +148 -0
  573. trueppm_api/apps/sso/serializers.py +437 -0
  574. trueppm_api/apps/sso/services.py +1385 -0
  575. trueppm_api/apps/sso/urls.py +47 -0
  576. trueppm_api/apps/sso/views.py +935 -0
  577. trueppm_api/apps/sync/__init__.py +0 -0
  578. trueppm_api/apps/sync/apps.py +15 -0
  579. trueppm_api/apps/sync/backfill.py +208 -0
  580. trueppm_api/apps/sync/broadcast.py +320 -0
  581. trueppm_api/apps/sync/conflict.py +262 -0
  582. trueppm_api/apps/sync/consumers.py +471 -0
  583. trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
  584. trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
  585. trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
  586. trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
  587. trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
  588. trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
  589. trueppm_api/apps/sync/migrations/__init__.py +0 -0
  590. trueppm_api/apps/sync/models.py +225 -0
  591. trueppm_api/apps/sync/pagination.py +231 -0
  592. trueppm_api/apps/sync/sequence.py +371 -0
  593. trueppm_api/apps/sync/serializers.py +687 -0
  594. trueppm_api/apps/sync/tasks.py +260 -0
  595. trueppm_api/apps/sync/throttles.py +175 -0
  596. trueppm_api/apps/sync/upload.py +501 -0
  597. trueppm_api/apps/sync/urls.py +17 -0
  598. trueppm_api/apps/sync/views.py +930 -0
  599. trueppm_api/apps/sync/ws_auth.py +193 -0
  600. trueppm_api/apps/taskruns/__init__.py +0 -0
  601. trueppm_api/apps/taskruns/apps.py +11 -0
  602. trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
  603. trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
  604. trueppm_api/apps/taskruns/models.py +68 -0
  605. trueppm_api/apps/taskruns/serializers.py +92 -0
  606. trueppm_api/apps/taskruns/tasks.py +60 -0
  607. trueppm_api/apps/taskruns/tracker.py +286 -0
  608. trueppm_api/apps/taskruns/urls.py +46 -0
  609. trueppm_api/apps/taskruns/views.py +195 -0
  610. trueppm_api/apps/teams/__init__.py +8 -0
  611. trueppm_api/apps/teams/apps.py +16 -0
  612. trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
  613. trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
  614. trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
  615. trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
  616. trueppm_api/apps/teams/migrations/__init__.py +0 -0
  617. trueppm_api/apps/teams/models.py +145 -0
  618. trueppm_api/apps/teams/permissions.py +149 -0
  619. trueppm_api/apps/teams/serializers.py +95 -0
  620. trueppm_api/apps/teams/services.py +434 -0
  621. trueppm_api/apps/teams/signals.py +61 -0
  622. trueppm_api/apps/teams/urls.py +34 -0
  623. trueppm_api/apps/teams/views.py +201 -0
  624. trueppm_api/apps/timetracking/__init__.py +0 -0
  625. trueppm_api/apps/timetracking/apps.py +9 -0
  626. trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
  627. trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
  628. trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
  629. trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
  630. trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
  631. trueppm_api/apps/timetracking/models.py +178 -0
  632. trueppm_api/apps/timetracking/serializers.py +175 -0
  633. trueppm_api/apps/timetracking/services.py +117 -0
  634. trueppm_api/apps/timetracking/urls.py +47 -0
  635. trueppm_api/apps/timetracking/views.py +526 -0
  636. trueppm_api/apps/webhooks/__init__.py +0 -0
  637. trueppm_api/apps/webhooks/apps.py +8 -0
  638. trueppm_api/apps/webhooks/backfill.py +90 -0
  639. trueppm_api/apps/webhooks/dispatch.py +144 -0
  640. trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
  641. trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
  642. trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
  643. trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
  644. trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
  645. trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
  646. trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
  647. trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
  648. trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
  649. trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
  650. trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
  651. trueppm_api/apps/webhooks/models.py +372 -0
  652. trueppm_api/apps/webhooks/serializers.py +318 -0
  653. trueppm_api/apps/webhooks/tasks.py +663 -0
  654. trueppm_api/apps/webhooks/urls.py +22 -0
  655. trueppm_api/apps/webhooks/views.py +383 -0
  656. trueppm_api/apps/workflow_engine/__init__.py +0 -0
  657. trueppm_api/apps/workflow_engine/apps.py +23 -0
  658. trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
  659. trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
  660. trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
  661. trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
  662. trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
  663. trueppm_api/apps/workflow_engine/models.py +265 -0
  664. trueppm_api/apps/workflow_engine/tasks.py +368 -0
  665. trueppm_api/apps/workspace/__init__.py +0 -0
  666. trueppm_api/apps/workspace/apps.py +10 -0
  667. trueppm_api/apps/workspace/backfill.py +111 -0
  668. trueppm_api/apps/workspace/export.py +242 -0
  669. trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
  670. trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
  671. trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
  672. trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
  673. trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
  674. trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
  675. trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
  676. trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
  677. trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
  678. trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
  679. trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
  680. trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
  681. trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
  682. trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
  683. trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
  684. trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
  685. trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
  686. trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
  687. trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
  688. trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
  689. trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
  690. trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
  691. trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
  692. trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
  693. trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
  694. trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
  695. trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
  696. trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
  697. trueppm_api/apps/workspace/migrations/__init__.py +0 -0
  698. trueppm_api/apps/workspace/models.py +857 -0
  699. trueppm_api/apps/workspace/permissions.py +187 -0
  700. trueppm_api/apps/workspace/serializers.py +590 -0
  701. trueppm_api/apps/workspace/services.py +919 -0
  702. trueppm_api/apps/workspace/signals.py +37 -0
  703. trueppm_api/apps/workspace/tasks.py +555 -0
  704. trueppm_api/apps/workspace/urls.py +113 -0
  705. trueppm_api/apps/workspace/views.py +1502 -0
  706. trueppm_api/asgi.py +42 -0
  707. trueppm_api/celery.py +31 -0
  708. trueppm_api/core/__init__.py +0 -0
  709. trueppm_api/core/admin_site.py +206 -0
  710. trueppm_api/core/auth_views.py +916 -0
  711. trueppm_api/core/constant_time.py +51 -0
  712. trueppm_api/core/csp.py +54 -0
  713. trueppm_api/core/db.py +87 -0
  714. trueppm_api/core/db_session.py +37 -0
  715. trueppm_api/core/exception_handlers.py +157 -0
  716. trueppm_api/core/export_downloads.py +56 -0
  717. trueppm_api/core/extension_providers.py +56 -0
  718. trueppm_api/core/extension_signals.py +147 -0
  719. trueppm_api/core/idempotent.py +260 -0
  720. trueppm_api/core/metadata.py +42 -0
  721. trueppm_api/core/middleware.py +51 -0
  722. trueppm_api/core/openapi.py +949 -0
  723. trueppm_api/core/password_reset.py +500 -0
  724. trueppm_api/core/protect_conflict.py +100 -0
  725. trueppm_api/core/ratelimit.py +148 -0
  726. trueppm_api/core/redis_throttle.py +82 -0
  727. trueppm_api/core/refresh_cookie_policy.py +156 -0
  728. trueppm_api/core/request_body.py +86 -0
  729. trueppm_api/core/security_checks.py +884 -0
  730. trueppm_api/core/storage_config.py +105 -0
  731. trueppm_api/core/text_decode.py +168 -0
  732. trueppm_api/core/throttling.py +344 -0
  733. trueppm_api/core/valkey.py +214 -0
  734. trueppm_api/core/valkey_cache.py +66 -0
  735. trueppm_api/core/valkey_checks.py +104 -0
  736. trueppm_api/core/valkey_config.py +39 -0
  737. trueppm_api/core/worker_broker_wait.py +149 -0
  738. trueppm_api/core/worker_heartbeat.py +134 -0
  739. trueppm_api/fields.py +36 -0
  740. trueppm_api/permissions.py +30 -0
  741. trueppm_api/py.typed +0 -0
  742. trueppm_api/routing.py +11 -0
  743. trueppm_api/settings/__init__.py +0 -0
  744. trueppm_api/settings/base.py +2455 -0
  745. trueppm_api/settings/dev.py +273 -0
  746. trueppm_api/settings/prod.py +228 -0
  747. trueppm_api/urls.py +153 -0
  748. trueppm_api/workflows/__init__.py +38 -0
  749. trueppm_api/workflows/backends/__init__.py +6 -0
  750. trueppm_api/workflows/backends/default.py +297 -0
  751. trueppm_api/workflows/consumers/__init__.py +11 -0
  752. trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
  753. trueppm_api/workflows/interface.py +148 -0
  754. trueppm_api/workflows/registry.py +106 -0
  755. trueppm_api/workflows/services.py +90 -0
  756. trueppm_api/wsgi.py +11 -0
  757. trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
  758. trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
  759. trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
@@ -0,0 +1,462 @@
1
+ """Board-level activity feed aggregator (ADR-0160, #325).
2
+
3
+ A read-only aggregator that joins existing change-history sources into one
4
+ time-ordered, board-scoped feed of card mutations. No new model, no migration —
5
+ every event already has a durable home:
6
+
7
+ * ``HistoricalTask`` (django-simple-history on ``Task``) — field changes diffed
8
+ against the same task's immediately-prior row over a curated board-relevant
9
+ allowlist (``task_created`` / ``task_updated`` / ``task_deleted``), plus the
10
+ ``sprint`` delta surfaced as a first-class ``entered_sprint`` / ``exited_sprint``
11
+ / ``moved_sprint`` event (the #325 AC's mid-sprint scope events).
12
+ * ``TaskComment`` creates — ``comment_added``.
13
+
14
+ The feed is built in Python (the ``TaskHistoryView`` precedent paginates a Python
15
+ list) but **bounded**: each source is queried for events older than the ``until``
16
+ cursor, capped at ``OVERFETCH * limit`` rows, and the three lists are merge-sorted
17
+ by timestamp DESC. Pagination is keyset-on-timestamp via ``until`` (the client passes
18
+ the returned ``next_until`` back) — no offset rebuild, no cursor-on-non-unique-leading
19
+ trap. ``HistoricalTask`` diffs are computed within the fetched batch (no per-row
20
+ ``prev_record`` query → no N+1); the documented edge is that a change whose prior row
21
+ fell outside the batch surfaces with an empty ``changes`` list.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from collections import defaultdict
27
+ from datetime import datetime
28
+ from typing import TYPE_CHECKING, Any
29
+
30
+ from django.contrib.auth import get_user_model
31
+ from django.utils import timezone
32
+
33
+ from trueppm_api.apps.access.models import Role
34
+
35
+ if TYPE_CHECKING:
36
+ from trueppm_api.apps.projects.models import Project
37
+
38
+ # Board-relevant Task fields surfaced as field-change deltas. CPM outputs and sync
39
+ # internals are already excluded from HistoricalTask; this is a further curation to
40
+ # the fields a board reader cares about (not the full per-task drawer history).
41
+ # ``sprint`` is deliberately NOT here — a sprint move is its own first-class event.
42
+ _BOARD_DIFF_FIELDS: tuple[str, ...] = (
43
+ "name",
44
+ "status",
45
+ "percent_complete",
46
+ "story_points",
47
+ "remaining_points",
48
+ "assignee_id",
49
+ )
50
+
51
+ # Cost/budget delta fields hidden from below-MEMBER readers (ADR-0160 RBAC). There
52
+ # are no cost fields on Task yet (intentionally absent until the cost model #73);
53
+ # the set is empty today so the gate is a no-op, but it is the live seam the moment
54
+ # cost fields land — the AC ("Viewer doesn't see cost-field deltas") is satisfied
55
+ # structurally, not deferred.
56
+ _COST_FIELDS: frozenset[str] = frozenset()
57
+
58
+ _TASK_EVENT_TYPES: frozenset[str] = frozenset({"task_created", "task_updated", "task_deleted"})
59
+ _SPRINT_EVENT_TYPES: frozenset[str] = frozenset({"entered_sprint", "exited_sprint", "moved_sprint"})
60
+ _COMMENT_EVENT_TYPES: frozenset[str] = frozenset({"comment_added"})
61
+ EVENT_TYPES: frozenset[str] = _TASK_EVENT_TYPES | _SPRINT_EVENT_TYPES | _COMMENT_EVENT_TYPES
62
+
63
+ _OVERFETCH = 4 # rows fetched per source = OVERFETCH * limit (bounded scan)
64
+ DEFAULT_LIMIT = 50
65
+ MAX_LIMIT = 100
66
+
67
+
68
+ def _scalar(value: Any) -> str | None:
69
+ return str(value) if value is not None else None
70
+
71
+
72
+ def build_board_activity(
73
+ project: Project,
74
+ *,
75
+ until: datetime | None = None,
76
+ since: datetime | None = None,
77
+ actor_id: Any = None,
78
+ event_types: set[str] | None = None,
79
+ limit: int = DEFAULT_LIMIT,
80
+ role: int | None = None,
81
+ sprint_id: str | None = None,
82
+ ) -> dict[str, Any]:
83
+ """Build one page of the board activity feed (ADR-0160).
84
+
85
+ Args:
86
+ project: the board's project.
87
+ until: keyset cursor — return events strictly older than this (default now).
88
+ since: optional lower time bound (inclusive).
89
+ actor_id: optional filter to one actor (user id).
90
+ event_types: optional set of ``EVENT_TYPES`` to include (default all).
91
+ limit: page size (clamped to ``[1, MAX_LIMIT]``).
92
+ role: the requester's project ``Role`` ordinal — drives cost-field gating.
93
+ sprint_id: optional sprint scope (ADR-0412, #1946) — narrow to events whose
94
+ task is currently in this sprint OR that reference this sprint directly
95
+ (an entered/exited/moved event whose old-or-new sprint is this one), so a
96
+ removal stays visible in the sprint it left even after the task moves on.
97
+
98
+ Returns:
99
+ ``{"results": [event, ...], "next_until": <iso str | None>}`` where each event
100
+ is ``{id, event_type, actor, actor_id, timestamp, task_id, task_name,
101
+ changes: [{field, old, new}], sprint_id, scope_change_status}``
102
+ (``scope_change_status`` is the SprintScopeChange accept-gate status on
103
+ ``entered_sprint`` events, else null). ``next_until`` is the oldest
104
+ returned timestamp (pass it back as ``until`` for the next page), or ``None``
105
+ when the window is exhausted.
106
+ """
107
+ limit = max(1, min(int(limit), MAX_LIMIT))
108
+ if until is None:
109
+ until = timezone.now()
110
+ wanted = event_types if event_types else EVENT_TYPES
111
+ cap = _OVERFETCH * limit
112
+
113
+ events: list[dict[str, Any]] = []
114
+ if wanted & (_TASK_EVENT_TYPES | _SPRINT_EVENT_TYPES):
115
+ events.extend(_history_events(project, until, since, wanted, cap, role))
116
+ if wanted & _COMMENT_EVENT_TYPES:
117
+ events.extend(_comment_events(project, until, since, cap))
118
+
119
+ # Sprint scope (ADR-0412, #1946) is applied AFTER the per-source fetch for the
120
+ # same reason as the actor post-filter below: pushing ``task__sprint_id`` into
121
+ # the HistoricalTask query would drop the immediately-prior row a diff needs, and
122
+ # it could not express the "event references the sprint it *left*" case that keeps
123
+ # a removal visible in the sprint it was pulled from.
124
+ if sprint_id is not None:
125
+ events = _filter_by_sprint(events, str(sprint_id))
126
+
127
+ # Actor filtering is applied AFTER the per-source fetch, not pushed into the
128
+ # HistoricalTask query: filtering the source rows by actor would drop the
129
+ # immediately-prior row a diff needs (a status change by A diffs against B's
130
+ # prior save), leaving the actor's change with no computable delta.
131
+ if actor_id is not None:
132
+ wanted_actor = str(actor_id)
133
+ events = [e for e in events if e["actor_id"] == wanted_actor]
134
+
135
+ # Merge-sort DESC by timestamp; the stable id breaks exact-timestamp ties so the
136
+ # ordering is deterministic across requests.
137
+ events.sort(key=lambda e: (e["timestamp"], e["id"]), reverse=True)
138
+
139
+ page = events[:limit]
140
+ next_until: str | None = None
141
+ if len(events) > limit and page:
142
+ # More rows beyond this page existed in the fetched window — hand the client
143
+ # the oldest returned timestamp to resume from (keyset).
144
+ next_until = page[-1]["timestamp"].isoformat()
145
+
146
+ return {
147
+ "results": [_serialize_event(e) for e in page],
148
+ "next_until": next_until,
149
+ }
150
+
151
+
152
+ def _filter_by_sprint(events: list[dict[str, Any]], sprint_id: str) -> list[dict[str, Any]]:
153
+ """Narrow the feed to one sprint's scope (ADR-0412, #1946).
154
+
155
+ An event is in scope when its task is CURRENTLY in ``sprint_id`` (``task.sprint_id``,
156
+ read live in one bulk query — not from the historical row, so the feed tracks where
157
+ a task *is now*) OR the event itself references ``sprint_id`` directly (an
158
+ entered/exited/moved event whose old-or-new sprint is this one). The second clause is
159
+ what keeps a *removal* visible in the sprint it left: an ``exited_sprint`` carries the
160
+ left sprint as its old id even though the task's current sprint is now null/elsewhere.
161
+ """
162
+ task_ids = {e["task_id"] for e in events}
163
+ current = _current_sprint_map(task_ids)
164
+ out: list[dict[str, Any]] = []
165
+ for e in events:
166
+ if current.get(e["task_id"]) == sprint_id or sprint_id in (
167
+ e.get("_old_sprint_id"),
168
+ e.get("_new_sprint_id"),
169
+ ):
170
+ out.append(e)
171
+ return out
172
+
173
+
174
+ def _current_sprint_map(task_ids: set[str]) -> dict[str, str | None]:
175
+ """Map each task id (str) to its CURRENT ``sprint_id`` (str or None), in one query."""
176
+ if not task_ids:
177
+ return {}
178
+ from trueppm_api.apps.projects.models import Task
179
+
180
+ return {
181
+ str(pk): (str(sid) if sid is not None else None)
182
+ for pk, sid in Task.objects.filter(pk__in=task_ids).values_list("pk", "sprint_id")
183
+ }
184
+
185
+
186
+ def _history_events(
187
+ project: Project,
188
+ until: datetime,
189
+ since: datetime | None,
190
+ wanted: set[str] | frozenset[str],
191
+ cap: int,
192
+ role: int | None,
193
+ ) -> list[dict[str, Any]]:
194
+ """Field-change + sprint-transition events from HistoricalTask (bounded batch)."""
195
+ from trueppm_api.apps.projects.models import Task
196
+
197
+ HistoricalTask = Task.history.model
198
+
199
+ qs = HistoricalTask.objects.filter(project_id=project.pk, history_date__lt=until)
200
+ if since is not None:
201
+ qs = qs.filter(history_date__gte=since)
202
+ rows = list(qs.order_by("-history_date")[:cap])
203
+
204
+ # Group newest-first per task so each row can diff against the next-older row in
205
+ # the batch without a per-row prev_record query (no N+1).
206
+ by_task: dict[Any, list[Any]] = defaultdict(list)
207
+ for row in rows:
208
+ by_task[row.id].append(row)
209
+
210
+ # Resolve the user + sprint references that appear, in two bulk queries.
211
+ user_ids = {r.history_user_id for r in rows if r.history_user_id is not None}
212
+ user_ids |= {r.assignee_id for r in rows if r.assignee_id is not None}
213
+ sprint_ids = {r.sprint_id for r in rows if r.sprint_id is not None}
214
+ user_names = _user_name_map(user_ids)
215
+ sprint_names = _sprint_name_map(sprint_ids)
216
+
217
+ hide_cost = role is not None and role < Role.MEMBER
218
+ events: list[dict[str, Any]] = []
219
+ for group in by_task.values():
220
+ for i, rec in enumerate(group):
221
+ older = group[i + 1] if i + 1 < len(group) else None
222
+ events.extend(
223
+ _events_from_record(rec, older, wanted, user_names, sprint_names, hide_cost)
224
+ )
225
+ _attach_scope_change_status(events)
226
+ return events
227
+
228
+
229
+ def _attach_scope_change_status(events: list[dict[str, Any]]) -> None:
230
+ """Enrich ``entered_sprint`` events with the ``SprintScopeChange`` accept-gate status.
231
+
232
+ A *post-activation* mid-sprint injection records a ``SprintScopeChange`` (ADR-0102)
233
+ whose ``status`` (``pending``/``accepted``/``rejected``) is the accept-gate outcome for
234
+ the *same* "task entered sprint X" fact the ``HistoricalTask`` sprint delta already
235
+ yields — which is why ADR-0160 sources the event from history (not also
236
+ ``SprintScopeChange``) to avoid double-counting, and deferred surfacing the *status* to
237
+ this enrichment (ADR-0160 Amendment B3, #1264).
238
+
239
+ Stamps ``scope_change_status`` onto each ``entered_sprint`` event via **one batched**
240
+ query keyed by ``(task_id, sprint_id)`` — index-covered by ``scope_change_task_sprint_idx``
241
+ — leaving it ``None`` for a pre-activation entry (no scope-change row). The status is not
242
+ cost-gated, so it is Viewer-readable like the rest of the feed. Mutates ``events`` in
243
+ place; a no-op when the batch holds no ``entered_sprint`` event.
244
+ """
245
+ keys = {
246
+ (e["task_id"], e["sprint_id"])
247
+ for e in events
248
+ if e["event_type"] == "entered_sprint" and e.get("sprint_id")
249
+ }
250
+ if not keys:
251
+ return
252
+
253
+ from trueppm_api.apps.projects.models import SprintScopeChange
254
+
255
+ status_map: dict[tuple[str, str], str] = {}
256
+ # Ordered ascending so the latest row wins on the rare re-injection of one task into the
257
+ # same sprint (multiple rows per (task, sprint)); the cross-product filter overfetches
258
+ # at most distinct-tasks × distinct-sprints rows, but only exact pairs are read back.
259
+ rows = (
260
+ SprintScopeChange.objects.filter(
261
+ task_id__in={k[0] for k in keys},
262
+ sprint_id__in={k[1] for k in keys},
263
+ )
264
+ .order_by("added_at")
265
+ .values("task_id", "sprint_id", "status")
266
+ )
267
+ for row in rows:
268
+ status_map[(str(row["task_id"]), str(row["sprint_id"]))] = row["status"]
269
+
270
+ for e in events:
271
+ if e["event_type"] == "entered_sprint":
272
+ # entered_sprint always carries a non-null sprint_id (see _sprint_event).
273
+ e["scope_change_status"] = status_map.get((e["task_id"], e["sprint_id"]))
274
+
275
+
276
+ def _events_from_record(
277
+ rec: Any,
278
+ older: Any,
279
+ wanted: set[str] | frozenset[str],
280
+ user_names: dict[Any, str],
281
+ sprint_names: dict[Any, str],
282
+ hide_cost: bool,
283
+ ) -> list[dict[str, Any]]:
284
+ """Derive the 0..2 board events a single HistoricalTask row contributes."""
285
+ out: list[dict[str, Any]] = []
286
+ actor_id = rec.history_user_id
287
+ base = {
288
+ "actor": user_names.get(actor_id),
289
+ "actor_id": str(actor_id) if actor_id is not None else None,
290
+ "timestamp": rec.history_date,
291
+ "task_id": str(rec.id),
292
+ "task_name": rec.name,
293
+ }
294
+
295
+ # django-simple-history history_type: "+" created, "~" changed, "-" deleted.
296
+ if rec.history_type == "+":
297
+ if "task_created" in wanted:
298
+ out.append(
299
+ {
300
+ **base,
301
+ "id": f"hist:{rec.history_id}",
302
+ "event_type": "task_created",
303
+ "changes": [],
304
+ }
305
+ )
306
+ return out
307
+ if rec.history_type == "-":
308
+ if "task_deleted" in wanted:
309
+ out.append(
310
+ {
311
+ **base,
312
+ "id": f"hist:{rec.history_id}",
313
+ "event_type": "task_deleted",
314
+ "changes": [],
315
+ }
316
+ )
317
+ return out
318
+
319
+ # A "~" (changed) row. With no prior-in-batch we cannot diff, so it is dropped
320
+ # (the documented deep-page edge — a board reader sees no change without a delta).
321
+ if older is None:
322
+ return out
323
+
324
+ # Sprint transition is its own first-class event (held out of the field diff).
325
+ if wanted & _SPRINT_EVENT_TYPES:
326
+ sprint_event = _sprint_event(rec, older, base, sprint_names)
327
+ if sprint_event is not None:
328
+ out.append(sprint_event)
329
+
330
+ if "task_updated" in wanted:
331
+ changes = _field_changes(rec, older, user_names, hide_cost)
332
+ if changes:
333
+ out.append(
334
+ {
335
+ **base,
336
+ "id": f"hist:{rec.history_id}",
337
+ "event_type": "task_updated",
338
+ "changes": changes,
339
+ }
340
+ )
341
+ return out
342
+
343
+
344
+ def _field_changes(
345
+ rec: Any, older: Any, user_names: dict[Any, str], hide_cost: bool
346
+ ) -> list[dict[str, Any]]:
347
+ """Board-allowlist field deltas between two HistoricalTask rows."""
348
+ changes: list[dict[str, Any]] = []
349
+ for field in _BOARD_DIFF_FIELDS:
350
+ if hide_cost and field in _COST_FIELDS:
351
+ continue
352
+ new_val = getattr(rec, field, None)
353
+ old_val = getattr(older, field, None)
354
+ if new_val == old_val:
355
+ continue
356
+ if field == "assignee_id":
357
+ changes.append(
358
+ {
359
+ "field": "assignee",
360
+ "old": user_names.get(old_val) if old_val is not None else None,
361
+ "new": user_names.get(new_val) if new_val is not None else None,
362
+ }
363
+ )
364
+ else:
365
+ changes.append({"field": field, "old": _scalar(old_val), "new": _scalar(new_val)})
366
+ return changes
367
+
368
+
369
+ def _sprint_event(
370
+ rec: Any, older: Any, base: dict[str, Any], sprint_names: dict[Any, str]
371
+ ) -> dict[str, Any] | None:
372
+ """An entered/exited/moved-sprint event from a sprint_id delta, or None."""
373
+ new_sprint = rec.sprint_id
374
+ old_sprint = older.sprint_id
375
+ if new_sprint == old_sprint:
376
+ return None
377
+ if old_sprint is None:
378
+ event_type = "entered_sprint"
379
+ elif new_sprint is None:
380
+ event_type = "exited_sprint"
381
+ else:
382
+ event_type = "moved_sprint"
383
+ return {
384
+ **base,
385
+ "id": f"hist:{rec.history_id}:sprint",
386
+ "event_type": event_type,
387
+ "sprint_id": str(new_sprint) if new_sprint is not None else None,
388
+ # Internal (stripped before serialization) — both endpoints of the sprint
389
+ # transition so ``_filter_by_sprint`` can keep a removal visible in the
390
+ # sprint it LEFT (its old id), not just the one it entered (ADR-0412, #1946).
391
+ "_old_sprint_id": str(old_sprint) if old_sprint is not None else None,
392
+ "_new_sprint_id": str(new_sprint) if new_sprint is not None else None,
393
+ "changes": [
394
+ {
395
+ "field": "sprint",
396
+ "old": sprint_names.get(old_sprint) if old_sprint is not None else None,
397
+ "new": sprint_names.get(new_sprint) if new_sprint is not None else None,
398
+ }
399
+ ],
400
+ }
401
+
402
+
403
+ def _comment_events(
404
+ project: Project,
405
+ until: datetime,
406
+ since: datetime | None,
407
+ cap: int,
408
+ ) -> list[dict[str, Any]]:
409
+ """``comment_added`` events from TaskComment creates (bounded batch)."""
410
+ from trueppm_api.apps.projects.models import TaskComment
411
+
412
+ qs = TaskComment.objects.filter(
413
+ task__project_id=project.pk, is_deleted=False, created_at__lt=until
414
+ )
415
+ if since is not None:
416
+ qs = qs.filter(created_at__gte=since)
417
+ rows = list(qs.select_related("author", "task").order_by("-created_at")[:cap])
418
+ return [
419
+ {
420
+ "id": f"comment:{comment.pk}",
421
+ "event_type": "comment_added",
422
+ "actor": comment.author.username if comment.author else None,
423
+ "actor_id": str(comment.author_id) if comment.author_id else None,
424
+ "timestamp": comment.created_at,
425
+ "task_id": str(comment.task_id),
426
+ "task_name": comment.task.name,
427
+ "changes": [],
428
+ }
429
+ for comment in rows
430
+ ]
431
+
432
+
433
+ def _user_name_map(user_ids: set[Any]) -> dict[Any, str]:
434
+ if not user_ids:
435
+ return {}
436
+ user_model = get_user_model()
437
+ return dict(user_model.objects.filter(pk__in=user_ids).values_list("pk", "username"))
438
+
439
+
440
+ def _sprint_name_map(sprint_ids: set[Any]) -> dict[Any, str]:
441
+ if not sprint_ids:
442
+ return {}
443
+ from trueppm_api.apps.projects.models import Sprint
444
+
445
+ return dict(Sprint.objects.filter(pk__in=sprint_ids).values_list("pk", "name"))
446
+
447
+
448
+ def _serialize_event(event: dict[str, Any]) -> dict[str, Any]:
449
+ """Stamp the timestamp as an ISO string for the response (kept native for sort).
450
+
451
+ Internal ``_``-prefixed keys (e.g. the sprint-transition endpoints used by the
452
+ ``?sprint=`` scope filter) are dropped here — the payload is rendered directly to
453
+ JSON (the response serializer is schema-only), so they must not leak.
454
+ """
455
+ out = {k: v for k, v in event.items() if not k.startswith("_")}
456
+ ts = out["timestamp"]
457
+ out["timestamp"] = ts.isoformat() if isinstance(ts, datetime) else ts
458
+ out.setdefault("sprint_id", None)
459
+ # Present on every row (null off ``entered_sprint``) so the response shape is uniform —
460
+ # same contract as ``sprint_id`` (ADR-0160 Amendment B3, #1264).
461
+ out.setdefault("scope_change_status", None)
462
+ return out
@@ -0,0 +1,186 @@
1
+ """Board-level activity feed endpoint (ADR-0160, #325).
2
+
3
+ GET /api/v1/projects/<project_pk>/board/activity?until=&since=&actor=&type=&limit=
4
+
5
+ A read-only, board-scoped, filterable feed of card mutations (status/assignee/points
6
+ changes, sprint entries/exits, comments) aggregated from existing change-history
7
+ sources — see :mod:`board_activity`. Any project member (Viewer+) may read it; the
8
+ keyset cursor is ``until`` (pass the response's ``next_until`` back to page).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+ from django.utils import timezone
16
+ from django.utils.dateparse import parse_datetime
17
+ from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
18
+ from rest_framework import serializers, status
19
+ from rest_framework.generics import get_object_or_404
20
+ from rest_framework.permissions import IsAuthenticated
21
+ from rest_framework.request import Request
22
+ from rest_framework.response import Response
23
+ from rest_framework.views import APIView
24
+
25
+ from trueppm_api.apps.access.permissions import (
26
+ IsProjectMember,
27
+ _membership_role,
28
+ )
29
+ from trueppm_api.apps.projects.board_activity import (
30
+ EVENT_TYPES,
31
+ MAX_LIMIT,
32
+ build_board_activity,
33
+ )
34
+ from trueppm_api.apps.projects.models import Project
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Serializers (response schema for drf-spectacular)
38
+ # ---------------------------------------------------------------------------
39
+
40
+
41
+ class BoardActivityChangeSerializer(serializers.Serializer[Any]):
42
+ field = serializers.CharField()
43
+ old = serializers.CharField(allow_null=True)
44
+ new = serializers.CharField(allow_null=True)
45
+
46
+
47
+ class BoardActivityEventSerializer(serializers.Serializer[Any]):
48
+ id = serializers.CharField()
49
+ # CharField (not ChoiceField) deliberately: a ChoiceField over EVENT_TYPES emits a
50
+ # second `event_type` enum component that collides with the existing one and renames
51
+ # the canonical `EventTypeEnum` in the schema (the #859 schema-drift trap). The valid
52
+ # values are documented on the `type` query parameter instead.
53
+ event_type = serializers.CharField()
54
+ actor = serializers.CharField(allow_null=True)
55
+ actor_id = serializers.CharField(allow_null=True)
56
+ timestamp = serializers.DateTimeField()
57
+ task_id = serializers.CharField()
58
+ task_name = serializers.CharField()
59
+ sprint_id = serializers.CharField(allow_null=True)
60
+ # SprintScopeChange accept-gate status (pending/accepted/rejected) on `entered_sprint`
61
+ # events, else null (ADR-0160 Amendment B3, #1264). CharField not ChoiceField for the
62
+ # same #859 enum-collision reason as `event_type` above.
63
+ scope_change_status = serializers.CharField(allow_null=True)
64
+ changes = BoardActivityChangeSerializer(many=True)
65
+
66
+
67
+ class BoardActivityResponseSerializer(serializers.Serializer[Any]):
68
+ results = BoardActivityEventSerializer(many=True)
69
+ next_until = serializers.DateTimeField(allow_null=True)
70
+
71
+
72
+ def _parse_dt(raw: str | None, field: str) -> Any:
73
+ """Parse an ISO datetime query param into an aware datetime, or 400 on garbage."""
74
+ if raw is None:
75
+ return None
76
+ parsed = parse_datetime(raw)
77
+ if parsed is None:
78
+ from rest_framework.exceptions import ValidationError
79
+
80
+ raise ValidationError({field: f"Invalid datetime '{raw}' (expected ISO 8601)."})
81
+ if timezone.is_naive(parsed):
82
+ parsed = timezone.make_aware(parsed, timezone.get_current_timezone())
83
+ return parsed
84
+
85
+
86
+ class BoardActivityView(APIView):
87
+ """GET the board's filterable, time-ordered activity feed (Viewer+)."""
88
+
89
+ # IsProjectNotArchived is deliberately omitted: history/activity is a read-only
90
+ # audit surface that must stay accessible after a project is archived. (It was
91
+ # also a no-op here — the permission passes all SAFE_METHODS and this view is
92
+ # GET-only — so listing it only misdocumented the RBAC contract; #1890.)
93
+ permission_classes = [IsAuthenticated, IsProjectMember] # noqa: RUF012
94
+
95
+ @extend_schema(
96
+ summary="Board-level activity feed (filterable, board-scoped)",
97
+ parameters=[
98
+ OpenApiParameter(
99
+ "until",
100
+ str,
101
+ description="Keyset cursor — events older than this ISO datetime (default now).",
102
+ ),
103
+ OpenApiParameter(
104
+ "since", str, description="Lower time bound (inclusive ISO datetime)."
105
+ ),
106
+ OpenApiParameter("actor", str, description="Filter to one actor (user id)."),
107
+ OpenApiParameter(
108
+ "type",
109
+ str,
110
+ description=f"Comma-separated event types: {', '.join(sorted(EVENT_TYPES))}.",
111
+ ),
112
+ OpenApiParameter("limit", int, description=f"Page size (1..{MAX_LIMIT}, default 50)."),
113
+ OpenApiParameter(
114
+ "sprint",
115
+ str,
116
+ description=(
117
+ "Narrow to one sprint's scope (ADR-0412): events whose task is "
118
+ "currently in this sprint, plus entered/exited/moved events that "
119
+ "reference it. Must be a sprint in this project (404 otherwise)."
120
+ ),
121
+ ),
122
+ ],
123
+ responses={200: OpenApiResponse(BoardActivityResponseSerializer)},
124
+ )
125
+ def get(self, request: Request, project_pk: str) -> Response:
126
+ project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
127
+ self.check_object_permissions(request, project)
128
+
129
+ until = _parse_dt(request.query_params.get("until"), "until")
130
+ since = _parse_dt(request.query_params.get("since"), "since")
131
+ # actor is an opaque user-id string (integer PKs); an unknown/garbage value
132
+ # simply matches nothing, which is standard REST for a filter param.
133
+ actor_id = request.query_params.get("actor") or None
134
+
135
+ types_raw = request.query_params.get("type")
136
+ event_types: set[str] | None = None
137
+ if types_raw:
138
+ requested = {t.strip() for t in types_raw.split(",") if t.strip()}
139
+ unknown = requested - EVENT_TYPES
140
+ if unknown:
141
+ return Response(
142
+ {"type": f"Unknown event type(s): {', '.join(sorted(unknown))}."},
143
+ status=status.HTTP_400_BAD_REQUEST,
144
+ )
145
+ event_types = requested
146
+
147
+ try:
148
+ limit = int(request.query_params.get("limit", 50))
149
+ except (TypeError, ValueError):
150
+ return Response({"limit": "Must be an integer."}, status=status.HTTP_400_BAD_REQUEST)
151
+
152
+ # Sprint scope (ADR-0412, #1946). Validate the sprint belongs to THIS project
153
+ # so a sprint id from another project can never leak that project's activity —
154
+ # the view is already project-scoped and IsProjectMember-gated, so a cross-
155
+ # project or unknown id is a 404 (a malformed non-UUID id is a 400).
156
+ sprint_scope: str | None = None
157
+ sprint_raw = request.query_params.get("sprint") or None
158
+ if sprint_raw is not None:
159
+ from django.core.exceptions import ValidationError as DjangoValidationError
160
+
161
+ from trueppm_api.apps.projects.models import Sprint
162
+
163
+ try:
164
+ sprint = Sprint.objects.get(pk=sprint_raw, project_id=project.pk, is_deleted=False)
165
+ except Sprint.DoesNotExist:
166
+ return Response(
167
+ {"sprint": "No such sprint in this project."},
168
+ status=status.HTTP_404_NOT_FOUND,
169
+ )
170
+ except (DjangoValidationError, ValueError):
171
+ return Response(
172
+ {"sprint": "Invalid sprint id."}, status=status.HTTP_400_BAD_REQUEST
173
+ )
174
+ sprint_scope = str(sprint.pk)
175
+
176
+ payload = build_board_activity(
177
+ project,
178
+ until=until,
179
+ since=since,
180
+ actor_id=actor_id,
181
+ event_types=event_types,
182
+ limit=limit,
183
+ role=_membership_role(request, project.pk),
184
+ sprint_id=sprint_scope,
185
+ )
186
+ return Response(payload)