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.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"""Service + pure-compute layer for the Product-Owner backlog (ADR-0105).
|
|
2
|
+
|
|
3
|
+
Separated from the viewset so the logic is unit-testable and has one source of truth:
|
|
4
|
+
|
|
5
|
+
1. **Scoring** — ``compute_score`` derives a WSJF / RICE / value-effort score from a task's
|
|
6
|
+
distinct per-model input columns for the project's active model. Computed, never stored.
|
|
7
|
+
2. **Definition of Ready** — ``dor_blockers`` enumerates why a story may not be marked READY;
|
|
8
|
+
``ac_counts`` powers the acceptance-criteria meter (from the AcceptanceCriterion child rows).
|
|
9
|
+
The READY transition is gated on an empty blocker list but is advisory (it does not block
|
|
10
|
+
the team from pulling a story into a sprint via the planning/scope path).
|
|
11
|
+
3. **Auto-rank** — ``auto_rank`` is a one-shot sort of the project backlog by the active model's
|
|
12
|
+
score, writing the result into ``priority_rank`` (manual rank as tiebreaker). There is no
|
|
13
|
+
persistent lock — a later manual drag always wins. Iterated via ``save()`` so each row bumps
|
|
14
|
+
``server_version`` and writes ``HistoricalTask`` (audit).
|
|
15
|
+
4. **Dual ordering** — ``seed_sprint_rank`` seeds the within-sprint execution order from
|
|
16
|
+
``priority_rank`` at sprint commit; in-sprint reorders write ``sprint_rank`` only (ADR-0105 §5).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from django.db import transaction
|
|
25
|
+
from django.utils import timezone
|
|
26
|
+
|
|
27
|
+
from trueppm_api.apps.projects.models import (
|
|
28
|
+
AcceptanceCriterion,
|
|
29
|
+
DorState,
|
|
30
|
+
PrioritizationModel,
|
|
31
|
+
Project,
|
|
32
|
+
Task,
|
|
33
|
+
TaskStatus,
|
|
34
|
+
TaskType,
|
|
35
|
+
)
|
|
36
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
37
|
+
|
|
38
|
+
# ── Scoring ─────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def compute_score(task: Task, model: str) -> float | None:
|
|
42
|
+
"""Return the prioritization score for ``task`` under ``model``, or None.
|
|
43
|
+
|
|
44
|
+
WSJF = (business_value+time_criticality+risk_reduction)/job_size,
|
|
45
|
+
RICE = (reach*impact*confidence)/effort, value_effort = value/effort_estimate.
|
|
46
|
+
Returns None when any required input is missing or the denominator is zero/absent —
|
|
47
|
+
an unscored story sorts last and renders as "—" rather than a misleading 0.
|
|
48
|
+
"""
|
|
49
|
+
if model == PrioritizationModel.WSJF:
|
|
50
|
+
bv, tc, rr = task.business_value, task.time_criticality, task.risk_reduction
|
|
51
|
+
size = task.job_size
|
|
52
|
+
if bv is None or tc is None or rr is None or not size:
|
|
53
|
+
return None
|
|
54
|
+
return (bv + tc + rr) / size
|
|
55
|
+
if model == PrioritizationModel.RICE:
|
|
56
|
+
reach, impact, confidence, effort = task.reach, task.impact, task.confidence, task.effort
|
|
57
|
+
if reach is None or impact is None or confidence is None or not effort:
|
|
58
|
+
return None
|
|
59
|
+
return (reach * impact * confidence) / effort
|
|
60
|
+
if model == PrioritizationModel.VALUE_EFFORT:
|
|
61
|
+
if task.value is None or not task.effort_estimate:
|
|
62
|
+
return None
|
|
63
|
+
return task.value / task.effort_estimate
|
|
64
|
+
return None # NONE or unknown
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ── Acceptance criteria + Definition of Ready ────────────────────────────────
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def ac_counts(task: Task) -> tuple[int, int]:
|
|
71
|
+
"""Return (met, total) acceptance criteria for the AC meter (DA-10/DA-14).
|
|
72
|
+
|
|
73
|
+
Uses the prefetched ``acceptance_criteria`` related rows when available (the grooming
|
|
74
|
+
endpoint prefetches them) so this stays O(1) queries per task.
|
|
75
|
+
"""
|
|
76
|
+
criteria = list(task.acceptance_criteria.all())
|
|
77
|
+
return (sum(1 for c in criteria if c.met), len(criteria))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def dor_blockers(task: Task) -> list[str]:
|
|
81
|
+
"""Reasons ``task`` cannot be marked READY (empty list ⇒ it may go Ready).
|
|
82
|
+
|
|
83
|
+
The Mark-ready gate (#731): estimated AND at least one acceptance criterion AND every
|
|
84
|
+
criterion met. Machine-stable codes the web maps to the drawer's DoR checklist copy.
|
|
85
|
+
"""
|
|
86
|
+
blockers: list[str] = []
|
|
87
|
+
if task.story_points is None:
|
|
88
|
+
blockers.append("unestimated")
|
|
89
|
+
met, total = ac_counts(task)
|
|
90
|
+
if total == 0:
|
|
91
|
+
blockers.append("no_acceptance_criteria")
|
|
92
|
+
elif met < total:
|
|
93
|
+
blockers.append("acceptance_criteria_unmet")
|
|
94
|
+
return blockers
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def apply_acceptance_met_change(
|
|
98
|
+
criterion: AcceptanceCriterion,
|
|
99
|
+
*,
|
|
100
|
+
was_met: bool,
|
|
101
|
+
actor: Any,
|
|
102
|
+
now: datetime | None = None,
|
|
103
|
+
) -> bool:
|
|
104
|
+
"""Persist a ``met`` flip and (un)stamp the review trail — the single attribution rule.
|
|
105
|
+
|
|
106
|
+
The ONE writer of the ``met_by``/``met_at`` trail (ADR-0105 §2 / ADR-0148), shared by
|
|
107
|
+
the interactive ``AcceptanceCriterionViewSet`` and the CI ingestion endpoint so the
|
|
108
|
+
attribution rule is declared once and the two paths can never drift. The caller has
|
|
109
|
+
already set ``criterion.met`` to its new value; this persists it together with the
|
|
110
|
+
trail in a single ``save()`` (one ``server_version`` bump). ``actor`` is stamped as
|
|
111
|
+
``met_by`` on a become-met flip (the human who minted the CI token, never the CI
|
|
112
|
+
system itself, mirroring ``ProjectApiTokenAuthentication``'s attribution); the trail
|
|
113
|
+
is cleared on a become-unmet flip. Idempotent: a no-op flip returns ``False`` and
|
|
114
|
+
writes nothing — so a CI job re-reporting the same result does not churn the version
|
|
115
|
+
or restamp attribution.
|
|
116
|
+
"""
|
|
117
|
+
if criterion.met == was_met:
|
|
118
|
+
return False
|
|
119
|
+
if criterion.met:
|
|
120
|
+
criterion.met_by = actor
|
|
121
|
+
criterion.met_at = now or timezone.now()
|
|
122
|
+
else:
|
|
123
|
+
criterion.met_by = None
|
|
124
|
+
criterion.met_at = None
|
|
125
|
+
criterion.save(update_fields=["met", "met_by", "met_at", "server_version"])
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class DorTransitionError(Exception):
|
|
130
|
+
"""Raised when a READY transition is attempted with unresolved DoR blockers."""
|
|
131
|
+
|
|
132
|
+
def __init__(self, blockers: list[str]) -> None:
|
|
133
|
+
self.blockers = blockers
|
|
134
|
+
super().__init__(f"Cannot mark ready: {', '.join(blockers)}")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def mark_ready(task: Task, actor: Any) -> Task:
|
|
138
|
+
"""Transition a story to READY, enforcing the (advisory) DoR gate. Idempotent."""
|
|
139
|
+
blockers = dor_blockers(task)
|
|
140
|
+
if blockers:
|
|
141
|
+
raise DorTransitionError(blockers)
|
|
142
|
+
if task.dor != DorState.READY:
|
|
143
|
+
task.dor = DorState.READY
|
|
144
|
+
task.save(update_fields=["dor", "server_version"])
|
|
145
|
+
_broadcast_task(task)
|
|
146
|
+
return task
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def send_to_refine(task: Task, actor: Any) -> Task:
|
|
150
|
+
"""Move a story back to REFINE (no gate — refining is always allowed)."""
|
|
151
|
+
if task.dor != DorState.REFINE:
|
|
152
|
+
task.dor = DorState.REFINE
|
|
153
|
+
task.save(update_fields=["dor", "server_version"])
|
|
154
|
+
_broadcast_task(task)
|
|
155
|
+
return task
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _broadcast_task(task: Task) -> None:
|
|
159
|
+
pid, tid = str(task.project_id), str(task.id)
|
|
160
|
+
transaction.on_commit(lambda: broadcast_board_event(pid, "task_updated", {"id": tid}))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ── Auto-rank ────────────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _backlog_stories(project: Project) -> list[Task]:
|
|
167
|
+
"""Project-backlog stories eligible for auto-rank.
|
|
168
|
+
|
|
169
|
+
Scoped to ``status=BACKLOG`` and ``sprint__isnull=True`` so re-ranking never touches a
|
|
170
|
+
task's sprint membership or its ADR-0102 ``sprint_pending`` flag. Epics excluded.
|
|
171
|
+
"""
|
|
172
|
+
return list(
|
|
173
|
+
Task.objects.filter(
|
|
174
|
+
project=project,
|
|
175
|
+
is_deleted=False,
|
|
176
|
+
status=TaskStatus.BACKLOG,
|
|
177
|
+
sprint__isnull=True,
|
|
178
|
+
)
|
|
179
|
+
.exclude(type=TaskType.EPIC)
|
|
180
|
+
.order_by("priority_rank", "short_id")
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def auto_rank(project: Project, actor: Any) -> int:
|
|
185
|
+
"""One-shot recompute of ``priority_rank`` from the active scoring model.
|
|
186
|
+
|
|
187
|
+
Scored stories sort by score descending; ties (and unscored stories) keep their existing
|
|
188
|
+
manual order (``priority_rank`` asc, then ``short_id``). Returns the count of rows whose
|
|
189
|
+
rank changed. Idempotent. Not a persistent lock — a later manual drag rewrites
|
|
190
|
+
``priority_rank`` and wins.
|
|
191
|
+
"""
|
|
192
|
+
model = project.prioritization_model
|
|
193
|
+
stories = _backlog_stories(project)
|
|
194
|
+
|
|
195
|
+
def sort_key(item: tuple[int, Task]) -> tuple[int, float, int]:
|
|
196
|
+
manual_index, task = item
|
|
197
|
+
score = compute_score(task, model)
|
|
198
|
+
if score is None:
|
|
199
|
+
return (1, 0.0, manual_index)
|
|
200
|
+
return (0, -score, manual_index)
|
|
201
|
+
|
|
202
|
+
ordered = [t for _, t in sorted(enumerate(stories), key=sort_key)]
|
|
203
|
+
|
|
204
|
+
changed = 0
|
|
205
|
+
with transaction.atomic():
|
|
206
|
+
for new_rank, task in enumerate(ordered, start=1):
|
|
207
|
+
if task.priority_rank != new_rank:
|
|
208
|
+
task.priority_rank = new_rank
|
|
209
|
+
# iterate save() (never bulk_update) so server_version bumps + history writes.
|
|
210
|
+
task.save(update_fields=["priority_rank", "server_version"])
|
|
211
|
+
changed += 1
|
|
212
|
+
|
|
213
|
+
if changed:
|
|
214
|
+
pid = str(project.id)
|
|
215
|
+
transaction.on_commit(
|
|
216
|
+
lambda: broadcast_board_event(pid, "backlog_reranked", {"project_id": pid})
|
|
217
|
+
)
|
|
218
|
+
return changed
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ── Manual reorder (ADR-0110, #494) ───────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class BacklogReorderConflict(Exception):
|
|
225
|
+
"""Raised when the client's backlog snapshot is stale (ADR-0110 §3).
|
|
226
|
+
|
|
227
|
+
Covers every "the backlog moved under you" case — a story added or removed
|
|
228
|
+
concurrently (the supplied set no longer matches the live backlog) or a row whose
|
|
229
|
+
``server_version`` advanced since the client loaded it. The view maps this to 409 and
|
|
230
|
+
the client refetches + replays the drag. ``ids`` lists the offending task ids.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(self, ids: list[str]) -> None:
|
|
234
|
+
self.ids = ids
|
|
235
|
+
super().__init__(f"Stale backlog snapshot for tasks: {', '.join(ids)}")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def reorder_backlog(project: Project, ordered: list[tuple[str, int]], actor: Any) -> int:
|
|
239
|
+
"""Apply a manual drag reorder of the project backlog (ADR-0110, #494).
|
|
240
|
+
|
|
241
|
+
``ordered`` is the *complete* current backlog as ``[(task_id, server_version), ...]`` in
|
|
242
|
+
the new priority order. Renumbers ``priority_rank`` densely 1..N (matching ``auto_rank``'s
|
|
243
|
+
output shape) so a later auto-rank diff stays clean; sparse keys buy nothing under a
|
|
244
|
+
full-list rewrite. Optimistic-locked on ``server_version`` and on set-completeness: if the
|
|
245
|
+
supplied set differs from the live backlog (concurrent add/remove) or any row's version is
|
|
246
|
+
stale, raises :class:`BacklogReorderConflict` (409) and writes nothing.
|
|
247
|
+
|
|
248
|
+
Writes only changed rows, each via ``save()`` (never ``bulk_update``) so ``server_version``
|
|
249
|
+
bumps and ``HistoricalTask`` is written. Returns the count of rows whose rank changed.
|
|
250
|
+
Idempotent: re-applying the same order writes nothing. Rank-only — never touches
|
|
251
|
+
``sprint_rank`` or ``parent_epic`` (ADR-0110 §5).
|
|
252
|
+
"""
|
|
253
|
+
changed = 0
|
|
254
|
+
with transaction.atomic():
|
|
255
|
+
# Lock the live backlog rows first to serialise concurrent reorders, scoped exactly
|
|
256
|
+
# like _backlog_stories (BACKLOG, sprint-less, non-epic).
|
|
257
|
+
live = (
|
|
258
|
+
Task.objects.select_for_update()
|
|
259
|
+
.filter(
|
|
260
|
+
project=project,
|
|
261
|
+
is_deleted=False,
|
|
262
|
+
status=TaskStatus.BACKLOG,
|
|
263
|
+
sprint__isnull=True,
|
|
264
|
+
)
|
|
265
|
+
.exclude(type=TaskType.EPIC)
|
|
266
|
+
)
|
|
267
|
+
by_id = {str(t.pk): t for t in live}
|
|
268
|
+
supplied = {tid for tid, _ in ordered}
|
|
269
|
+
|
|
270
|
+
# Completeness + membership: the client must hold exactly the current backlog. Any
|
|
271
|
+
# drift (story added/removed since load, or an id that isn't a current backlog story)
|
|
272
|
+
# is a stale snapshot → 409, not a partial write.
|
|
273
|
+
drift = sorted((supplied - by_id.keys()) | (by_id.keys() - supplied))
|
|
274
|
+
if drift:
|
|
275
|
+
raise BacklogReorderConflict(drift)
|
|
276
|
+
|
|
277
|
+
# Per-row optimistic lock.
|
|
278
|
+
stale = [tid for tid, sv in ordered if by_id[tid].server_version != sv]
|
|
279
|
+
if stale:
|
|
280
|
+
raise BacklogReorderConflict(stale)
|
|
281
|
+
|
|
282
|
+
for new_rank, (tid, _) in enumerate(ordered, start=1):
|
|
283
|
+
task = by_id[tid]
|
|
284
|
+
if task.priority_rank != new_rank:
|
|
285
|
+
task.priority_rank = new_rank
|
|
286
|
+
task.save(update_fields=["priority_rank", "server_version"])
|
|
287
|
+
changed += 1
|
|
288
|
+
|
|
289
|
+
# Registered inside the atomic block so it defers to commit (and fires only within a
|
|
290
|
+
# transaction). pid is a non-mutating local, so the plain closure capture is safe —
|
|
291
|
+
# matching the auto_rank pattern.
|
|
292
|
+
if changed:
|
|
293
|
+
pid = str(project.id)
|
|
294
|
+
transaction.on_commit(
|
|
295
|
+
lambda: broadcast_board_event(pid, "backlog_reranked", {"project_id": pid})
|
|
296
|
+
)
|
|
297
|
+
return changed
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# ── Dual ordering (#365) ──────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def seed_sprint_rank(sprint: Any) -> None:
|
|
304
|
+
"""Seed within-sprint execution order from product-backlog priority at commit.
|
|
305
|
+
|
|
306
|
+
Called when a sprint activates (PLANNED→ACTIVE). Orders the sprint's tasks by
|
|
307
|
+
``priority_rank`` and writes ``sprint_rank`` 1..N. In-sprint reorders thereafter write
|
|
308
|
+
``sprint_rank`` only and never touch ``priority_rank`` (ADR-0105 §5).
|
|
309
|
+
"""
|
|
310
|
+
tasks = list(
|
|
311
|
+
Task.objects.filter(sprint=sprint, is_deleted=False)
|
|
312
|
+
.exclude(type=TaskType.EPIC)
|
|
313
|
+
.order_by("priority_rank", "short_id")
|
|
314
|
+
)
|
|
315
|
+
with transaction.atomic():
|
|
316
|
+
for rank, task in enumerate(tasks, start=1):
|
|
317
|
+
if task.sprint_rank != rank:
|
|
318
|
+
task.sprint_rank = rank
|
|
319
|
+
task.save(update_fields=["sprint_rank", "server_version"])
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class SprintReorderConflict(Exception):
|
|
323
|
+
"""Raised when an in-sprint reorder can't apply because the sprint backlog drifted.
|
|
324
|
+
|
|
325
|
+
Mirrors :class:`BacklogReorderConflict`: a concurrent add/remove/move means the
|
|
326
|
+
client's snapshot is stale, so the whole reorder is a 409 (write nothing) and the
|
|
327
|
+
client refetches + replays the drag. ``ids`` lists the offending task ids.
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
def __init__(self, ids: list[str]) -> None:
|
|
331
|
+
self.ids = ids
|
|
332
|
+
super().__init__(f"Stale sprint snapshot for tasks: {', '.join(ids)}")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def reorder_sprint(sprint: Any, ordered: list[tuple[str, int]], actor: Any) -> int:
|
|
336
|
+
"""Apply a manual drag reorder of a sprint's within-sprint execution order (#365).
|
|
337
|
+
|
|
338
|
+
The sprint-backlog analog of :func:`reorder_backlog` (ADR-0105 §5): writes ``sprint_rank``
|
|
339
|
+
*only* and never touches ``priority_rank``, so reordering for execution never disturbs the
|
|
340
|
+
Product Owner's product-backlog ranking. ``ordered`` is the *complete* current sprint set as
|
|
341
|
+
``[(task_id, server_version), ...]`` in the new execution order, scoped exactly like
|
|
342
|
+
:func:`seed_sprint_rank` (the sprint's live, non-deleted, non-epic tasks).
|
|
343
|
+
|
|
344
|
+
Renumbers ``sprint_rank`` densely 1..N. Optimistic-locked on ``server_version`` and on
|
|
345
|
+
set-completeness: if the supplied set differs from the live sprint set (concurrent
|
|
346
|
+
add/remove/carry) or any row's version is stale, raises :class:`SprintReorderConflict`
|
|
347
|
+
(409) and writes nothing. Writes only changed rows, each via ``save()`` so
|
|
348
|
+
``server_version`` bumps and ``HistoricalTask`` is written. Returns the count of rows
|
|
349
|
+
whose rank changed; idempotent (re-applying the same order writes nothing).
|
|
350
|
+
"""
|
|
351
|
+
changed = 0
|
|
352
|
+
with transaction.atomic():
|
|
353
|
+
# Lock the live sprint rows first to serialise concurrent reorders, scoped exactly
|
|
354
|
+
# like seed_sprint_rank (the set that was seeded at commit): sprint members, non-epic.
|
|
355
|
+
live = (
|
|
356
|
+
Task.objects.select_for_update()
|
|
357
|
+
.filter(sprint=sprint, is_deleted=False)
|
|
358
|
+
.exclude(type=TaskType.EPIC)
|
|
359
|
+
)
|
|
360
|
+
by_id = {str(t.pk): t for t in live}
|
|
361
|
+
supplied = {tid for tid, _ in ordered}
|
|
362
|
+
|
|
363
|
+
# Completeness + membership: the client must hold exactly the current sprint set. Any
|
|
364
|
+
# drift (task pulled/carried since load, or an id that isn't a current sprint task) is
|
|
365
|
+
# a stale snapshot → 409, not a partial write.
|
|
366
|
+
drift = sorted((supplied - by_id.keys()) | (by_id.keys() - supplied))
|
|
367
|
+
if drift:
|
|
368
|
+
raise SprintReorderConflict(drift)
|
|
369
|
+
|
|
370
|
+
# Per-row optimistic lock.
|
|
371
|
+
stale = [tid for tid, sv in ordered if by_id[tid].server_version != sv]
|
|
372
|
+
if stale:
|
|
373
|
+
raise SprintReorderConflict(stale)
|
|
374
|
+
|
|
375
|
+
for new_rank, (tid, _) in enumerate(ordered, start=1):
|
|
376
|
+
task = by_id[tid]
|
|
377
|
+
if task.sprint_rank != new_rank:
|
|
378
|
+
task.sprint_rank = new_rank
|
|
379
|
+
task.save(update_fields=["sprint_rank", "server_version"])
|
|
380
|
+
changed += 1
|
|
381
|
+
|
|
382
|
+
# Registered inside the atomic block so it defers to commit (and fires only within a
|
|
383
|
+
# transaction). The board channel is the project; sprint_id lets the client scope the
|
|
384
|
+
# refetch. pid/sid are stable locals (not loop vars), so a plain closure is safe.
|
|
385
|
+
if changed:
|
|
386
|
+
pid = str(sprint.project_id)
|
|
387
|
+
sid = str(sprint.id)
|
|
388
|
+
transaction.on_commit(
|
|
389
|
+
lambda: broadcast_board_event(
|
|
390
|
+
pid, "sprint_reranked", {"project_id": pid, "sprint_id": sid}
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
return changed
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
# ── Split story ──────────────────────────────────────────────────────────────
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def split_story(parent: Task, actor: Any, *, name: str | None = None) -> Task:
|
|
400
|
+
"""Create a sibling story under the same epic, carrying over unmet criteria.
|
|
401
|
+
|
|
402
|
+
What the child inherits, and why each answer is what it is:
|
|
403
|
+
|
|
404
|
+
* ``parent_epic`` — so the split stays grouped under the same epic.
|
|
405
|
+
* ``priority_rank`` — the parent's position, verbatim (#3002). A split of the #3
|
|
406
|
+
story is still the #3 story's work; leaving it null sorts the child **below every
|
|
407
|
+
ranked item** (the client's 9999 sentinel for "unranked"), which is close to the
|
|
408
|
+
opposite of where the work belongs. Ranks are not unique and ties already break on
|
|
409
|
+
a secondary sort, so both halves sitting at the parent's position is harmless.
|
|
410
|
+
Ranking *after* the parent was rejected: it renumbers neighbors, and ``auto_rank``
|
|
411
|
+
/ ``reorder`` already own the rank space — a third writer of it is a worse problem
|
|
412
|
+
than a tie. This matches what #2906 decided for the program-backlog pull.
|
|
413
|
+
* Only **unmet** acceptance criteria are copied — the remaining work.
|
|
414
|
+
* Story points are **not** divided, and no estimate field carries: the PO re-estimates
|
|
415
|
+
both halves, so velocity is never double-counted. This is the deliberate asymmetry
|
|
416
|
+
with ``priority_rank`` — *where* the work sits is known and worth preserving, *how
|
|
417
|
+
big* it is has to be answered again.
|
|
418
|
+
|
|
419
|
+
``test_every_task_field_is_classified_for_the_split`` holds the rest of the field set
|
|
420
|
+
to the same standard: every concrete ``Task`` field is classified as carried or
|
|
421
|
+
explicitly not, so adding one to the model forces the decision rather than defaulting
|
|
422
|
+
it to "dropped" the way ``priority_rank`` was.
|
|
423
|
+
"""
|
|
424
|
+
from trueppm_api.apps.projects.models import AcceptanceCriterion
|
|
425
|
+
|
|
426
|
+
unmet = [c for c in parent.acceptance_criteria.all() if not c.met]
|
|
427
|
+
with transaction.atomic():
|
|
428
|
+
child = Task.objects.create(
|
|
429
|
+
project=parent.project,
|
|
430
|
+
name=name or f"{parent.name} (split)",
|
|
431
|
+
type=parent.type,
|
|
432
|
+
parent_epic=parent.parent_epic,
|
|
433
|
+
priority_rank=parent.priority_rank,
|
|
434
|
+
status=TaskStatus.BACKLOG,
|
|
435
|
+
sprint=None,
|
|
436
|
+
dor=DorState.IDEA,
|
|
437
|
+
notes=f"Split from {parent.short_id or parent.id}.",
|
|
438
|
+
)
|
|
439
|
+
for pos, c in enumerate(unmet):
|
|
440
|
+
AcceptanceCriterion.objects.create(
|
|
441
|
+
task=child,
|
|
442
|
+
text=c.text,
|
|
443
|
+
given=c.given,
|
|
444
|
+
when=c.when,
|
|
445
|
+
then=c.then,
|
|
446
|
+
position=pos,
|
|
447
|
+
)
|
|
448
|
+
pid, cid = str(parent.project_id), str(child.id)
|
|
449
|
+
transaction.on_commit(lambda: broadcast_board_event(pid, "task_created", {"id": cid}))
|
|
450
|
+
return child
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Program-scoped label reads (ADR-0638, issue #2333).
|
|
2
|
+
|
|
3
|
+
Labels are project-scoped (`unique(project, name)`), so "every task tagged
|
|
4
|
+
`security-review` in this program" cannot be answered by label id — the same name
|
|
5
|
+
is a different row, with a different id and possibly a different color, in every
|
|
6
|
+
project that uses it. These helpers answer it by **name** instead.
|
|
7
|
+
|
|
8
|
+
Both functions take an explicit ``project_ids`` iterable rather than a ``Program``.
|
|
9
|
+
That is deliberate and load-bearing (ADR-0638 Decision 4): it is the seam
|
|
10
|
+
``trueppm-enterprise`` calls with a *portfolio's* project set to build the
|
|
11
|
+
cross-program view, without the OSS package ever learning that portfolios exist.
|
|
12
|
+
Callers in this repo are responsible for passing only project ids the requesting
|
|
13
|
+
user may read — the viewset does that, and these functions do not re-check.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
from typing import TypedDict
|
|
20
|
+
from uuid import UUID
|
|
21
|
+
|
|
22
|
+
from django.db.models import Count, Min, Prefetch, QuerySet
|
|
23
|
+
from django.db.models.functions import Lower
|
|
24
|
+
|
|
25
|
+
from trueppm_api.apps.projects.models import Label, Task
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LabelCatalogEntry(TypedDict):
|
|
29
|
+
"""One distinct label name across a set of projects."""
|
|
30
|
+
|
|
31
|
+
name: str
|
|
32
|
+
project_count: int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def tasks_by_label_name(*, project_ids: Iterable[UUID | str], label_name: str) -> QuerySet[Task]:
|
|
36
|
+
"""Tasks in ``project_ids`` carrying a label whose name matches ``label_name``.
|
|
37
|
+
|
|
38
|
+
Matching is **case-insensitive** (ADR-0638 Decision 3): a PM who typed
|
|
39
|
+
``Security-Review`` in one project and ``security-review`` in another means one
|
|
40
|
+
concept, and treating them as two is a bug report waiting to happen.
|
|
41
|
+
|
|
42
|
+
Ordering is ``(project.name, wbs_path)`` — grouped by project because the
|
|
43
|
+
user's first question about a cross-project row is "which project is this in",
|
|
44
|
+
then plan order within each group. The ordering is total and stable, which
|
|
45
|
+
matters for pagination: a non-deterministic order across projects lets a page
|
|
46
|
+
boundary silently skip rows.
|
|
47
|
+
"""
|
|
48
|
+
return (
|
|
49
|
+
Task.objects.filter(
|
|
50
|
+
project_id__in=list(project_ids),
|
|
51
|
+
is_deleted=False,
|
|
52
|
+
labels__name__iexact=label_name,
|
|
53
|
+
labels__is_deleted=False,
|
|
54
|
+
)
|
|
55
|
+
.select_related("project")
|
|
56
|
+
# Soft-deleted labels must never render as a pill — the established
|
|
57
|
+
# Prefetch pattern for every label read.
|
|
58
|
+
.prefetch_related(Prefetch("labels", queryset=Label.objects.filter(is_deleted=False)))
|
|
59
|
+
# A task carrying two labels that both match would otherwise repeat.
|
|
60
|
+
.distinct()
|
|
61
|
+
.order_by("project__name", "wbs_path")
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def program_label_catalog(*, project_ids: Iterable[UUID | str]) -> list[LabelCatalogEntry]:
|
|
66
|
+
"""Distinct label names across ``project_ids``, with the count of projects using each.
|
|
67
|
+
|
|
68
|
+
The count is of **projects carrying the name**, not of tasks. A task count
|
|
69
|
+
would mean joining the whole task table to render a picker — a read that looks
|
|
70
|
+
free at five projects and is not at fifty (ADR-0638 Decision 5). This is the
|
|
71
|
+
one place the program view deliberately differs from ADR-0620, where per-label
|
|
72
|
+
counts are computed client-side over already-loaded rows; here there is no
|
|
73
|
+
single loaded row set to count over.
|
|
74
|
+
|
|
75
|
+
Grouping is **case-insensitive**, and must be: ``tasks_by_label_name`` matches
|
|
76
|
+
with ``iexact``, so if the catalog grouped by exact name then ``Security-Review``
|
|
77
|
+
and ``security-review`` would list as two entries that return byte-identical
|
|
78
|
+
result sets — the picker would contradict the filter. Rows are keyed on the
|
|
79
|
+
lowercased name and displayed using the alphabetically-first original spelling,
|
|
80
|
+
so one concept yields one entry whose label still reads the way a human typed it.
|
|
81
|
+
|
|
82
|
+
One aggregate query, not one per project.
|
|
83
|
+
"""
|
|
84
|
+
rows = (
|
|
85
|
+
Label.objects.filter(project_id__in=list(project_ids), is_deleted=False)
|
|
86
|
+
.annotate(name_key=Lower("name"))
|
|
87
|
+
.values("name_key")
|
|
88
|
+
.annotate(project_count=Count("project", distinct=True), display_name=Min("name"))
|
|
89
|
+
.order_by("name_key")
|
|
90
|
+
)
|
|
91
|
+
return [
|
|
92
|
+
LabelCatalogEntry(name=row["display_name"], project_count=row["project_count"])
|
|
93
|
+
for row in rows
|
|
94
|
+
]
|