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,287 @@
1
+ """Data-backfill helpers for the projects app migrations.
2
+
3
+ These functions are extracted from migration files so tests can import them
4
+ without coupling to migration file names, which break on squash (CLAUDE.md rule 3).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from typing import Any, NamedTuple
11
+
12
+ from django.db.models import Count, F
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def backfill_risk_short_ids(apps: Any, schema_editor: Any) -> None:
18
+ """Renumber existing risks to a contiguous decimal sequence per project (#929).
19
+
20
+ Existing risks carry 8-char hex short_ids allocated from the shared
21
+ ``object_sequence`` — the bug behind every register row collapsing to
22
+ ``R-0000``. For each project, order *all* risks (including soft-deleted ones)
23
+ by ``created_at`` and assign ``1, 2, 3 …`` as the new decimal ``short_id``,
24
+ then set ``project.risk_sequence`` to the high-water mark so new risks never
25
+ reuse a number.
26
+
27
+ Soft-deleted risks are included in the ordering on purpose: the VoC panel
28
+ required immutability with *no reuse after deletion* (deletion leaves a gap —
29
+ "where is R-8?" is an expected audit finding, not a bug). Numbering only the
30
+ live rows would silently re-pack the sequence and reuse a dead risk's number.
31
+
32
+ ``server_version`` is bumped on every renumbered risk so offline/mobile
33
+ clients re-pull the corrected identifier on their next sync delta. The writes
34
+ use bulk ``.update()`` / ``bulk_update()`` — a one-time data correction, not
35
+ an audited user edit, so they intentionally leave no HistoricalRisk rows
36
+ (matching the 0071 backfill precedent).
37
+ """
38
+ Project = apps.get_model("projects", "Project")
39
+ Risk = apps.get_model("projects", "Risk")
40
+
41
+ for project_id in Project.objects.values_list("pk", flat=True):
42
+ risks = list(Risk.objects.filter(project_id=project_id).order_by("created_at", "pk"))
43
+ if not risks:
44
+ continue
45
+
46
+ # Two-pass renumber. ``unique_risk_short_id_per_project`` is a plain
47
+ # (non-deferrable) UniqueConstraint, so Postgres checks it per-row inside
48
+ # the bulk UPDATE. Writing the final decimals in one pass is only safe
49
+ # because legacy hex short_ids can't equal a bare decimal — but a
50
+ # mixed-version deploy window (new app code minting decimal short_ids
51
+ # before this migration runs) could break that assumption and abort the
52
+ # migration with an IntegrityError. Routing through a sentinel namespace
53
+ # that is disjoint from BOTH legacy hex (uppercase ``[0-9A-F]``) and the
54
+ # final decimals (the lowercase ``x`` prefix guarantees this) makes the
55
+ # renumber collision-proof regardless of the starting values.
56
+ for seq, risk in enumerate(risks, start=1):
57
+ risk.short_id = f"x{seq:07d}"
58
+ Risk.objects.bulk_update(risks, ["short_id"], batch_size=2000)
59
+ for seq, risk in enumerate(risks, start=1):
60
+ risk.short_id = str(seq)
61
+ Risk.objects.bulk_update(risks, ["short_id"], batch_size=2000)
62
+
63
+ # Bump server_version so sync clients see the changed short_id. Done as a
64
+ # single bulk UPDATE per project rather than per-row save().
65
+ Risk.objects.filter(project_id=project_id).update(server_version=F("server_version") + 1)
66
+ Project.objects.filter(pk=project_id).update(risk_sequence=len(risks))
67
+
68
+
69
+ def _backfill_wbs_paths(apps: Any, schema_editor: object) -> None:
70
+ """Assign sequential root-level wbs_path values to tasks that have none (#138).
71
+
72
+ Tasks with null wbs_path have no hierarchy information (parent_id is derived
73
+ from wbs_path, not stored separately), so they are assigned sequential
74
+ root-level paths within their project, ordered by short_id (project-scoped
75
+ insertion order).
76
+ """
77
+ Task = apps.get_model("projects", "Task")
78
+
79
+ # Order by short_id, not pk: Task.id is a UUID (random), so pk ordering is
80
+ # non-deterministic. short_id is allocated from Project.object_sequence on
81
+ # INSERT and zero-padded to 8 hex digits, so its lexicographic order matches
82
+ # creation order within a project. Backfill from migration 0015 ensures
83
+ # every pre-existing Task has a short_id assigned.
84
+ null_tasks = (
85
+ Task.objects.filter(wbs_path__isnull=True, is_deleted=False)
86
+ .order_by("project_id", "short_id")
87
+ .values_list("id", "project_id")
88
+ )
89
+
90
+ project_root_counts: dict[Any, int] = {}
91
+ updates = []
92
+ for task_id, project_id in null_tasks:
93
+ # Count existing root-level tasks (wbs_path matches ^\d+$) for this project.
94
+ # Computed once per project and cached; the update list preserves order so
95
+ # each new task sees the correct next position.
96
+ if project_id not in project_root_counts:
97
+ project_root_counts[project_id] = Task.objects.filter(
98
+ project_id=project_id,
99
+ is_deleted=False,
100
+ wbs_path__isnull=False,
101
+ wbs_path__regex=r"^\d+$",
102
+ ).count()
103
+ project_root_counts[project_id] += 1
104
+ updates.append((task_id, str(project_root_counts[project_id])))
105
+
106
+ for task_id, new_path in updates:
107
+ Task.objects.filter(id=task_id).update(wbs_path=new_path)
108
+
109
+
110
+ class RepairedWbsPath(NamedTuple):
111
+ """One row moved off a duplicated ``wbs_path`` by :func:`repair_duplicate_wbs_paths`."""
112
+
113
+ project_id: str
114
+ task_id: str
115
+ old_path: str
116
+ new_path: str
117
+ kept_task_id: str
118
+ #: Live rows under ``old_path`` that stayed with the kept task. See the
119
+ #: function docstring — they are ambiguous by construction, not merely unmoved.
120
+ stranded_descendants: int
121
+
122
+
123
+ def _free_sibling_path(taken: set[str], prefix: str, start: int) -> tuple[str, int]:
124
+ """The next unused path under ``prefix``, and the ordinal to resume searching from.
125
+
126
+ ``taken`` is every live path already claimed in the project, including the ones
127
+ this repair has just handed out — so two duplicates in the same group cannot
128
+ collide with each other.
129
+ """
130
+ ordinal = start
131
+ while True:
132
+ candidate = f"{prefix}{ordinal}" if prefix else str(ordinal)
133
+ if candidate not in taken:
134
+ return candidate, ordinal + 1
135
+ ordinal += 1
136
+
137
+
138
+ def repair_duplicate_wbs_paths(task_model: Any) -> list[RepairedWbsPath]:
139
+ """Re-path duplicate live ``(project, wbs_path)`` rows and report every move (#3068).
140
+
141
+ Migration ``0148`` adds ``unique_task_wbs_path_per_project_live`` as an
142
+ ``ExclusionConstraint``. ``AddConstraint`` builds and **validates** the GiST index
143
+ against every existing row, so on a database that already holds two live tasks
144
+ sharing a path, ``migrate`` aborts. Migrations run on container start, which makes
145
+ that an upgrade crash-loop rather than an error an operator can step around — and
146
+ the state is not hypothetical. It is exactly the corruption #3048 was filed to stop,
147
+ which any database running the pre-#3048 code could have accumulated (#3061 and the
148
+ three importer paths in #3069 are the known producers).
149
+
150
+ Repairing rather than refusing is deliberate: refusing leaves the operator with a
151
+ crash loop and no tooling to get out of it. But rows are being moved out from under
152
+ them, so **every move is logged at WARNING here**, not by the caller. A silent
153
+ reshuffle of someone's WBS would be worse than failing loudly, and the migration
154
+ output is the only record the operator will have — so the log is part of this
155
+ function's contract and is testable with it, rather than living in the migration
156
+ wrapper where CLAUDE.md rule 3 forbids a test from reaching it.
157
+
158
+ **Which row keeps the path.** #3068 proposed "oldest ``created_at``, tie-break on
159
+ ``pk``". ``Task`` has no ``created_at`` — it carries ``blocked_since``,
160
+ ``status_changed_at``, ``deleted_at``, ``seeded_at`` and ``edited_at``, none of
161
+ which is a creation stamp and each of which is null on most rows. The rule used is
162
+ **lowest ``sync_seq``, then lowest ``id``**: ``sync_seq`` is the owning project's
163
+ monotonic write cursor (ADR-0686), so the row that reached the project's sequence
164
+ first keeps the path — the closest thing to "created first" this table records.
165
+ Both terms are stable, so repairing the same database twice, or two replicas of it,
166
+ produces identical output.
167
+
168
+ **Descendant count deliberately does not enter the rule, and that is the crux.** The
169
+ instinct is "keep whichever row owns the subtree" — but descendants are addressed BY
170
+ PATH, and every row in a duplicate group shares the path. A row at ``4.2.1`` is a
171
+ child of "the ``4.2`` in this project"; when there are two of those, nothing in the
172
+ data says which. The count is identical for every candidate and cannot rank them.
173
+ Using it would look principled while sorting on a constant.
174
+
175
+ So the subtree stays with the kept row, and the moved row becomes childless at its
176
+ new path. That is a real consequence for the operator, which is why the count rides
177
+ back on :attr:`RepairedWbsPath.stranded_descendants` instead of being dropped.
178
+
179
+ A moved row is re-pathed among its **own siblings** — ``4.2`` becomes ``4.9``, not a
180
+ new root — so it stays inside the phase a planner put it in. A root-level duplicate
181
+ shifts to a free root ordinal past the project's current max, the same rule
182
+ ``root_ordinal_offset()`` applies to template adoption, so repaired and seeded rows
183
+ agree about what "the next free root" means.
184
+
185
+ Takes the model class rather than reaching for ``Task`` directly so the migration can
186
+ pass its historical model and a test can pass the real one (CLAUDE.md rule 3: never
187
+ import a migration module in a test).
188
+
189
+ On the overwhelmingly common clean database this is one aggregate query and a return.
190
+ """
191
+ dupes = (
192
+ task_model.objects.filter(is_deleted=False, wbs_path__isnull=False)
193
+ .values("project_id", "wbs_path")
194
+ .annotate(n=Count("id"))
195
+ .filter(n__gt=1)
196
+ )
197
+ affected = {(g["project_id"], str(g["wbs_path"])) for g in dupes}
198
+ if not affected:
199
+ return []
200
+
201
+ repaired: list[RepairedWbsPath] = []
202
+
203
+ for project_id in sorted({pid for pid, _ in affected}, key=str):
204
+ rows = list(
205
+ task_model.objects.filter(
206
+ project_id=project_id, is_deleted=False, wbs_path__isnull=False
207
+ ).values("id", "wbs_path", "sync_seq")
208
+ )
209
+ taken = {str(r["wbs_path"]) for r in rows}
210
+
211
+ by_path: dict[str, list[dict[str, Any]]] = {}
212
+ for row in rows:
213
+ by_path.setdefault(str(row["wbs_path"]), []).append(row)
214
+
215
+ # Live descendants per path — for the operator-facing report ONLY, never for
216
+ # the keep rule (see the docstring). A path's descendants are the rows whose
217
+ # path starts with it plus a separator; the string test is exact because an
218
+ # ltree label cannot contain a dot.
219
+ descendants = {
220
+ path: sum(len(v) for p, v in by_path.items() if p.startswith(path + "."))
221
+ for path in by_path
222
+ }
223
+
224
+ # Resume the ordinal search per prefix so a group of three duplicates does not
225
+ # rescan from 1 for each move.
226
+ next_ordinal: dict[str, int] = {}
227
+
228
+ for path in sorted(p for pid, p in affected if pid == project_id):
229
+ group = by_path.get(path, [])
230
+ if len(group) < 2:
231
+ continue
232
+ # Earliest position in the project's write sequence keeps the path; the pk
233
+ # breaks a tie. Both are stable, so this is reproducible.
234
+ group.sort(key=lambda r: (r["sync_seq"], str(r["id"])))
235
+ keeper, movers = group[0], group[1:]
236
+
237
+ head, _, _ = path.rpartition(".")
238
+ prefix = f"{head}." if head else ""
239
+ start = next_ordinal.get(prefix, 1)
240
+
241
+ for mover in movers:
242
+ new_path, start = _free_sibling_path(taken, prefix, start)
243
+ taken.add(new_path)
244
+ task_model.objects.filter(pk=mover["id"]).update(wbs_path=new_path)
245
+ repaired.append(
246
+ RepairedWbsPath(
247
+ project_id=str(project_id),
248
+ task_id=str(mover["id"]),
249
+ old_path=path,
250
+ new_path=new_path,
251
+ kept_task_id=str(keeper["id"]),
252
+ stranded_descendants=descendants.get(path, 0),
253
+ )
254
+ )
255
+ next_ordinal[prefix] = start
256
+
257
+ _log_repairs(repaired)
258
+ return repaired
259
+
260
+
261
+ def _log_repairs(repaired: list[RepairedWbsPath]) -> None:
262
+ """Report every moved row at WARNING. See :func:`repair_duplicate_wbs_paths`."""
263
+ if not repaired:
264
+ return
265
+ logger.warning(
266
+ "wbs_path repair (#3068): %d row(s) across %d project(s) shared a live WBS path "
267
+ "with another row and have been moved so the uniqueness constraint can be "
268
+ "applied. Review the moves below.",
269
+ len(repaired),
270
+ len({r.project_id for r in repaired}),
271
+ )
272
+ for row in repaired:
273
+ logger.warning(
274
+ "wbs_path repair (#3068): project=%s task=%s moved %s -> %s (kept task=%s)%s",
275
+ row.project_id,
276
+ row.task_id,
277
+ row.old_path,
278
+ row.new_path,
279
+ row.kept_task_id,
280
+ (
281
+ f"; {row.stranded_descendants} live row(s) under {row.old_path} stayed "
282
+ f"with task {row.kept_task_id} - a duplicated path makes their parent "
283
+ "ambiguous and nothing in the data distinguishes them"
284
+ if row.stranded_descendants
285
+ else ""
286
+ ),
287
+ )
@@ -0,0 +1,324 @@
1
+ """Service layer for the program backlog pull-down (ADR-0069 Erratum, #737).
2
+
3
+ The pull action is the one place where a program-level ``BacklogItem`` crosses
4
+ into a project: it converts a PROPOSED item into a project-backlog ``Task``
5
+ (``status=BACKLOG``, ``sprint=NULL``) and records the conversion on the item.
6
+ Sprint sovereignty is preserved by design — pull never assigns a sprint
7
+ (Morgan's VoC blocker, resolved in ADR-0069).
8
+
9
+ Kept as a service (not inline in the view) so the atomic transition is testable
10
+ in isolation and reusable by a future batch-pull from Sprint Planning.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import uuid
16
+ from typing import Any
17
+
18
+ from django.db import IntegrityError, transaction
19
+ from django.db.models import Max
20
+ from django.utils import timezone
21
+
22
+ from trueppm_api.apps.projects.models import (
23
+ BacklogItem,
24
+ BacklogItemStatus,
25
+ BacklogItemType,
26
+ Label,
27
+ LabelColor,
28
+ Project,
29
+ Task,
30
+ TaskLabel,
31
+ TaskStatus,
32
+ TaskType,
33
+ )
34
+
35
+ # Map the program intake taxonomy (ADR-0069) onto the project work-item taxonomy
36
+ # (ADR-0105). The two enums are reconciled (#1995): every intake type maps to a
37
+ # distinct Task type so a pulled bug/spike/chore keeps its kind instead of
38
+ # silently collapsing to a plain task. ``chore`` is the intake name for
39
+ # ``TaskType.TECH_DEBT``; ``feature`` has no Task analogue and maps to ``TASK``.
40
+ _ITEM_TYPE_TO_TASK_TYPE: dict[str, TaskType] = {
41
+ BacklogItemType.EPIC: TaskType.EPIC,
42
+ BacklogItemType.STORY: TaskType.STORY,
43
+ BacklogItemType.TASK: TaskType.TASK,
44
+ BacklogItemType.BUG: TaskType.BUG,
45
+ BacklogItemType.SPIKE: TaskType.SPIKE,
46
+ BacklogItemType.CHORE: TaskType.TECH_DEBT,
47
+ BacklogItemType.FEATURE: TaskType.TASK,
48
+ }
49
+
50
+
51
+ class BacklogItemNotPullable(Exception):
52
+ """Raised when a pull is attempted on an item that is not PROPOSED.
53
+
54
+ The view maps this to ``409 Conflict`` — the item was already pulled or
55
+ archived (or a concurrent pull won the row lock first).
56
+ """
57
+
58
+
59
+ class CrossProgramPullError(Exception):
60
+ """Raised when the pull target project does not belong to the item's program.
61
+
62
+ The view maps this to ``400 Bad Request``. Single-program scoping is the OSS
63
+ boundary (ADR-0069): a program backlog item can only be pulled into one of
64
+ its own program's projects, never into another program's project.
65
+ """
66
+
67
+
68
+ def pull_to_project_backlog(
69
+ item_id: str,
70
+ project: Project,
71
+ actor: Any,
72
+ ) -> Task:
73
+ """Atomically convert a PROPOSED ``BacklogItem`` into a project-backlog Task.
74
+
75
+ Locks the item ``FOR UPDATE`` and asserts it is still PROPOSED before doing
76
+ any work, so two concurrent pulls cannot both succeed — the loser raises
77
+ :class:`BacklogItemNotPullable` (→ 409). The new Task lands in the project
78
+ backlog (``status=BACKLOG``, ``sprint=None``); the item is flipped to PULLED
79
+ with ``pulled_task``/``pulled_at``/``pulled_by`` recorded. Both rows bump
80
+ ``server_version`` via their ``save()``.
81
+
82
+ On commit it enqueues a CPM recalculation (existing scheduling outbox — no
83
+ new outbox category), broadcasts a ``task_created`` board event, and fires the
84
+ ``task.created`` outbound webhook (#752) — matching every other Task-create
85
+ path so external subscribers see backlog pulls like any other create. All
86
+ three are deferred with ``transaction.on_commit`` so a rolled-back pull never
87
+ leaks a recalc request, a phantom WS event, or a webhook delivery.
88
+
89
+ Args:
90
+ item_id: PK of the ``BacklogItem`` to pull.
91
+ project: The resolved target ``Project`` (must belong to the item's program).
92
+ actor: The user performing the pull (recorded as ``pulled_by``).
93
+
94
+ Returns:
95
+ The created project-backlog ``Task``.
96
+
97
+ Raises:
98
+ BacklogItem.DoesNotExist: if no such item exists.
99
+ CrossProgramPullError: if ``project`` is not in the item's program.
100
+ BacklogItemNotPullable: if the item is not in PROPOSED status.
101
+ """
102
+ # Call-time import (not module-level) to avoid a views→services→views cycle.
103
+ # By the time this service runs, the app is fully loaded, so reusing the
104
+ # canonical task payload builder keeps the pull's task.created shape identical
105
+ # to the REST create path with zero drift risk.
106
+ from trueppm_api.apps.access.permissions import assert_project_not_archived
107
+ from trueppm_api.apps.projects.views import _task_webhook_payload
108
+ from trueppm_api.apps.scheduling.services import enqueue_recalculate
109
+ from trueppm_api.apps.sync.broadcast import broadcast_board_event
110
+ from trueppm_api.apps.webhooks.dispatch import dispatch_webhooks
111
+
112
+ # A pull creates a Task, and archived plans take no writes (#3354). The view
113
+ # checks this too, but archived is lifecycle state — a property of the target
114
+ # plan, not of the caller — so it holds for every caller rather than only for
115
+ # one that arrives through DRF. Every caller is a view today; the point is that
116
+ # nothing makes that stay true. Placed before the transaction so nothing is
117
+ # opened on a write that must not happen.
118
+ assert_project_not_archived(project.pk)
119
+
120
+ new_label_ids: list[str] = []
121
+ with transaction.atomic():
122
+ item = BacklogItem.objects.select_for_update().get(pk=item_id, is_deleted=False)
123
+
124
+ if project.program_id != item.program_id:
125
+ raise CrossProgramPullError("The target project is not part of this program.")
126
+
127
+ if item.status != BacklogItemStatus.PROPOSED:
128
+ raise BacklogItemNotPullable(
129
+ f"This item is '{item.status}' and can no longer be pulled."
130
+ )
131
+
132
+ task = Task.objects.create(
133
+ project=project,
134
+ name=item.title,
135
+ # BacklogItem.description maps to Task.notes — Task has no separate
136
+ # description field; the Board/drawer render notes as the body.
137
+ notes=item.description,
138
+ story_points=item.story_points,
139
+ # The PO's intake ordering is the whole point of ranking a program
140
+ # backlog, and it is not reconstructable after the crossing — there is
141
+ # no un-pull endpoint, so a lost rank cannot be recovered by undoing.
142
+ # Both fields are nullable PositiveIntegerFields meaning "lower = higher
143
+ # priority", so the raw value carries: it preserves relative order among
144
+ # items pulled from the same pool. Ranks are not unique and ties already
145
+ # break on a secondary sort, so colliding with an existing project task's
146
+ # rank is harmless — whereas leaving it null sorts the pulled item last
147
+ # (the 9999 client sentinel), which is the defect (#2906).
148
+ priority_rank=item.priority_rank,
149
+ status=TaskStatus.BACKLOG,
150
+ sprint=None,
151
+ # ADR-0105: carry the intake item's type into the project work-item
152
+ # taxonomy so a pulled story/bug/spike keeps its kind. The taxonomies
153
+ # are reconciled (#1995), so ``.get`` always hits; the TASK fallback is
154
+ # defensive only (an unknown value can no longer be created).
155
+ type=_ITEM_TYPE_TO_TASK_TYPE.get(item.item_type, TaskType.TASK),
156
+ )
157
+
158
+ # Carry the intake item's free-text tags onto the task as project labels
159
+ # (#1992) so a PO's thematic grouping survives the crossing. Runs inside
160
+ # the atomic block so a rolled-back pull leaves no orphan labels.
161
+ new_label_ids = _apply_tags_as_labels(task, project, item.tags, actor)
162
+
163
+ item.status = BacklogItemStatus.PULLED
164
+ item.pulled_task = task
165
+ item.pulled_at = timezone.now()
166
+ item.pulled_by = actor if getattr(actor, "is_authenticated", False) else None
167
+ # server_version is bumped automatically by VersionedModel.save() (it is
168
+ # stripped from update_fields and incremented via F()), so it is omitted here.
169
+ item.save(update_fields=["status", "pulled_task", "pulled_at", "pulled_by"])
170
+
171
+ project_id = str(project.pk)
172
+ task_id = str(task.pk)
173
+ # source="backlog_pull" tells external consumers which surface originated the
174
+ # create (ADR-0065 X-Source semantics) so a pull is distinguishable from a
175
+ # board/schedule/sync create on the same task.created event.
176
+ webhook_payload = _task_webhook_payload(task, source="backlog_pull")
177
+ # CPM recalc via the existing scheduling outbox (ADR-0027). A BACKLOG task is
178
+ # excluded from the CPM graph, so this coalesces to a no-op run today, but we
179
+ # keep it on the standard Task-create path for consistency and forward-safety.
180
+ transaction.on_commit(lambda: enqueue_recalculate(project_id))
181
+ transaction.on_commit(
182
+ lambda: broadcast_board_event(project_id, "task_created", {"id": task_id})
183
+ )
184
+
185
+ # Any labels coined from the item's tags are new project vocabulary — tell open
186
+ # boards/label managers so they can render the pulled task's pills (#1992). Bind
187
+ # the id through a function arg so closure late-binding can't collapse the loop's
188
+ # events to the last id.
189
+ def _broadcast_label(lid: str) -> None:
190
+ transaction.on_commit(
191
+ lambda: broadcast_board_event(project_id, "label_created", {"id": lid})
192
+ )
193
+
194
+ for _label_id in new_label_ids:
195
+ _broadcast_label(_label_id)
196
+
197
+ # Bind the payload via a default arg so closure late-binding can never swap it
198
+ # if this function grows more branches later (matches inbound_sync.py).
199
+ def _dispatch(pid: str = project_id, wp: dict[str, Any] = webhook_payload) -> None:
200
+ dispatch_webhooks(pid, "task.created", wp)
201
+
202
+ transaction.on_commit(_dispatch)
203
+ return task
204
+
205
+
206
+ def _apply_tags_as_labels(
207
+ task: Task,
208
+ project: Project,
209
+ tags: list[str],
210
+ actor: Any,
211
+ ) -> list[str]:
212
+ """Carry a pulled item's free-text tags onto the task as project labels (#1992).
213
+
214
+ Each tag becomes — or, case-insensitively, reuses — a project-scoped ``Label``
215
+ and is attached to ``task`` via ``TaskLabel``. Reuse keeps the catalog from
216
+ sprouting a near-duplicate on every pull; new labels take the neutral default
217
+ slot (``SLATE``) appended to the end of the palette order, which a project admin
218
+ can recolor later. Idempotent per tag via ``TaskLabel.get_or_create``.
219
+
220
+ Must be called inside the pull's ``transaction.atomic`` block so a rolled-back
221
+ pull leaves no orphan labels. The label soft cap is honored: once the project is
222
+ at the ceiling, further *new* tags are skipped (existing matches still attach) so
223
+ a pull can never blow past the curation floor the ``LabelViewSet`` enforces.
224
+
225
+ Args:
226
+ task: The freshly created project-backlog Task to label.
227
+ project: The task's project (labels are project-scoped).
228
+ tags: The item's tags — already normalized (trimmed, de-duped) by the
229
+ serializer's ``validate_tags``.
230
+ actor: The pulling user, recorded as ``created_by`` on any new label.
231
+
232
+ Returns:
233
+ The ids of labels *created* here (not reused), so the caller can broadcast
234
+ ``label_created`` once the transaction commits.
235
+ """
236
+ if not tags:
237
+ return []
238
+ # Call-time import mirrors the pull's views import — avoids a services→views cycle.
239
+ from trueppm_api.apps.projects.views import _label_soft_cap
240
+
241
+ cap = _label_soft_cap()
242
+ live = Label.objects.filter(project=project, is_deleted=False)
243
+ label_count = live.count()
244
+ next_position = (live.aggregate(m=Max("position"))["m"] or 0) + 1
245
+ creator = actor if getattr(actor, "is_authenticated", False) else None
246
+
247
+ created_ids: list[str] = []
248
+ for tag in tags:
249
+ # Label.name is capped at 50 chars; BacklogItem tags are unbounded, so clamp
250
+ # to keep an over-long tag from raising instead of degrading gracefully.
251
+ name = tag[:50]
252
+ existing = Label.objects.filter(
253
+ project=project, name__iexact=name, is_deleted=False
254
+ ).first()
255
+ if existing is not None:
256
+ TaskLabel.objects.get_or_create(task=task, label=existing)
257
+ continue
258
+ if label_count >= cap:
259
+ # At the curation ceiling — skip coining a new label but keep going;
260
+ # a pull must not fail because the project's label catalog is full.
261
+ continue
262
+ label, created = _coin_label(project, name, next_position, creator)
263
+ if label is None:
264
+ continue
265
+ if created:
266
+ next_position += 1
267
+ label_count += 1
268
+ created_ids.append(str(label.pk))
269
+ TaskLabel.objects.get_or_create(task=task, label=label)
270
+ return created_ids
271
+
272
+
273
+ def _coin_label(
274
+ project: Project,
275
+ name: str,
276
+ position: int,
277
+ creator: Any,
278
+ ) -> tuple[Any, bool]:
279
+ """Create a new project label at ``position``, or reuse the race winner.
280
+
281
+ Returns ``(label, created)`` where ``created`` is True only when this call
282
+ coined the label (so the caller can bump the position/count and broadcast).
283
+
284
+ Savepoint the create so a concurrent pull that coined the same tag first raises
285
+ only a local IntegrityError (on uniq_label_name_per_project) instead of poisoning
286
+ the whole pull transaction. Without the nested atomic the outer pull would roll
287
+ back to a transient 500. On a lost race the other transaction's label is now
288
+ committed and visible; reuse it (exact-name match, since the unique constraint is
289
+ on exact (project, name)).
290
+ """
291
+ try:
292
+ with transaction.atomic():
293
+ label = Label.objects.create(
294
+ project=project,
295
+ name=name,
296
+ color=LabelColor.SLATE,
297
+ position=position,
298
+ created_by=creator,
299
+ )
300
+ except IntegrityError:
301
+ return Label.objects.filter(project=project, name=name, is_deleted=False).first(), False
302
+ return label, True
303
+
304
+
305
+ def reset_pulled_item_on_task_delete(task_id: str) -> None:
306
+ """Reset a BacklogItem to PROPOSED when its pulled Task is (soft-)deleted.
307
+
308
+ Rollback path (ADR-0069): if the Task created by a pull is deleted, the
309
+ originating item must become re-pullable. ``pulled_task`` is ``SET_NULL`` so
310
+ deletion never cascades to the item; this restores the lifecycle by clearing
311
+ the pull bookkeeping and flipping the item back to PROPOSED.
312
+
313
+ Idempotent and a no-op when no item points at the task. Called from the Task
314
+ post_save soft-delete receiver (TruePPM Tasks soft-delete, not hard-delete).
315
+ """
316
+ item = BacklogItem.objects.filter(pulled_task_id=uuid.UUID(task_id), is_deleted=False).first()
317
+ if item is None or item.status != BacklogItemStatus.PULLED:
318
+ return
319
+ item.status = BacklogItemStatus.PROPOSED
320
+ item.pulled_task = None
321
+ item.pulled_at = None
322
+ item.pulled_by = None
323
+ # server_version auto-bumps in VersionedModel.save() — omitted from update_fields.
324
+ item.save(update_fields=["status", "pulled_task", "pulled_at", "pulled_by"])