trueppm-api 0.4.0__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 +27 -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 +346 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +483 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +184 -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_squashed_0019_release_0_4.py +302 -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 +143 -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/minter_role.py +38 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +3263 -0
- trueppm_api/apps/access/serializers.py +1007 -0
- trueppm_api/apps/access/services.py +878 -0
- trueppm_api/apps/access/signals.py +171 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +96 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1965 -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/0001_squashed_0003_release_0_4.py +344 -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 +109 -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/0001_squashed_0003_release_0_4.py +90 -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 +459 -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 +178 -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 +863 -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 +597 -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_squashed_0014_release_0_4.py +225 -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/0014_tasklink_created_by.py +27 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +635 -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 +528 -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 +734 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1158 -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 +280 -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 +551 -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 +1276 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +439 -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 +462 -0
- trueppm_api/apps/notifications/email_backend.py +350 -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 +299 -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_squashed_0011_release_0_4.py +204 -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 +1148 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +1094 -0
- trueppm_api/apps/notifications/services.py +1489 -0
- trueppm_api/apps/notifications/tasks.py +762 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +919 -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 +566 -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 +993 -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 +537 -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_squashed_0009_release_0_4.py +128 -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 +541 -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 +458 -0
- trueppm_api/apps/projects/backfill.py +459 -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 +212 -0
- trueppm_api/apps/projects/config_notice.py +1330 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +132 -0
- trueppm_api/apps/projects/decisions_views.py +97 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +423 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4278 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2001 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1827 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1909 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1410 -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/key_views.py +468 -0
- trueppm_api/apps/projects/keys.py +205 -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 +514 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +406 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +192 -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/migration_helpers.py +75 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5659 -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 +59 -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 +75 -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 +76 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0106_squashed_0158_release_0_4.py +2357 -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/0154_object_keys.py +184 -0
- trueppm_api/apps/projects/migrations/0155_task_milestone_at_day_end.py +20 -0
- trueppm_api/apps/projects/migrations/0156_program_import_job_replaced_project_ids.py +17 -0
- trueppm_api/apps/projects/migrations/0157_forecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/0158_baselinetask_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +9209 -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 +903 -0
- trueppm_api/apps/projects/program_schedule.py +767 -0
- trueppm_api/apps/projects/program_views.py +3445 -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 +492 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +891 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/demo_landing_overlay.py +532 -0
- trueppm_api/apps/projects/seed/exporter.py +1717 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2764 -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 +128 -0
- trueppm_api/apps/projects/seed/replay.py +1695 -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 +1169 -0
- trueppm_api/apps/projects/serializers.py +12904 -0
- trueppm_api/apps/projects/services.py +7525 -0
- trueppm_api/apps/projects/share_serializers.py +142 -0
- trueppm_api/apps/projects/share_services.py +373 -0
- trueppm_api/apps/projects/share_views.py +395 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +910 -0
- trueppm_api/apps/projects/signal_privacy_views.py +485 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +288 -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 +1244 -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 +1979 -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 +261 -0
- trueppm_api/apps/projects/template_views.py +829 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1016 -0
- trueppm_api/apps/projects/utilization.py +909 -0
- trueppm_api/apps/projects/views.py +21225 -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/0014_squashed_0015_release_0_4.py +47 -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 +1752 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +144 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/finish_reading.py +198 -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/mc_admission.py +181 -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/0010_squashed_0016_release_0_4.py +106 -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/0015_projectforecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/scheduling/migrations/0016_montecarlorun_finish_readings.py +32 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +542 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +334 -0
- trueppm_api/apps/scheduling/serializers.py +723 -0
- trueppm_api/apps/scheduling/services.py +1251 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +2248 -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 +2908 -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 +446 -0
- trueppm_api/apps/sso/services.py +1473 -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 +369 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +548 -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/0003_squashed_0005_release_0_4.py +77 -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/0006_boardevent_per_project_seq.py +77 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +272 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +488 -0
- trueppm_api/apps/sync/serializers.py +688 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +556 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +957 -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 +159 -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/0004_backfill_default_teams_on_upgrade.py +49 -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 +440 -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/0001_squashed_0004_release_0_4.py +167 -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 +211 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +597 -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 +279 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +249 -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 +100 -0
- trueppm_api/apps/webhooks/migrations/0010_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/0011_webhookeditor.py +48 -0
- trueppm_api/apps/webhooks/migrations/0012_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +437 -0
- trueppm_api/apps/webhooks/serializers.py +326 -0
- trueppm_api/apps/webhooks/tasks.py +680 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +533 -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 +371 -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 +254 -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_squashed_0027_release_0_4.py +199 -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 +967 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +616 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1581 -0
- trueppm_api/asgi.py +49 -0
- trueppm_api/celery.py +40 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +934 -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/demo_read_only.py +314 -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 +1221 -0
- trueppm_api/core/password_reset.py +513 -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 +1034 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +514 -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/core/worker_selfheal.py +205 -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 +2605 -0
- trueppm_api/settings/dev.py +296 -0
- trueppm_api/settings/prod.py +265 -0
- trueppm_api/urls.py +217 -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.0.dist-info/METADATA +273 -0
- trueppm_api-0.4.0.dist-info/RECORD +799 -0
- trueppm_api-0.4.0.dist-info/WHEEL +4 -0
- trueppm_api-0.4.0.dist-info/licenses/LICENSE +201 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICE +7 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICES +104 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/GPL-3.0.txt +674 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/LGPL-3.0.txt +165 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/MPL-2.0.txt +373 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Service layer for the program backlog pull-down (ADR-0069 Erratum, #737).
|
|
2
|
+
|
|
3
|
+
The pull action is the one place where a program-level ``BacklogItem`` crosses
|
|
4
|
+
into a project: it converts a PROPOSED item into a project-backlog ``Task``
|
|
5
|
+
(``status=BACKLOG``, ``sprint=NULL``) and records the conversion on the item.
|
|
6
|
+
Sprint sovereignty is preserved by design — pull never assigns a sprint
|
|
7
|
+
(Morgan's VoC blocker, resolved in ADR-0069).
|
|
8
|
+
|
|
9
|
+
Kept as a service (not inline in the view) so the atomic transition is testable
|
|
10
|
+
in isolation and reusable by a future batch-pull from Sprint Planning.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import uuid
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from django.db import IntegrityError, transaction
|
|
19
|
+
from django.db.models import Max
|
|
20
|
+
from django.utils import timezone
|
|
21
|
+
|
|
22
|
+
from trueppm_api.apps.projects.models import (
|
|
23
|
+
BacklogItem,
|
|
24
|
+
BacklogItemStatus,
|
|
25
|
+
BacklogItemType,
|
|
26
|
+
Label,
|
|
27
|
+
LabelColor,
|
|
28
|
+
Project,
|
|
29
|
+
Task,
|
|
30
|
+
TaskLabel,
|
|
31
|
+
TaskStatus,
|
|
32
|
+
TaskType,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Map the program intake taxonomy (ADR-0069) onto the project work-item taxonomy
|
|
36
|
+
# (ADR-0105). The two enums are reconciled (#1995): every intake type maps to a
|
|
37
|
+
# distinct Task type so a pulled bug/spike/chore keeps its kind instead of
|
|
38
|
+
# silently collapsing to a plain task. ``chore`` is the intake name for
|
|
39
|
+
# ``TaskType.TECH_DEBT``; ``feature`` has no Task analogue and maps to ``TASK``.
|
|
40
|
+
_ITEM_TYPE_TO_TASK_TYPE: dict[str, TaskType] = {
|
|
41
|
+
BacklogItemType.EPIC: TaskType.EPIC,
|
|
42
|
+
BacklogItemType.STORY: TaskType.STORY,
|
|
43
|
+
BacklogItemType.TASK: TaskType.TASK,
|
|
44
|
+
BacklogItemType.BUG: TaskType.BUG,
|
|
45
|
+
BacklogItemType.SPIKE: TaskType.SPIKE,
|
|
46
|
+
BacklogItemType.CHORE: TaskType.TECH_DEBT,
|
|
47
|
+
BacklogItemType.FEATURE: TaskType.TASK,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BacklogItemNotPullable(Exception):
|
|
52
|
+
"""Raised when a pull is attempted on an item that is not PROPOSED.
|
|
53
|
+
|
|
54
|
+
The view maps this to ``409 Conflict`` — the item was already pulled or
|
|
55
|
+
archived (or a concurrent pull won the row lock first).
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CrossProgramPullError(Exception):
|
|
60
|
+
"""Raised when the pull target project does not belong to the item's program.
|
|
61
|
+
|
|
62
|
+
The view maps this to ``400 Bad Request``. Single-program scoping is the OSS
|
|
63
|
+
boundary (ADR-0069): a program backlog item can only be pulled into one of
|
|
64
|
+
its own program's projects, never into another program's project.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def pull_to_project_backlog(
|
|
69
|
+
item_id: str,
|
|
70
|
+
project: Project,
|
|
71
|
+
actor: Any,
|
|
72
|
+
) -> Task:
|
|
73
|
+
"""Atomically convert a PROPOSED ``BacklogItem`` into a project-backlog Task.
|
|
74
|
+
|
|
75
|
+
Locks the item ``FOR UPDATE`` and asserts it is still PROPOSED before doing
|
|
76
|
+
any work, so two concurrent pulls cannot both succeed — the loser raises
|
|
77
|
+
:class:`BacklogItemNotPullable` (→ 409). The new Task lands in the project
|
|
78
|
+
backlog (``status=BACKLOG``, ``sprint=None``); the item is flipped to PULLED
|
|
79
|
+
with ``pulled_task``/``pulled_at``/``pulled_by`` recorded. Both rows bump
|
|
80
|
+
``server_version`` via their ``save()``.
|
|
81
|
+
|
|
82
|
+
On commit it enqueues a CPM recalculation (existing scheduling outbox — no
|
|
83
|
+
new outbox category), broadcasts a ``task_created`` board event, and fires the
|
|
84
|
+
``task.created`` outbound webhook (#752) — matching every other Task-create
|
|
85
|
+
path so external subscribers see backlog pulls like any other create. All
|
|
86
|
+
three are deferred with ``transaction.on_commit`` so a rolled-back pull never
|
|
87
|
+
leaks a recalc request, a phantom WS event, or a webhook delivery.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
item_id: PK of the ``BacklogItem`` to pull.
|
|
91
|
+
project: The resolved target ``Project`` (must belong to the item's program).
|
|
92
|
+
actor: The user performing the pull (recorded as ``pulled_by``).
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
The created project-backlog ``Task``.
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
BacklogItem.DoesNotExist: if no such item exists.
|
|
99
|
+
CrossProgramPullError: if ``project`` is not in the item's program.
|
|
100
|
+
BacklogItemNotPullable: if the item is not in PROPOSED status.
|
|
101
|
+
"""
|
|
102
|
+
# Call-time import (not module-level) to avoid a views→services→views cycle.
|
|
103
|
+
# By the time this service runs, the app is fully loaded, so reusing the
|
|
104
|
+
# canonical task payload builder keeps the pull's task.created shape identical
|
|
105
|
+
# to the REST create path with zero drift risk.
|
|
106
|
+
from trueppm_api.apps.access.permissions import assert_project_not_archived
|
|
107
|
+
from trueppm_api.apps.projects.views import _task_webhook_payload
|
|
108
|
+
from trueppm_api.apps.scheduling.services import enqueue_recalculate
|
|
109
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
110
|
+
from trueppm_api.apps.webhooks.dispatch import dispatch_webhooks
|
|
111
|
+
|
|
112
|
+
# A pull creates a Task, and archived plans take no writes (#3354). The view
|
|
113
|
+
# checks this too, but archived is lifecycle state — a property of the target
|
|
114
|
+
# plan, not of the caller — so it holds for every caller rather than only for
|
|
115
|
+
# one that arrives through DRF. Every caller is a view today; the point is that
|
|
116
|
+
# nothing makes that stay true. Placed before the transaction so nothing is
|
|
117
|
+
# opened on a write that must not happen.
|
|
118
|
+
assert_project_not_archived(project.pk)
|
|
119
|
+
|
|
120
|
+
new_label_ids: list[str] = []
|
|
121
|
+
with transaction.atomic():
|
|
122
|
+
item = BacklogItem.objects.select_for_update().get(pk=item_id, is_deleted=False)
|
|
123
|
+
|
|
124
|
+
if project.program_id != item.program_id:
|
|
125
|
+
raise CrossProgramPullError("The target project is not part of this program.")
|
|
126
|
+
|
|
127
|
+
if item.status != BacklogItemStatus.PROPOSED:
|
|
128
|
+
raise BacklogItemNotPullable(
|
|
129
|
+
f"This item is '{item.status}' and can no longer be pulled."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
task = Task.objects.create(
|
|
133
|
+
project=project,
|
|
134
|
+
name=item.title,
|
|
135
|
+
# BacklogItem.description maps to Task.notes — Task has no separate
|
|
136
|
+
# description field; the Board/drawer render notes as the body.
|
|
137
|
+
notes=item.description,
|
|
138
|
+
story_points=item.story_points,
|
|
139
|
+
# The PO's intake ordering is the whole point of ranking a program
|
|
140
|
+
# backlog, and it is not reconstructable after the crossing — there is
|
|
141
|
+
# no un-pull endpoint, so a lost rank cannot be recovered by undoing.
|
|
142
|
+
# Both fields are nullable PositiveIntegerFields meaning "lower = higher
|
|
143
|
+
# priority", so the raw value carries: it preserves relative order among
|
|
144
|
+
# items pulled from the same pool. Ranks are not unique and ties already
|
|
145
|
+
# break on a secondary sort, so colliding with an existing project task's
|
|
146
|
+
# rank is harmless — whereas leaving it null sorts the pulled item last
|
|
147
|
+
# (the 9999 client sentinel), which is the defect (#2906).
|
|
148
|
+
priority_rank=item.priority_rank,
|
|
149
|
+
status=TaskStatus.BACKLOG,
|
|
150
|
+
sprint=None,
|
|
151
|
+
# ADR-0105: carry the intake item's type into the project work-item
|
|
152
|
+
# taxonomy so a pulled story/bug/spike keeps its kind. The taxonomies
|
|
153
|
+
# are reconciled (#1995), so ``.get`` always hits; the TASK fallback is
|
|
154
|
+
# defensive only (an unknown value can no longer be created).
|
|
155
|
+
type=_ITEM_TYPE_TO_TASK_TYPE.get(item.item_type, TaskType.TASK),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Carry the intake item's free-text tags onto the task as project labels
|
|
159
|
+
# (#1992) so a PO's thematic grouping survives the crossing. Runs inside
|
|
160
|
+
# the atomic block so a rolled-back pull leaves no orphan labels.
|
|
161
|
+
new_label_ids = _apply_tags_as_labels(task, project, item.tags, actor)
|
|
162
|
+
|
|
163
|
+
item.status = BacklogItemStatus.PULLED
|
|
164
|
+
item.pulled_task = task
|
|
165
|
+
item.pulled_at = timezone.now()
|
|
166
|
+
item.pulled_by = actor if getattr(actor, "is_authenticated", False) else None
|
|
167
|
+
# server_version is bumped automatically by VersionedModel.save() (it is
|
|
168
|
+
# stripped from update_fields and incremented via F()), so it is omitted here.
|
|
169
|
+
item.save(update_fields=["status", "pulled_task", "pulled_at", "pulled_by"])
|
|
170
|
+
|
|
171
|
+
project_id = str(project.pk)
|
|
172
|
+
task_id = str(task.pk)
|
|
173
|
+
# source="backlog_pull" tells external consumers which surface originated the
|
|
174
|
+
# create (ADR-0065 X-Source semantics) so a pull is distinguishable from a
|
|
175
|
+
# board/schedule/sync create on the same task.created event.
|
|
176
|
+
webhook_payload = _task_webhook_payload(task, source="backlog_pull")
|
|
177
|
+
# CPM recalc via the existing scheduling outbox (ADR-0027). A BACKLOG task is
|
|
178
|
+
# excluded from the CPM graph, so this coalesces to a no-op run today, but we
|
|
179
|
+
# keep it on the standard Task-create path for consistency and forward-safety.
|
|
180
|
+
transaction.on_commit(lambda: enqueue_recalculate(project_id))
|
|
181
|
+
transaction.on_commit(
|
|
182
|
+
lambda: broadcast_board_event(project_id, "task_created", {"id": task_id})
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Any labels coined from the item's tags are new project vocabulary — tell open
|
|
186
|
+
# boards/label managers so they can render the pulled task's pills (#1992). Bind
|
|
187
|
+
# the id through a function arg so closure late-binding can't collapse the loop's
|
|
188
|
+
# events to the last id.
|
|
189
|
+
def _broadcast_label(lid: str) -> None:
|
|
190
|
+
transaction.on_commit(
|
|
191
|
+
lambda: broadcast_board_event(project_id, "label_created", {"id": lid})
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
for _label_id in new_label_ids:
|
|
195
|
+
_broadcast_label(_label_id)
|
|
196
|
+
|
|
197
|
+
# Bind the payload via a default arg so closure late-binding can never swap it
|
|
198
|
+
# if this function grows more branches later (matches inbound_sync.py).
|
|
199
|
+
def _dispatch(pid: str = project_id, wp: dict[str, Any] = webhook_payload) -> None:
|
|
200
|
+
dispatch_webhooks(pid, "task.created", wp)
|
|
201
|
+
|
|
202
|
+
transaction.on_commit(_dispatch)
|
|
203
|
+
return task
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _apply_tags_as_labels(
|
|
207
|
+
task: Task,
|
|
208
|
+
project: Project,
|
|
209
|
+
tags: list[str],
|
|
210
|
+
actor: Any,
|
|
211
|
+
) -> list[str]:
|
|
212
|
+
"""Carry a pulled item's free-text tags onto the task as project labels (#1992).
|
|
213
|
+
|
|
214
|
+
Each tag becomes — or, case-insensitively, reuses — a project-scoped ``Label``
|
|
215
|
+
and is attached to ``task`` via ``TaskLabel``. Reuse keeps the catalog from
|
|
216
|
+
sprouting a near-duplicate on every pull; new labels take the neutral default
|
|
217
|
+
slot (``SLATE``) appended to the end of the palette order, which a project admin
|
|
218
|
+
can recolor later. Idempotent per tag via ``TaskLabel.get_or_create``.
|
|
219
|
+
|
|
220
|
+
Must be called inside the pull's ``transaction.atomic`` block so a rolled-back
|
|
221
|
+
pull leaves no orphan labels. The label soft cap is honored: once the project is
|
|
222
|
+
at the ceiling, further *new* tags are skipped (existing matches still attach) so
|
|
223
|
+
a pull can never blow past the curation floor the ``LabelViewSet`` enforces.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
task: The freshly created project-backlog Task to label.
|
|
227
|
+
project: The task's project (labels are project-scoped).
|
|
228
|
+
tags: The item's tags — already normalized (trimmed, de-duped) by the
|
|
229
|
+
serializer's ``validate_tags``.
|
|
230
|
+
actor: The pulling user, recorded as ``created_by`` on any new label.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
The ids of labels *created* here (not reused), so the caller can broadcast
|
|
234
|
+
``label_created`` once the transaction commits.
|
|
235
|
+
"""
|
|
236
|
+
if not tags:
|
|
237
|
+
return []
|
|
238
|
+
# Call-time import mirrors the pull's views import — avoids a services→views cycle.
|
|
239
|
+
from trueppm_api.apps.projects.views import _label_soft_cap
|
|
240
|
+
|
|
241
|
+
cap = _label_soft_cap()
|
|
242
|
+
live = Label.objects.filter(project=project, is_deleted=False)
|
|
243
|
+
label_count = live.count()
|
|
244
|
+
next_position = (live.aggregate(m=Max("position"))["m"] or 0) + 1
|
|
245
|
+
creator = actor if getattr(actor, "is_authenticated", False) else None
|
|
246
|
+
|
|
247
|
+
created_ids: list[str] = []
|
|
248
|
+
for tag in tags:
|
|
249
|
+
# Label.name is capped at 50 chars; BacklogItem tags are unbounded, so clamp
|
|
250
|
+
# to keep an over-long tag from raising instead of degrading gracefully.
|
|
251
|
+
name = tag[:50]
|
|
252
|
+
existing = Label.objects.filter(
|
|
253
|
+
project=project, name__iexact=name, is_deleted=False
|
|
254
|
+
).first()
|
|
255
|
+
if existing is not None:
|
|
256
|
+
TaskLabel.objects.get_or_create(task=task, label=existing)
|
|
257
|
+
continue
|
|
258
|
+
if label_count >= cap:
|
|
259
|
+
# At the curation ceiling — skip coining a new label but keep going;
|
|
260
|
+
# a pull must not fail because the project's label catalog is full.
|
|
261
|
+
continue
|
|
262
|
+
label, created = _coin_label(project, name, next_position, creator)
|
|
263
|
+
if label is None:
|
|
264
|
+
continue
|
|
265
|
+
if created:
|
|
266
|
+
next_position += 1
|
|
267
|
+
label_count += 1
|
|
268
|
+
created_ids.append(str(label.pk))
|
|
269
|
+
TaskLabel.objects.get_or_create(task=task, label=label)
|
|
270
|
+
return created_ids
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _coin_label(
|
|
274
|
+
project: Project,
|
|
275
|
+
name: str,
|
|
276
|
+
position: int,
|
|
277
|
+
creator: Any,
|
|
278
|
+
) -> tuple[Any, bool]:
|
|
279
|
+
"""Create a new project label at ``position``, or reuse the race winner.
|
|
280
|
+
|
|
281
|
+
Returns ``(label, created)`` where ``created`` is True only when this call
|
|
282
|
+
coined the label (so the caller can bump the position/count and broadcast).
|
|
283
|
+
|
|
284
|
+
Savepoint the create so a concurrent pull that coined the same tag first raises
|
|
285
|
+
only a local IntegrityError (on uniq_label_name_per_project) instead of poisoning
|
|
286
|
+
the whole pull transaction. Without the nested atomic the outer pull would roll
|
|
287
|
+
back to a transient 500. On a lost race the other transaction's label is now
|
|
288
|
+
committed and visible; reuse it (exact-name match, since the unique constraint is
|
|
289
|
+
on exact (project, name)).
|
|
290
|
+
"""
|
|
291
|
+
try:
|
|
292
|
+
with transaction.atomic():
|
|
293
|
+
label = Label.objects.create(
|
|
294
|
+
project=project,
|
|
295
|
+
name=name,
|
|
296
|
+
color=LabelColor.SLATE,
|
|
297
|
+
position=position,
|
|
298
|
+
created_by=creator,
|
|
299
|
+
)
|
|
300
|
+
except IntegrityError:
|
|
301
|
+
return Label.objects.filter(project=project, name=name, is_deleted=False).first(), False
|
|
302
|
+
return label, True
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def reset_pulled_item_on_task_delete(task_id: str) -> None:
|
|
306
|
+
"""Reset a BacklogItem to PROPOSED when its pulled Task is (soft-)deleted.
|
|
307
|
+
|
|
308
|
+
Rollback path (ADR-0069): if the Task created by a pull is deleted, the
|
|
309
|
+
originating item must become re-pullable. ``pulled_task`` is ``SET_NULL`` so
|
|
310
|
+
deletion never cascades to the item; this restores the lifecycle by clearing
|
|
311
|
+
the pull bookkeeping and flipping the item back to PROPOSED.
|
|
312
|
+
|
|
313
|
+
Idempotent and a no-op when no item points at the task. Called from the Task
|
|
314
|
+
post_save soft-delete receiver (TruePPM Tasks soft-delete, not hard-delete).
|
|
315
|
+
"""
|
|
316
|
+
item = BacklogItem.objects.filter(pulled_task_id=uuid.UUID(task_id), is_deleted=False).first()
|
|
317
|
+
if item is None or item.status != BacklogItemStatus.PULLED:
|
|
318
|
+
return
|
|
319
|
+
item.status = BacklogItemStatus.PROPOSED
|
|
320
|
+
item.pulled_task = None
|
|
321
|
+
item.pulled_at = None
|
|
322
|
+
item.pulled_by = None
|
|
323
|
+
# server_version auto-bumps in VersionedModel.save() — omitted from update_fields.
|
|
324
|
+
item.save(update_fields=["status", "pulled_task", "pulled_at", "pulled_by"])
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Views for the program backlog (ADR-0069 Erratum, #737 / #739).
|
|
2
|
+
|
|
3
|
+
``BacklogItem`` is a program-scoped CRUD resource nested under a program, plus a
|
|
4
|
+
``pull`` action that converts a PROPOSED item into a project-backlog Task. The
|
|
5
|
+
project-backlog *read* (Task with ``status=BACKLOG``, ``sprint=NULL``) is served
|
|
6
|
+
by the existing ``TaskViewSet`` filters (``?status=BACKLOG&sprint=none``) — no
|
|
7
|
+
separate endpoint is added here.
|
|
8
|
+
|
|
9
|
+
Permission matrix (ADR-0069 Erratum §4):
|
|
10
|
+
list / retrieve — IsProgramMember (Viewer+)
|
|
11
|
+
create / update / destroy — IsProgramEditor (Team Member+)
|
|
12
|
+
pull — IsProgramEditor **and** write role on the target project
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from django.contrib.postgres.search import TrigramSimilarity
|
|
18
|
+
from django.core.exceptions import ValidationError as DjangoValidationError
|
|
19
|
+
from django.db.models import QuerySet
|
|
20
|
+
from django.shortcuts import get_object_or_404
|
|
21
|
+
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
|
22
|
+
from rest_framework import mixins, serializers, status, viewsets
|
|
23
|
+
from rest_framework.decorators import action
|
|
24
|
+
from rest_framework.permissions import BasePermission, IsAuthenticated
|
|
25
|
+
from rest_framework.request import Request
|
|
26
|
+
from rest_framework.response import Response
|
|
27
|
+
from rest_framework.serializers import BaseSerializer
|
|
28
|
+
|
|
29
|
+
from trueppm_api.apps.access.models import Role
|
|
30
|
+
from trueppm_api.apps.access.permissions import (
|
|
31
|
+
IsProgramEditor,
|
|
32
|
+
IsProgramMember,
|
|
33
|
+
IsProgramNotClosed,
|
|
34
|
+
McpReadableViewMixin,
|
|
35
|
+
McpScope,
|
|
36
|
+
_membership_role,
|
|
37
|
+
assert_project_not_archived,
|
|
38
|
+
)
|
|
39
|
+
from trueppm_api.apps.idempotency.mixins import IdempotencyMixin
|
|
40
|
+
from trueppm_api.apps.projects.backlog_services import (
|
|
41
|
+
BacklogItemNotPullable,
|
|
42
|
+
CrossProgramPullError,
|
|
43
|
+
pull_to_project_backlog,
|
|
44
|
+
)
|
|
45
|
+
from trueppm_api.apps.projects.models import (
|
|
46
|
+
BacklogItem,
|
|
47
|
+
BacklogItemStatus,
|
|
48
|
+
Program,
|
|
49
|
+
Project,
|
|
50
|
+
)
|
|
51
|
+
from trueppm_api.apps.projects.serializers import BacklogItemSerializer, TaskSerializer
|
|
52
|
+
from trueppm_api.core.request_body import object_body
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class BacklogItemViewSet(
|
|
56
|
+
McpReadableViewMixin,
|
|
57
|
+
IdempotencyMixin,
|
|
58
|
+
mixins.ListModelMixin,
|
|
59
|
+
mixins.CreateModelMixin,
|
|
60
|
+
mixins.RetrieveModelMixin,
|
|
61
|
+
mixins.UpdateModelMixin,
|
|
62
|
+
mixins.DestroyModelMixin,
|
|
63
|
+
viewsets.GenericViewSet[BacklogItem],
|
|
64
|
+
):
|
|
65
|
+
"""CRUD + pull for program backlog items.
|
|
66
|
+
|
|
67
|
+
URL: ``/api/v1/programs/<program_pk>/backlog-items/[<pk>/]``
|
|
68
|
+
``/api/v1/programs/<program_pk>/backlog-items/<pk>/pull/``
|
|
69
|
+
|
|
70
|
+
``McpReadableViewMixin`` (ADR-0186 §E) additively exposes the list/retrieve
|
|
71
|
+
reads to a personal ``mcp:read`` API token — it powers the MCP
|
|
72
|
+
``list_program_backlog`` tool. Because this viewset overrides
|
|
73
|
+
``get_permissions`` with a per-action list, the MCP token guards are appended
|
|
74
|
+
from that wrapper (via ``mcp_token_guards()``) rather than the mixin's default
|
|
75
|
+
``get_permissions``, so no branch — including the write branches — can leak a
|
|
76
|
+
token past ``TokenReadOnlyMethods`` / ``TokenHasScope`` / ``TokenIsOwnerScoped``.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
# ADR-0678 (#2482): program-level intake pool. BacklogItem has no project FK,
|
|
80
|
+
# so the central filter reaches it through the `program` branch — a child
|
|
81
|
+
# project's opt-out does not withhold intake data the project does not own.
|
|
82
|
+
mcp_scope = McpScope.AGGREGATE
|
|
83
|
+
|
|
84
|
+
serializer_class = BacklogItemSerializer
|
|
85
|
+
|
|
86
|
+
def get_permissions(self) -> list[BasePermission]:
|
|
87
|
+
# ADR-0186 §E: append the read-only MCP token guards around the
|
|
88
|
+
# action-specific RBAC list so a mcp:read token is confined to safe
|
|
89
|
+
# methods on every action (no write-branch leak); human auth passes both.
|
|
90
|
+
return [*self._rbac_permissions(), *self.mcp_token_guards()]
|
|
91
|
+
|
|
92
|
+
def _rbac_permissions(self) -> list[BasePermission]:
|
|
93
|
+
if self.action in ("list", "retrieve"):
|
|
94
|
+
return [IsAuthenticated(), IsProgramMember(), IsProgramNotClosed()]
|
|
95
|
+
# create / update / partial_update / destroy / pull all require program
|
|
96
|
+
# write (Team Member+). The pull action additionally checks project-write
|
|
97
|
+
# on the resolved target inside the handler.
|
|
98
|
+
return [IsAuthenticated(), IsProgramEditor(), IsProgramNotClosed()]
|
|
99
|
+
|
|
100
|
+
def get_queryset(self) -> QuerySet[BacklogItem]:
|
|
101
|
+
"""Program-scoped backlog.
|
|
102
|
+
|
|
103
|
+
The list view layers on item_type / status / tags / ``?q=`` filters and
|
|
104
|
+
defaults ``status`` to PROPOSED (the active pool). Those filters are
|
|
105
|
+
**list-only** — detail operations (retrieve / update / destroy) must be
|
|
106
|
+
able to reach an item in *any* status (e.g. PATCH an item to ARCHIVED,
|
|
107
|
+
or fetch a PULLED item), so they get the unfiltered program-scoped set.
|
|
108
|
+
"""
|
|
109
|
+
program_pk = str(self.kwargs["program_pk"])
|
|
110
|
+
qs = BacklogItem.objects.filter(program_id=program_pk, is_deleted=False).select_related(
|
|
111
|
+
# pulled_task__project powers the serializer's pulled_task_project_*
|
|
112
|
+
# wayfinding fields (#1994) without an extra query per pulled row.
|
|
113
|
+
"pulled_task__project",
|
|
114
|
+
"created_by",
|
|
115
|
+
"pulled_by",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if self.action != "list":
|
|
119
|
+
return qs
|
|
120
|
+
|
|
121
|
+
item_type = self.request.query_params.get("item_type")
|
|
122
|
+
if item_type:
|
|
123
|
+
qs = qs.filter(item_type=item_type)
|
|
124
|
+
|
|
125
|
+
# status: explicit value filters; a present-but-empty value means "all";
|
|
126
|
+
# absent means the default active pool (PROPOSED).
|
|
127
|
+
if "status" in self.request.query_params:
|
|
128
|
+
status_filter = self.request.query_params.get("status")
|
|
129
|
+
if status_filter:
|
|
130
|
+
qs = qs.filter(status=status_filter)
|
|
131
|
+
else:
|
|
132
|
+
qs = qs.filter(status=BacklogItemStatus.PROPOSED)
|
|
133
|
+
|
|
134
|
+
# tags: repeatable ?tags=foo&tags=bar — match items carrying ALL given tags.
|
|
135
|
+
tags = self.request.query_params.getlist("tags")
|
|
136
|
+
for tag in tags:
|
|
137
|
+
if tag:
|
|
138
|
+
qs = qs.filter(tags__contains=[tag])
|
|
139
|
+
|
|
140
|
+
# Trigram fuzzy search on title (#739). Combines with the filters above;
|
|
141
|
+
# an empty/absent q is a no-op (other filters + default ordering apply).
|
|
142
|
+
#
|
|
143
|
+
# The FILTER uses the ``%`` operator (``__trigram_similar``) — this is the
|
|
144
|
+
# form the ``gin_trgm_ops`` GIN index (backlogitem_title_trgm) accelerates,
|
|
145
|
+
# so search scales as the ADR intends rather than seq-scanning a computed
|
|
146
|
+
# similarity per row. The ``%`` operator honours pg_trgm's
|
|
147
|
+
# ``similarity_threshold`` (default 0.3). We additionally annotate the
|
|
148
|
+
# whole-string similarity and order by it desc so the closest title ranks
|
|
149
|
+
# first, overriding the model's default priority_rank ordering for searches.
|
|
150
|
+
q = (self.request.query_params.get("q") or "").strip()
|
|
151
|
+
if q:
|
|
152
|
+
qs = (
|
|
153
|
+
qs.annotate(similarity=TrigramSimilarity("title", q))
|
|
154
|
+
.filter(title__trigram_similar=q)
|
|
155
|
+
.order_by("-similarity")
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return qs
|
|
159
|
+
|
|
160
|
+
def _resolve_program(self) -> Program:
|
|
161
|
+
program = get_object_or_404(Program, pk=self.kwargs["program_pk"], is_deleted=False)
|
|
162
|
+
# DRF skips has_object_permission on create/custom actions — enforce the
|
|
163
|
+
# program role gate against the resolved program before any write.
|
|
164
|
+
self.check_object_permissions(self.request, program)
|
|
165
|
+
return program
|
|
166
|
+
|
|
167
|
+
def perform_create(self, serializer: BaseSerializer[BacklogItem]) -> None:
|
|
168
|
+
program = self._resolve_program()
|
|
169
|
+
# status is forced to PROPOSED on create regardless of payload — a new
|
|
170
|
+
# item always enters the active pool; PULLED/ARCHIVED are reached later
|
|
171
|
+
# via the pull action / archive PATCH.
|
|
172
|
+
serializer.save(
|
|
173
|
+
program=program,
|
|
174
|
+
created_by=self.request.user,
|
|
175
|
+
status=BacklogItemStatus.PROPOSED,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def perform_destroy(self, instance: BacklogItem) -> None:
|
|
179
|
+
# Soft-delete so the sync protocol ships a tombstone rather than the row
|
|
180
|
+
# vanishing silently (VersionedModel contract).
|
|
181
|
+
instance.soft_delete()
|
|
182
|
+
|
|
183
|
+
# Surfaced by the #2840 conformance guard, which scans every module under
|
|
184
|
+
# ``apps/`` rather than ``views.py`` alone. Every part of the inferred contract
|
|
185
|
+
# was wrong: the fallback declared a full BacklogItemRequest body (the real
|
|
186
|
+
# body is ``{"project_id": …}``) and a bare ``200 BacklogItem`` response (the
|
|
187
|
+
# handler returns ``201`` with a two-key envelope carrying the created Task
|
|
188
|
+
# *and* the transitioned item).
|
|
189
|
+
@extend_schema(
|
|
190
|
+
summary="Pull a backlog item into a project's backlog",
|
|
191
|
+
request=inline_serializer(
|
|
192
|
+
name="BacklogItemPullRequest",
|
|
193
|
+
fields={"project_id": serializers.UUIDField()},
|
|
194
|
+
),
|
|
195
|
+
responses={
|
|
196
|
+
201: inline_serializer(
|
|
197
|
+
name="BacklogItemPullResult",
|
|
198
|
+
fields={
|
|
199
|
+
"task": TaskSerializer(),
|
|
200
|
+
"backlog_item": BacklogItemSerializer(),
|
|
201
|
+
},
|
|
202
|
+
),
|
|
203
|
+
400: OpenApiResponse(
|
|
204
|
+
description="project_id missing, or the target project is not in this program."
|
|
205
|
+
),
|
|
206
|
+
403: OpenApiResponse(
|
|
207
|
+
description=(
|
|
208
|
+
"Caller lacks Team Member+ on the target project, **or** the target "
|
|
209
|
+
"project is archived. The archived case is not a role problem and no "
|
|
210
|
+
"role clears it — unarchive the project and retry (#3354)."
|
|
211
|
+
)
|
|
212
|
+
),
|
|
213
|
+
409: OpenApiResponse(description="The item is no longer PROPOSED."),
|
|
214
|
+
},
|
|
215
|
+
)
|
|
216
|
+
@action(detail=True, methods=["post"], url_path="pull")
|
|
217
|
+
def pull(self, request: Request, program_pk: str, pk: str) -> Response:
|
|
218
|
+
"""Pull a PROPOSED item into a project's backlog (ADR-0069 Erratum §5).
|
|
219
|
+
|
|
220
|
+
Body: ``{"project_id": "<uuid>"}``. Creates a Task (status=BACKLOG,
|
|
221
|
+
sprint=NULL) in the target project and transitions the item to PULLED.
|
|
222
|
+
Requires program-write (gated by ``get_permissions``) **and** write role
|
|
223
|
+
on the target project (checked here). Never assigns a sprint.
|
|
224
|
+
|
|
225
|
+
Returns ``201`` with the created task and the updated backlog item.
|
|
226
|
+
``400`` if project_id is missing / not in this program; ``403`` if the
|
|
227
|
+
caller lacks project-write **or the target project is archived** (the
|
|
228
|
+
latter clears for no role — the project must be unarchived); ``409`` if
|
|
229
|
+
the item is no longer PROPOSED.
|
|
230
|
+
"""
|
|
231
|
+
# Confirm the program exists and the caller holds program-write.
|
|
232
|
+
self._resolve_program()
|
|
233
|
+
|
|
234
|
+
project_id = object_body(request).get("project_id")
|
|
235
|
+
if not project_id:
|
|
236
|
+
return Response(
|
|
237
|
+
{"detail": "project_id is required."},
|
|
238
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Resolve the target project, scoped to this program. A project_id that
|
|
242
|
+
# is not in the program returns 404-as-400 (cross-program pull rejected).
|
|
243
|
+
try:
|
|
244
|
+
project = Project.objects.get(pk=project_id, program_id=program_pk, is_deleted=False)
|
|
245
|
+
except (Project.DoesNotExist, ValueError, DjangoValidationError):
|
|
246
|
+
return Response(
|
|
247
|
+
{"detail": "Target project not found in this program."},
|
|
248
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Project-level write gate: program-write alone is not enough to drop a
|
|
252
|
+
# Task into a project — the caller must also be Team Member+ on it.
|
|
253
|
+
role = _membership_role(request, project.pk)
|
|
254
|
+
if role is None or role < Role.MEMBER:
|
|
255
|
+
return Response(
|
|
256
|
+
{"detail": "You need at least Team Member role on the target project."},
|
|
257
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
258
|
+
)
|
|
259
|
+
# Archived target projects take no writes, and a pull creates a Task (#3354).
|
|
260
|
+
# `IsProgramNotClosed` in `get_permissions` gates the *program*; nothing was
|
|
261
|
+
# checking the project's own lifecycle. `IsProjectNotArchived` cannot cover
|
|
262
|
+
# this route: the target project comes from the request body, so there is no
|
|
263
|
+
# `project_pk` kwarg for `has_permission` and `get_object()` returns the
|
|
264
|
+
# `BacklogItem` — the same blind spot as `ProjectTemplateViewSet.apply`, and
|
|
265
|
+
# it needs the same explicit assert. Placed after the role check so a caller
|
|
266
|
+
# without project-write still gets the role refusal first.
|
|
267
|
+
assert_project_not_archived(project.pk)
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
task = pull_to_project_backlog(item_id=str(pk), project=project, actor=request.user)
|
|
271
|
+
except BacklogItem.DoesNotExist:
|
|
272
|
+
return Response(
|
|
273
|
+
{"detail": "Backlog item not found."},
|
|
274
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
275
|
+
)
|
|
276
|
+
except CrossProgramPullError as exc:
|
|
277
|
+
# False positive: domain exception with a curated, client-safe message
|
|
278
|
+
# (single-program pull boundary, ADR-0069) — no server internals.
|
|
279
|
+
return Response(
|
|
280
|
+
{"detail": str(exc)}, # codeql[py/stack-trace-exposure]
|
|
281
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
282
|
+
)
|
|
283
|
+
except BacklogItemNotPullable as exc:
|
|
284
|
+
# False positive: curated domain message (item no longer PROPOSED).
|
|
285
|
+
return Response(
|
|
286
|
+
{"detail": str(exc)}, # codeql[py/stack-trace-exposure]
|
|
287
|
+
status=status.HTTP_409_CONFLICT,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Re-fetch with the FK joins the serializer reads (pulled_task__project,
|
|
291
|
+
# created_by, pulled_by) so the pull response doesn't fire lazy per-FK
|
|
292
|
+
# queries — including the pulled_task_project_* wayfinding fields (#1994).
|
|
293
|
+
item = BacklogItem.objects.select_related(
|
|
294
|
+
"pulled_task__project", "created_by", "pulled_by"
|
|
295
|
+
).get(pk=pk)
|
|
296
|
+
return Response(
|
|
297
|
+
{
|
|
298
|
+
"task": TaskSerializer(task).data,
|
|
299
|
+
"backlog_item": BacklogItemSerializer(item).data,
|
|
300
|
+
},
|
|
301
|
+
status=status.HTTP_201_CREATED,
|
|
302
|
+
)
|