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,500 @@
1
+ """Export TruePPM project data to MS Project XML format."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import xml.etree.ElementTree as ET
6
+ from datetime import date
7
+ from typing import Any
8
+
9
+ from trueppm_api.apps.msproject.extended_attributes import (
10
+ DURATION1_FIELD_ID,
11
+ DURATION2_FIELD_ID,
12
+ DURATION3_FIELD_ID,
13
+ DURATION4_FIELD_ID,
14
+ PERT_ALIAS_LABELS,
15
+ PERT_EXPECTED_FORMULA,
16
+ PERT_FIELD_NAMES,
17
+ )
18
+ from trueppm_api.apps.scheduling.units import (
19
+ HOURS_PER_WORKING_DAY,
20
+ MSPDI_LAG_TENTHS_PER_WORKING_DAY,
21
+ )
22
+
23
+ # MS Project XML namespace.
24
+ _NS = "http://schemas.microsoft.com/project"
25
+
26
+ # MSPDI <ConstraintType> codes this exporter emits (#2891). Named rather than
27
+ # inlined because the importer's mapping table (`CONSTRAINT_TYPES_APPLIED_AS_SNET`
28
+ # in `dataclasses`) has to stay in agreement with them for the round trip to close.
29
+ MSPDI_CONSTRAINT_AS_SOON_AS_POSSIBLE = 0
30
+ MSPDI_CONSTRAINT_START_NO_EARLIER_THAN = 4
31
+
32
+ # TruePPM dep_type -> MS Project PredecessorLink/Type.
33
+ _DEP_TYPE_TO_LINK_TYPE = {
34
+ "FF": "0",
35
+ "FS": "1",
36
+ "SF": "2",
37
+ "SS": "3",
38
+ }
39
+
40
+ # MSPDI WeekDay/DayType (1=Sunday … 7=Saturday) paired with the TruePPM
41
+ # Calendar.working_days bit (Mon=1 … Sun=64), in MSPDI emission order.
42
+ # Keep in lockstep with parser._DAY_TYPE_TO_BIT.
43
+ _DAY_TYPES_AND_BITS = (
44
+ ("1", 64), # Sunday
45
+ ("2", 1), # Monday
46
+ ("3", 2), # Tuesday
47
+ ("4", 4), # Wednesday
48
+ ("5", 8), # Thursday
49
+ ("6", 16), # Friday
50
+ ("7", 32), # Saturday
51
+ )
52
+
53
+
54
+ def export_project_xml(project_id: str) -> bytes:
55
+ """Export a TruePPM project to MS Project XML format.
56
+
57
+ Returns:
58
+ UTF-8 encoded XML bytes.
59
+ """
60
+ from trueppm_api.apps.projects.models import Dependency, Project, Task
61
+ from trueppm_api.apps.resources.models import Resource, TaskResource
62
+ from trueppm_api.apps.scheduling.calendars import resolve_applied_calendars
63
+
64
+ # calendar__exceptions + calendar_layers__calendar__exceptions: the calendar
65
+ # emission below walks every applied calendar's exceptions (#1769).
66
+ project = Project.objects.prefetch_related(
67
+ "calendar__exceptions", "calendar_layers__calendar__exceptions"
68
+ ).get(pk=project_id)
69
+ applied_calendars = resolve_applied_calendars(project)
70
+ tasks = list(
71
+ Task.objects.filter(project_id=project_id, is_deleted=False).order_by("wbs_path", "name")
72
+ )
73
+ dependencies = list(
74
+ Dependency.objects.filter(
75
+ predecessor__project_id=project_id, is_deleted=False
76
+ ).select_related("predecessor", "successor")
77
+ )
78
+ task_ids = [str(t.pk) for t in tasks]
79
+ assignments = list(TaskResource.objects.filter(task_id__in=task_ids).select_related("resource"))
80
+ resource_ids = {a.resource_id for a in assignments}
81
+ resources = list(Resource.objects.filter(pk__in=resource_ids))
82
+
83
+ # UID maps - MS Project expects sequential integer UIDs.
84
+ task_pk_to_uid = {str(t.pk): i for i, t in enumerate(tasks, start=1)}
85
+ resource_pk_to_uid = {str(r.pk): i for i, r in enumerate(resources, start=1)}
86
+ dep_by_successor = _build_dep_lookup(dependencies)
87
+
88
+ # --- Build XML ---
89
+ ET.register_namespace("", _NS)
90
+ root = ET.Element(f"{{{_NS}}}Project")
91
+
92
+ _sub_text(root, "Name", project.name)
93
+ _sub_text(root, "StartDate", _format_date(project.start_date))
94
+ if applied_calendars:
95
+ # The merged working calendar is always emitted as UID 1 (see
96
+ # _add_calendars_block). Header element order follows the MSPDI schema:
97
+ # CalendarUID sits among the project properties, before
98
+ # ExtendedAttributes and Calendars.
99
+ _sub_text(root, "CalendarUID", "1")
100
+
101
+ # --- Project-level ExtendedAttribute definitions (#798, ADR-0093) ---
102
+ # Emit the four PERT slots only when at least one non-summary, non-milestone
103
+ # task carries all three values. MS Project tolerates the block being
104
+ # absent; emitting it unconditionally would clutter files that don't use
105
+ # three-point estimates.
106
+ pert_tasks = _select_pert_tasks(tasks)
107
+ if pert_tasks:
108
+ _add_pert_extended_attribute_defs(root)
109
+
110
+ # --- Calendars (#1769) ---
111
+ # Emitted between ExtendedAttributes and Tasks, matching the MSPDI element
112
+ # order. Omitted entirely when the project has no applied calendar (the
113
+ # engine's implicit Mon-Fri/8h default), which MS Project also defaults to.
114
+ if applied_calendars:
115
+ _add_calendars_block(root, applied_calendars)
116
+
117
+ # --- Tasks ---
118
+ tasks_el = ET.SubElement(root, f"{{{_NS}}}Tasks")
119
+
120
+ # Task UID 0: project summary task
121
+ _add_summary_task(tasks_el, project)
122
+
123
+ pert_task_pks = {str(t.pk) for t in pert_tasks}
124
+ for task in tasks:
125
+ _add_task_element(
126
+ tasks_el,
127
+ task,
128
+ task_pk_to_uid[str(task.pk)],
129
+ is_pert=str(task.pk) in pert_task_pks,
130
+ preds=dep_by_successor.get(str(task.pk), []),
131
+ task_pk_to_uid=task_pk_to_uid,
132
+ )
133
+
134
+ _add_resources_block(root, resources, resource_pk_to_uid)
135
+ _add_assignments_block(root, assignments, task_pk_to_uid, resource_pk_to_uid)
136
+
137
+ # Serialize
138
+ tree = ET.ElementTree(root)
139
+ ET.indent(tree, space=" ")
140
+
141
+ xml_decl = b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
142
+ return xml_decl + ET.tostring(root, encoding="unicode").encode("utf-8")
143
+
144
+
145
+ def _build_dep_lookup(dependencies: list[Any]) -> dict[str, list[tuple[str, str, int]]]:
146
+ """Index dependencies by successor: ``successor_pk -> [(pred_pk, dep_type, lag)]``."""
147
+ dep_by_successor: dict[str, list[tuple[str, str, int]]] = {}
148
+ for dep in dependencies:
149
+ dep_by_successor.setdefault(str(dep.successor_id), []).append(
150
+ (str(dep.predecessor_id), dep.dep_type, dep.lag)
151
+ )
152
+ return dep_by_successor
153
+
154
+
155
+ def _select_pert_tasks(tasks: list[Any]) -> list[Any]:
156
+ """Return the leaf, non-milestone tasks carrying all three PERT durations.
157
+
158
+ The summary set is precomputed once for O(N) filtering — a summary or
159
+ milestone task never emits three-point values (ADR-0093 Q5).
160
+ """
161
+ summary_pks = _summary_pks_from_tasks(tasks)
162
+ return [
163
+ t
164
+ for t in tasks
165
+ if (
166
+ str(t.pk) not in summary_pks
167
+ and not t.is_milestone
168
+ and t.optimistic_duration is not None
169
+ and t.most_likely_duration is not None
170
+ and t.pessimistic_duration is not None
171
+ )
172
+ ]
173
+
174
+
175
+ def _add_task_dates(task_el: ET.Element, task: Any) -> None:
176
+ """Emit the contiguous Start/Finish/Constraint/Actual date run for one ``<Task>``.
177
+
178
+ MSPDI is order-sensitive, and ``ET.SubElement`` appends at call time, so the
179
+ caller's invocation position *is* the positional guarantee for every element
180
+ below — it must stay exactly where it sits in ``_add_task_element``'s append
181
+ sequence (after ``Notes``, before the PERT ExtendedAttribute values).
182
+
183
+ Constraint + actuals round-trip (#2891). ``planned_start`` IS a
184
+ start-no-earlier-than floor, so emitting it as MSPDI ConstraintType 4 with a
185
+ ConstraintDate is a faithful translation rather than an approximation — and
186
+ it is what makes the round trip closed: the importer reads the constraint
187
+ date back as ``planned_start``. Emitting only <Start> (which is CPM output, and
188
+ per ADR-0132 a remaining-work date on a partially-complete task) meant a
189
+ re-import silently promoted computed dates to committed ones.
190
+
191
+ Tasks with no floor get ConstraintType 0 (As Soon As Possible), which is
192
+ TruePPM's own default: an omitted element would leave MS Project to infer a
193
+ constraint from the <Start> we emit, re-creating the same promotion.
194
+ """
195
+ if task.early_start:
196
+ _sub_text(task_el, "Start", _format_date(task.early_start))
197
+ elif task.planned_start:
198
+ _sub_text(task_el, "Start", _format_date(task.planned_start))
199
+ if task.early_finish:
200
+ _sub_text(task_el, "Finish", _format_date(task.early_finish))
201
+
202
+ if task.planned_start:
203
+ _sub_text(task_el, "ConstraintType", str(MSPDI_CONSTRAINT_START_NO_EARLIER_THAN))
204
+ _sub_text(task_el, "ConstraintDate", _format_date(task.planned_start))
205
+ else:
206
+ _sub_text(task_el, "ConstraintType", str(MSPDI_CONSTRAINT_AS_SOON_AS_POSSIBLE))
207
+ if task.actual_start:
208
+ _sub_text(task_el, "ActualStart", _format_date(task.actual_start))
209
+ if task.actual_finish:
210
+ _sub_text(task_el, "ActualFinish", _format_date(task.actual_finish))
211
+
212
+
213
+ def _add_task_element(
214
+ tasks_el: ET.Element,
215
+ task: Any,
216
+ uid: int,
217
+ *,
218
+ is_pert: bool,
219
+ preds: list[tuple[str, str, int]],
220
+ task_pk_to_uid: dict[str, int],
221
+ ) -> None:
222
+ """Emit one ``<Task>`` element, including PERT values and predecessor links."""
223
+ task_el = ET.SubElement(tasks_el, f"{{{_NS}}}Task")
224
+ _sub_text(task_el, "UID", str(uid))
225
+ _sub_text(task_el, "Name", task.name)
226
+ _sub_text(task_el, "Duration", _days_to_duration(task.duration))
227
+ _sub_text(task_el, "DurationFormat", "7")
228
+ if task.wbs_path:
229
+ _sub_text(task_el, "OutlineNumber", task.wbs_path)
230
+ _sub_text(task_el, "OutlineLevel", str(task.wbs_path.count(".") + 1))
231
+ else:
232
+ _sub_text(task_el, "OutlineLevel", "1")
233
+ _sub_text(task_el, "Milestone", "1" if task.is_milestone else "0")
234
+ # Task.percent_complete is already a 0-100 percent (#1759) and MSPDI
235
+ # PercentComplete is the same 0-100 scale, so emit it as-is. The earlier
236
+ # `* 100` turned a native 50% task into an invalid PercentComplete=5000.
237
+ _sub_text(task_el, "PercentComplete", str(round(task.percent_complete or 0)))
238
+ if task.notes:
239
+ _sub_text(task_el, "Notes", task.notes)
240
+ _add_task_dates(task_el, task)
241
+
242
+ # PERT three-point per-task values. Only emitted for the leaf, non-milestone
243
+ # tasks selected by _select_pert_tasks. Duration4 is the PERT-Expected
244
+ # formula slot; MS Project derives it on read from the project-level Formula
245
+ # definition, so we never emit a per-task Duration4 value.
246
+ if is_pert:
247
+ # All three are non-None by the _select_pert_tasks filter, but mypy
248
+ # can't carry that proof across the call boundary.
249
+ assert task.optimistic_duration is not None
250
+ assert task.most_likely_duration is not None
251
+ assert task.pessimistic_duration is not None
252
+ _add_pert_task_values(
253
+ task_el,
254
+ task.optimistic_duration,
255
+ task.most_likely_duration,
256
+ task.pessimistic_duration,
257
+ )
258
+
259
+ for pred_pk, dep_type, lag in preds:
260
+ pred_uid = task_pk_to_uid.get(pred_pk)
261
+ if pred_uid is None:
262
+ continue
263
+ pred_el = ET.SubElement(task_el, f"{{{_NS}}}PredecessorLink")
264
+ _sub_text(pred_el, "PredecessorUID", str(pred_uid))
265
+ _sub_text(pred_el, "Type", _DEP_TYPE_TO_LINK_TYPE.get(dep_type, "1"))
266
+ # LinkLag is tenths of a minute; one working day of lag is
267
+ # MSPDI_LAG_TENTHS_PER_WORKING_DAY (#2290 unit seam).
268
+ _sub_text(pred_el, "LinkLag", str(lag * MSPDI_LAG_TENTHS_PER_WORKING_DAY))
269
+ _sub_text(pred_el, "LagFormat", "7")
270
+
271
+
272
+ def _add_resources_block(
273
+ root: ET.Element, resources: list[Any], resource_pk_to_uid: dict[str, int]
274
+ ) -> None:
275
+ """Emit the ``<Resources>`` block."""
276
+ resources_el = ET.SubElement(root, f"{{{_NS}}}Resources")
277
+ for resource in resources:
278
+ uid = resource_pk_to_uid[str(resource.pk)]
279
+ res_el = ET.SubElement(resources_el, f"{{{_NS}}}Resource")
280
+ _sub_text(res_el, "UID", str(uid))
281
+ _sub_text(res_el, "Name", resource.name)
282
+ # The resource's own catalog capacity, NOT its project-effective figure
283
+ # (#3574). MSPDI has no per-project override concept, so writing the
284
+ # effective value here would bake one project's slice into the resource
285
+ # default — and the importer reads MaxUnits straight back into
286
+ # ``Resource.max_units``, so a round trip would make the override permanent
287
+ # and global. The override is a TruePPM-side fact with no MSPDI counterpart.
288
+ _sub_text(res_el, "MaxUnits", f"{float(resource.max_units):.2f}")
289
+
290
+
291
+ def _add_assignments_block(
292
+ root: ET.Element,
293
+ assignments: list[Any],
294
+ task_pk_to_uid: dict[str, int],
295
+ resource_pk_to_uid: dict[str, int],
296
+ ) -> None:
297
+ """Emit the ``<Assignments>`` block, skipping any dangling task/resource ref."""
298
+ assignments_el = ET.SubElement(root, f"{{{_NS}}}Assignments")
299
+ asgn_uid = 1
300
+ for asgn in assignments:
301
+ task_uid = task_pk_to_uid.get(str(asgn.task_id))
302
+ res_uid = resource_pk_to_uid.get(str(asgn.resource_id))
303
+ if task_uid is None or res_uid is None:
304
+ continue
305
+ asgn_el = ET.SubElement(assignments_el, f"{{{_NS}}}Assignment")
306
+ _sub_text(asgn_el, "UID", str(asgn_uid))
307
+ _sub_text(asgn_el, "TaskUID", str(task_uid))
308
+ _sub_text(asgn_el, "ResourceUID", str(res_uid))
309
+ _sub_text(asgn_el, "Units", f"{float(asgn.units):.2f}")
310
+ asgn_uid += 1
311
+
312
+
313
+ def _sub_text(parent: ET.Element, tag: str, text: str) -> ET.Element:
314
+ """Add a child element with text content."""
315
+ el = ET.SubElement(parent, f"{{{_NS}}}{tag}")
316
+ el.text = text
317
+ return el
318
+
319
+
320
+ def _format_date(d: date) -> str:
321
+ """Format a date as MS Project expects: YYYY-MM-DDT00:00:00."""
322
+ return f"{d.isoformat()}T00:00:00"
323
+
324
+
325
+ def _days_to_duration(days: int) -> str:
326
+ """Convert working days to MS Project ISO 8601 duration.
327
+
328
+ Uses the nominal working-day length from the unit seam (#2290) rather than a
329
+ bare ``* 8`` so a 0.5 canonical-unit change re-homes in one place.
330
+ """
331
+ hours = days * HOURS_PER_WORKING_DAY
332
+ return f"PT{hours}H0M0S"
333
+
334
+
335
+ def _minutes_to_time(minutes: int) -> str:
336
+ """Format minutes-since-midnight as an MSPDI time-of-day (``HH:MM:00``)."""
337
+ return f"{minutes // 60:02d}:{minutes % 60:02d}:00"
338
+
339
+
340
+ def _add_calendars_block(root: ET.Element, applied_calendars: list[Any]) -> None:
341
+ """Emit ``<Calendars>`` with one base calendar folding every applied layer (#1769).
342
+
343
+ MSPDI has no equivalent of TruePPM's composable calendar layers (#906,
344
+ ADR-0251), so the export flattens them exactly the way the CPM pass composes
345
+ them: weekday masks AND-ed, exception ranges unioned, ``hours_per_day`` and
346
+ name from the base (first) calendar. The exported file then schedules on the
347
+ same non-working mask TruePPM computed, at the cost of the layer structure.
348
+
349
+ TruePPM stores only a daily hour total, not shift times, so each working day
350
+ is synthesized as a single continuous shift starting at 08:00 (falling back
351
+ to midnight when the total would run past midnight). The caller must invoke
352
+ this before the ``<Tasks>`` block is appended — ``ET.SubElement`` appends at
353
+ call time, so call ordering is the positional guarantee (same contract as
354
+ ``_add_pert_extended_attribute_defs``).
355
+ """
356
+ base = applied_calendars[0]
357
+ mask, merged_exceptions = _merge_applied_calendars(applied_calendars)
358
+
359
+ cals_el = ET.SubElement(root, f"{{{_NS}}}Calendars")
360
+ cal_el = ET.SubElement(cals_el, f"{{{_NS}}}Calendar")
361
+ _sub_text(cal_el, "UID", "1")
362
+ _sub_text(cal_el, "Name", base.name)
363
+ _sub_text(cal_el, "IsBaseCalendar", "1")
364
+ _sub_text(cal_el, "BaseCalendarUID", "-1")
365
+
366
+ start_minute = 8 * 60
367
+ shift_minutes = max(0, min(round(base.hours_per_day * 60), 24 * 60))
368
+ if start_minute + shift_minutes > 24 * 60:
369
+ start_minute = 0
370
+
371
+ _add_weekday_elements(cal_el, mask, start_minute, shift_minutes)
372
+ if merged_exceptions:
373
+ _add_calendar_exception_elements(cal_el, merged_exceptions)
374
+
375
+
376
+ def _merge_applied_calendars(
377
+ applied_calendars: list[Any],
378
+ ) -> tuple[int, dict[tuple[Any, Any], str]]:
379
+ """Fold the applied calendar layers into one working mask and exception set.
380
+
381
+ Weekday masks are AND-ed and exception ranges unioned, matching how the CPM
382
+ pass composes layers. First writer wins on a duplicate range so the base
383
+ calendar's description (not an overlay's) labels the exception.
384
+ """
385
+ mask = applied_calendars[0].working_days
386
+ merged_exceptions: dict[tuple[Any, Any], str] = {}
387
+ for cal in applied_calendars:
388
+ mask &= cal.working_days
389
+ for exc in cal.exceptions.all():
390
+ merged_exceptions.setdefault((exc.exc_start, exc.exc_end), exc.description)
391
+ return mask, merged_exceptions
392
+
393
+
394
+ def _add_weekday_elements(
395
+ cal_el: ET.Element, mask: int, start_minute: int, shift_minutes: int
396
+ ) -> None:
397
+ """Emit the seven ``<WeekDay>`` entries, each with a single synthesized shift."""
398
+ weekdays_el = ET.SubElement(cal_el, f"{{{_NS}}}WeekDays")
399
+ for day_type, bit in _DAY_TYPES_AND_BITS:
400
+ day_el = ET.SubElement(weekdays_el, f"{{{_NS}}}WeekDay")
401
+ _sub_text(day_el, "DayType", day_type)
402
+ working = bool(mask & bit)
403
+ _sub_text(day_el, "DayWorking", "1" if working else "0")
404
+ if working and shift_minutes > 0:
405
+ times_el = ET.SubElement(day_el, f"{{{_NS}}}WorkingTimes")
406
+ wt_el = ET.SubElement(times_el, f"{{{_NS}}}WorkingTime")
407
+ _sub_text(wt_el, "FromTime", _minutes_to_time(start_minute))
408
+ _sub_text(wt_el, "ToTime", _minutes_to_time(start_minute + shift_minutes))
409
+
410
+
411
+ def _add_calendar_exception_elements(
412
+ cal_el: ET.Element, merged_exceptions: dict[tuple[Any, Any], str]
413
+ ) -> None:
414
+ """Emit the ``<Exceptions>`` block for the merged non-working date ranges."""
415
+ excs_el = ET.SubElement(cal_el, f"{{{_NS}}}Exceptions")
416
+ for (exc_start, exc_end), description in sorted(merged_exceptions.items()):
417
+ exc_el = ET.SubElement(excs_el, f"{{{_NS}}}Exception")
418
+ period_el = ET.SubElement(exc_el, f"{{{_NS}}}TimePeriod")
419
+ _sub_text(period_el, "FromDate", f"{exc_start.isoformat()}T00:00:00")
420
+ _sub_text(period_el, "ToDate", f"{exc_end.isoformat()}T23:59:00")
421
+ if description:
422
+ _sub_text(exc_el, "Name", description)
423
+ _sub_text(exc_el, "DayWorking", "0")
424
+
425
+
426
+ def _add_summary_task(tasks_el: ET.Element, project: object) -> None:
427
+ """Add the UID 0 project summary task."""
428
+ task_el = ET.SubElement(tasks_el, f"{{{_NS}}}Task")
429
+ _sub_text(task_el, "UID", "0")
430
+ _sub_text(task_el, "Name", project.name) # type: ignore[attr-defined]
431
+ _sub_text(task_el, "OutlineLevel", "0")
432
+ _sub_text(task_el, "OutlineNumber", "0")
433
+
434
+
435
+ def _summary_pks_from_tasks(tasks: list[Any]) -> set[str]:
436
+ """Return the set of task primary keys that are summary tasks.
437
+
438
+ A task is a summary when another task's ``wbs_path`` is strictly descended
439
+ from this one's (e.g. ``"1"`` is a summary if any task has path ``"1.1"``).
440
+ O(N) over the task list: build the set of strict ancestor paths from every
441
+ leaf path in one pass, then mark every task whose own path is in that set.
442
+ """
443
+ ancestor_paths: set[str] = set()
444
+ for t in tasks:
445
+ path = getattr(t, "wbs_path", None)
446
+ if not path:
447
+ continue
448
+ path = str(path)
449
+ parts = path.split(".")
450
+ for end in range(1, len(parts)):
451
+ ancestor_paths.add(".".join(parts[:end]))
452
+ return {
453
+ str(t.pk)
454
+ for t in tasks
455
+ if getattr(t, "wbs_path", None) and str(t.wbs_path) in ancestor_paths
456
+ }
457
+
458
+
459
+ def _add_pert_extended_attribute_defs(root: ET.Element) -> None:
460
+ """Emit the four PERT ExtendedAttribute definitions at project level.
461
+
462
+ Duration1–3 carry the aliases Optimistic / Most Likely / Pessimistic.
463
+ Duration4 carries the formula MS Project uses to derive PERT Expected
464
+ on file open. The caller is responsible for invoking this **before** the
465
+ ``<Tasks>`` block is appended, so the resulting element order
466
+ (``<ExtendedAttributes>`` then ``<Tasks>``) matches MS Project's own
467
+ element order. ``ET.SubElement`` appends at call time, so call ordering
468
+ is the positional guarantee — don't move this call after ``<Tasks>``.
469
+ """
470
+ ea_block = ET.SubElement(root, f"{{{_NS}}}ExtendedAttributes")
471
+ for fid in (DURATION1_FIELD_ID, DURATION2_FIELD_ID, DURATION3_FIELD_ID, DURATION4_FIELD_ID):
472
+ ea = ET.SubElement(ea_block, f"{{{_NS}}}ExtendedAttribute")
473
+ _sub_text(ea, "FieldID", fid)
474
+ _sub_text(ea, "FieldName", PERT_FIELD_NAMES[fid])
475
+ _sub_text(ea, "Alias", PERT_ALIAS_LABELS[fid])
476
+ if fid == DURATION4_FIELD_ID:
477
+ _sub_text(ea, "Formula", PERT_EXPECTED_FORMULA)
478
+
479
+
480
+ def _add_pert_task_values(
481
+ task_el: ET.Element,
482
+ optimistic_days: int,
483
+ most_likely_days: int,
484
+ pessimistic_days: int,
485
+ ) -> None:
486
+ """Emit per-task <ExtendedAttribute> values for Duration1/2/3.
487
+
488
+ Values are encoded as ISO-8601 ``PT{hours}H0M0S`` (8h working day) using
489
+ the same convention as the primary Duration field. ``DurationFormat=7``
490
+ marks the value as a duration in days for MS Project's UI.
491
+ """
492
+ for fid, days in (
493
+ (DURATION1_FIELD_ID, optimistic_days),
494
+ (DURATION2_FIELD_ID, most_likely_days),
495
+ (DURATION3_FIELD_ID, pessimistic_days),
496
+ ):
497
+ ea = ET.SubElement(task_el, f"{{{_NS}}}ExtendedAttribute")
498
+ _sub_text(ea, "FieldID", fid)
499
+ _sub_text(ea, "Value", _days_to_duration(days))
500
+ _sub_text(ea, "DurationFormat", "7")
@@ -0,0 +1,77 @@
1
+ """MS Project ExtendedAttribute FieldID constants and PERT mapping.
2
+
3
+ These FieldIDs are the canonical MS Project Custom Field identifiers for the
4
+ idiomatic three-point / PERT estimate convention. They are sourced from MPXJ's
5
+ ``TaskField`` enum (via the mpp-sample-generator's ``field_id_reference.md``)
6
+ and cross-checked against Microsoft's one documented anchor:
7
+ ``pjCustomTaskText1 = 188743731``
8
+ (https://learn.microsoft.com/en-us/previous-versions/office/developer/office-2007/bb968474(v=office.12)).
9
+
10
+ The Duration family is **non-contiguous** within the Custom Field enumeration:
11
+ ``Duration1``–``Duration3`` sit at 188743783–785 (contiguous), but ``Duration4``
12
+ (the PERT-Expected formula slot) lives 170 IDs higher at 188743955, in a
13
+ separate sub-range. Inferring one Duration FieldID by offset arithmetic from
14
+ another is wrong. Use these named constants — never a range.
15
+
16
+ ADR-0093 covers the import/export mapping decisions (alias-vs-FieldID
17
+ precedence, all-or-none rule, summary/milestone skipping, round-trip
18
+ tolerance).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ # Locked FieldIDs (sourced, not guessed). Strings because MSPDI carries them
24
+ # as text and we compare against text from the parsed XML.
25
+ DURATION1_FIELD_ID = "188743783" # Optimistic
26
+ DURATION2_FIELD_ID = "188743784" # Most Likely
27
+ DURATION3_FIELD_ID = "188743785" # Pessimistic
28
+ DURATION4_FIELD_ID = "188743955" # PERT Expected (formula slot, not imported)
29
+
30
+ # Semantic role -> FieldID. Duration4 is informational only: the formula slot
31
+ # is derived from the other three at file-read time by MS Project, so the
32
+ # importer never reads its value. The exporter still emits the definition so
33
+ # Project knows the alias and formula.
34
+ PERT_FIELD_IDS: dict[str, str] = {
35
+ "optimistic": DURATION1_FIELD_ID,
36
+ "most_likely": DURATION2_FIELD_ID,
37
+ "pessimistic": DURATION3_FIELD_ID,
38
+ "expected": DURATION4_FIELD_ID,
39
+ }
40
+
41
+ # FieldID -> the role it should play (used during import to recognize standard
42
+ # definitions).
43
+ PERT_ROLE_BY_FIELD_ID: dict[str, str] = {v: k for k, v in PERT_FIELD_IDS.items()}
44
+
45
+ # Canonical alias labels emitted on export.
46
+ PERT_ALIAS_LABELS: dict[str, str] = {
47
+ DURATION1_FIELD_ID: "Optimistic",
48
+ DURATION2_FIELD_ID: "Most Likely",
49
+ DURATION3_FIELD_ID: "Pessimistic",
50
+ DURATION4_FIELD_ID: "PERT Expected",
51
+ }
52
+
53
+ # Canonical FieldName labels (Duration1..4). MS Project uses these as the
54
+ # native field identifiers; the Alias is the user-visible label.
55
+ PERT_FIELD_NAMES: dict[str, str] = {
56
+ DURATION1_FIELD_ID: "Duration1",
57
+ DURATION2_FIELD_ID: "Duration2",
58
+ DURATION3_FIELD_ID: "Duration3",
59
+ DURATION4_FIELD_ID: "Duration4",
60
+ }
61
+
62
+ # Duration4 carries the PERT expected formula so MS Project re-derives the
63
+ # value on file open. Not a TruePPM field.
64
+ PERT_EXPECTED_FORMULA = "([Duration1] + 4*[Duration2] + [Duration3]) / 6"
65
+
66
+ # Substring tokens for alias confirmation (case-insensitive, first-match).
67
+ # These let us spot when a file has repurposed Duration1/2/3 with an alias
68
+ # that explicitly contradicts the PERT role (e.g. Duration1 aliased
69
+ # "Risk Score") and refuse the import for that field rather than silently
70
+ # treat unrelated values as estimates. Empty / missing aliases are treated
71
+ # as "no contradiction" — we trust the FieldID.
72
+ PERT_ALIAS_TOKENS: dict[str, tuple[str, ...]] = {
73
+ DURATION1_FIELD_ID: ("optimistic",),
74
+ DURATION2_FIELD_ID: ("most likely", "most-likely"),
75
+ DURATION3_FIELD_ID: ("pessimistic",),
76
+ DURATION4_FIELD_ID: ("pert", "expected"),
77
+ }