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,453 @@
|
|
|
1
|
+
"""Sprint cadence generation — the bulk stand-up of an iteration series (#2968).
|
|
2
|
+
|
|
3
|
+
Standing up a year of iterations one ``POST /sprints/`` at a time is data entry,
|
|
4
|
+
not planning. This module computes a whole cadence in one pass and commits it in
|
|
5
|
+
one transaction.
|
|
6
|
+
|
|
7
|
+
Three properties are load-bearing and each is here rather than in the view so
|
|
8
|
+
they are testable without HTTP:
|
|
9
|
+
|
|
10
|
+
**Calendar-aware.** Sprint length is counted in *working days* against the
|
|
11
|
+
project's composed calendar (``compose_project_calendar`` — the same fold CPM,
|
|
12
|
+
Monte Carlo, and the program pass use, so a cadence can never disagree with the
|
|
13
|
+
schedule about what a working day is). A holiday inside a window pushes the
|
|
14
|
+
finish date out rather than silently shrinking the iteration; a fixed 14-calendar-day
|
|
15
|
+
cadence would keep *calling* it a two-week sprint while quietly deleting two days
|
|
16
|
+
of the team's capacity, and that is exactly the kind of number that misleads
|
|
17
|
+
planning.
|
|
18
|
+
|
|
19
|
+
**Idempotent on name.** ``Sprint`` carries no unique constraint on
|
|
20
|
+
``(project, name)`` — duplicate names are legal and some projects have them — so
|
|
21
|
+
idempotency is enforced here: a candidate whose name already exists in the project
|
|
22
|
+
is reported as ``exists`` and never re-created. A double submit therefore creates
|
|
23
|
+
one set, not two. :func:`commit_cadence` re-reads the existing names *inside* the
|
|
24
|
+
transaction while holding a row lock on the project, so two concurrent requests
|
|
25
|
+
serialize rather than racing past each other's absence check.
|
|
26
|
+
|
|
27
|
+
**Capacity is a planning aid, never a cap.** :func:`capacity_hint` returns a
|
|
28
|
+
suggestion derived from the team's own closed sprints, and generation *never*
|
|
29
|
+
writes it to ``Sprint.capacity_points`` on its own. A capacity is only stored when
|
|
30
|
+
the caller explicitly passes one, and then only onto the **first row of the
|
|
31
|
+
cadence** and only when that row is one this call is creating — a throughput
|
|
32
|
+
figure projected onto sprint twelve is fiction, and a re-run must not divert the
|
|
33
|
+
operator's number onto whichever mid-series sprint happened to be missing. Sprint
|
|
34
|
+
commitment
|
|
35
|
+
belongs to the team (ADR-0073 / ADR-0113); a generator that stamps a ceiling onto
|
|
36
|
+
a year of iterations is a cadence tool dictating what a team commits to.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import statistics
|
|
42
|
+
from dataclasses import dataclass
|
|
43
|
+
from datetime import date, timedelta
|
|
44
|
+
from typing import TYPE_CHECKING, Any
|
|
45
|
+
|
|
46
|
+
from django.db import transaction
|
|
47
|
+
|
|
48
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
49
|
+
from trueppm_api.apps.projects.models import Project, Sprint
|
|
50
|
+
|
|
51
|
+
# Bulk-size bound (#2968) — write amplification per call, not agent containment.
|
|
52
|
+
# A read-only MCP token is refused this POST outright by `TokenReadOnlyMethods`,
|
|
53
|
+
# so do NOT read this bound as the thing that holds agents back; what it holds
|
|
54
|
+
# back is one authenticated human or PAT turning a single request into an
|
|
55
|
+
# unbounded write and an unbounded calendar walk. 52 is a year of weekly
|
|
56
|
+
# iterations — past that a caller wants a second call and a look at the preview,
|
|
57
|
+
# not a bigger number. Aggregate rate limiting across calls is a separate,
|
|
58
|
+
# cross-cutting concern (#2749); nothing here provides it.
|
|
59
|
+
MAX_GENERATED_SPRINTS = 52
|
|
60
|
+
# A single iteration longer than six working weeks is not a sprint; the bound
|
|
61
|
+
# exists so `count * length` cannot be used to walk the calendar scan out to the
|
|
62
|
+
# date ceiling.
|
|
63
|
+
MAX_SPRINT_LENGTH_DAYS = 30
|
|
64
|
+
# Two, not one: `Sprint.Meta` carries `CheckConstraint(finish_date__gt=start_date)`
|
|
65
|
+
# — strictly greater — so a single-working-day iteration is illegal at the
|
|
66
|
+
# database level and would surface as an IntegrityError (a 500), not a 400.
|
|
67
|
+
MIN_SPRINT_LENGTH_DAYS = 2
|
|
68
|
+
# Two Mon–Fri weeks. Deliberately *not* `services._typical_sprint_length_days`,
|
|
69
|
+
# which derives the team's cadence from the last sprint's span in **calendar**
|
|
70
|
+
# days; this module counts **working** days, and silently mixing the two units
|
|
71
|
+
# would make a two-week default land as a two-and-a-half-week sprint.
|
|
72
|
+
DEFAULT_SPRINT_LENGTH_DAYS = 10
|
|
73
|
+
MAX_FIRST_INDEX = 999
|
|
74
|
+
# Calendar-day ceiling on a single window, applied to the *edited* rows a client
|
|
75
|
+
# posts back. The generated path is bounded in working days, but an edited row
|
|
76
|
+
# carries the operator's own dates and would otherwise accept any span the scan
|
|
77
|
+
# guard tolerates — 52 five-year "iterations" in one call. 120 calendar days is
|
|
78
|
+
# generous headroom for a 30-working-day sprint stretched by a long shutdown, and
|
|
79
|
+
# refuses anything that is plainly not an iteration.
|
|
80
|
+
MAX_SPRINT_SPAN_DAYS = 120
|
|
81
|
+
NAME_TOKEN = "{n}"
|
|
82
|
+
|
|
83
|
+
# Guard against a degenerate composed calendar. `compose_project_calendar`
|
|
84
|
+
# AND-folds every applied mask, so two overlays with disjoint working days
|
|
85
|
+
# compose to "no working day ever" — a walk that would otherwise spin to the
|
|
86
|
+
# `date` ceiling and surface as an opaque OverflowError mid-request. Mirrors
|
|
87
|
+
# the scheduler engine's own MAX_CALENDAR_SCAN_DAYS guard, scaled to the span a
|
|
88
|
+
# cadence can legitimately need.
|
|
89
|
+
MAX_CALENDAR_SCAN_DAYS = 366 * 5
|
|
90
|
+
|
|
91
|
+
# Rolling window for the capacity suggestion. Matches the window the velocity
|
|
92
|
+
# panel and `scheduler_velocity_inputs` already read, so the number the wizard
|
|
93
|
+
# shows and the number the velocity surfaces show are derived from the same set.
|
|
94
|
+
CAPACITY_HINT_WINDOW = 6
|
|
95
|
+
|
|
96
|
+
# The one string that carries the ADR-0073 sovereignty rule onto the wire. Every
|
|
97
|
+
# client that renders the hint renders this sentence with it, so a UI cannot
|
|
98
|
+
# quietly re-label a suggestion as a limit.
|
|
99
|
+
CAPACITY_HINT_NOTE = (
|
|
100
|
+
"A starting point drawn from this team's own closed iterations — not a limit. "
|
|
101
|
+
"The team decides what it commits to."
|
|
102
|
+
)
|
|
103
|
+
CAPACITY_HINT_NOTE_NO_HISTORY = (
|
|
104
|
+
"No closed iterations to draw from yet, so there is no suggestion to make. "
|
|
105
|
+
"The team decides what it commits to."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class CadenceError(ValueError):
|
|
110
|
+
"""A cadence request that cannot be satisfied (degenerate calendar, bad span).
|
|
111
|
+
|
|
112
|
+
Raised by the pure functions here so they stay usable outside DRF; the view
|
|
113
|
+
translates it into a 400.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True)
|
|
118
|
+
class CadenceRow:
|
|
119
|
+
"""One proposed iteration in a generated cadence.
|
|
120
|
+
|
|
121
|
+
``exists`` is the idempotency verdict: a row whose name already belongs to a
|
|
122
|
+
live sprint in this project is never created, in preview or on commit.
|
|
123
|
+
|
|
124
|
+
``non_working_days_skipped`` counts **every** non-working day the window
|
|
125
|
+
spans — weekends included, not holidays only. That is deliberate: it is the
|
|
126
|
+
calendar evidence behind the finish date, so a window lengthened by a
|
|
127
|
+
shutdown reads as a deliberate skip in the preview rather than an
|
|
128
|
+
unexplained date jump, and a reader can check ``working_days +
|
|
129
|
+
non_working_days_skipped`` against the span.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
name: str
|
|
133
|
+
start_date: date
|
|
134
|
+
finish_date: date
|
|
135
|
+
working_days: int
|
|
136
|
+
non_working_days_skipped: int
|
|
137
|
+
exists: bool
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def render_sprint_name(pattern: str, index: int) -> str:
|
|
141
|
+
"""Substitute ``{n}`` in a cadence name pattern.
|
|
142
|
+
|
|
143
|
+
Deliberately a literal replace rather than :meth:`str.format`: the pattern is
|
|
144
|
+
user input, and ``format`` on user input is an attribute-traversal primitive
|
|
145
|
+
(``{0.__class__}``) plus a KeyError surface for every unrecognized brace.
|
|
146
|
+
Replacing one known token leaves every other brace as the literal the author
|
|
147
|
+
typed.
|
|
148
|
+
"""
|
|
149
|
+
return pattern.replace(NAME_TOKEN, str(index))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _next_working_day(cal: Any, d: date) -> date:
|
|
153
|
+
"""``d`` itself when it is a working day, else the first working day after it."""
|
|
154
|
+
scanned = 0
|
|
155
|
+
while not cal.is_working_day(d):
|
|
156
|
+
if scanned >= MAX_CALENDAR_SCAN_DAYS:
|
|
157
|
+
raise CadenceError(
|
|
158
|
+
"This project's calendar has no working day within "
|
|
159
|
+
f"{MAX_CALENDAR_SCAN_DAYS} days of the requested start. Check the "
|
|
160
|
+
"project's working-day mask and its holiday overlays."
|
|
161
|
+
)
|
|
162
|
+
d += timedelta(days=1)
|
|
163
|
+
scanned += 1
|
|
164
|
+
return d
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _window_from_start(cal: Any, start: date, length_days: int) -> tuple[date, int]:
|
|
168
|
+
"""The window that holds exactly ``length_days`` working days from ``start``.
|
|
169
|
+
|
|
170
|
+
``start`` must already be a working day. Returns the inclusive finish date
|
|
171
|
+
and the count of non-working days that fell inside the window — the number
|
|
172
|
+
the preview shows so a holiday-lengthened sprint reads as deliberate.
|
|
173
|
+
"""
|
|
174
|
+
counted = 1 # `start` is a working day and is day one
|
|
175
|
+
cursor = start
|
|
176
|
+
skipped = 0
|
|
177
|
+
scanned = 0
|
|
178
|
+
while counted < length_days:
|
|
179
|
+
cursor += timedelta(days=1)
|
|
180
|
+
scanned += 1
|
|
181
|
+
if scanned >= MAX_CALENDAR_SCAN_DAYS:
|
|
182
|
+
raise CadenceError(
|
|
183
|
+
f"This project's calendar cannot fit {length_days} working days "
|
|
184
|
+
f"within {MAX_CALENDAR_SCAN_DAYS} days of {start.isoformat()}. "
|
|
185
|
+
"Check the project's working-day mask and its holiday overlays."
|
|
186
|
+
)
|
|
187
|
+
if cal.is_working_day(cursor):
|
|
188
|
+
counted += 1
|
|
189
|
+
else:
|
|
190
|
+
skipped += 1
|
|
191
|
+
return cursor, skipped
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def working_days_in_window(cal: Any, start: date, finish: date) -> tuple[int, int]:
|
|
195
|
+
"""``(working, non_working)`` day counts across the inclusive ``[start, finish]``.
|
|
196
|
+
|
|
197
|
+
Used for rows the operator edited in the preview: the dates are theirs, but
|
|
198
|
+
the calendar read-out stays server-computed so an edited row reports the same
|
|
199
|
+
kind of fact a generated one does.
|
|
200
|
+
"""
|
|
201
|
+
# Strictly greater, matching the model's own CheckConstraint — a same-day
|
|
202
|
+
# window is rejected here rather than at INSERT time as an IntegrityError.
|
|
203
|
+
# (The serializer rejects it first; the guard keeps the helper honest for
|
|
204
|
+
# non-HTTP callers.)
|
|
205
|
+
if finish <= start:
|
|
206
|
+
raise CadenceError("finish_date must be after start_date.")
|
|
207
|
+
span = (finish - start).days + 1
|
|
208
|
+
if span > MAX_CALENDAR_SCAN_DAYS:
|
|
209
|
+
raise CadenceError(f"An iteration may not span more than {MAX_CALENDAR_SCAN_DAYS} days.")
|
|
210
|
+
working = sum(1 for i in range(span) if cal.is_working_day(start + timedelta(days=i)))
|
|
211
|
+
return working, span - working
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def existing_sprint_names(project: Project) -> set[str]:
|
|
215
|
+
"""Every live sprint name in the project — the idempotency key set.
|
|
216
|
+
|
|
217
|
+
Exact, case-sensitive match. A name differing only in case is a different
|
|
218
|
+
name, and silently folding them would make the generator refuse to create a
|
|
219
|
+
sprint the operator can see is missing.
|
|
220
|
+
"""
|
|
221
|
+
from trueppm_api.apps.projects.models import Sprint
|
|
222
|
+
|
|
223
|
+
return set(
|
|
224
|
+
Sprint.objects.filter(project=project, is_deleted=False).values_list("name", flat=True)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def build_cadence(
|
|
229
|
+
project: Project,
|
|
230
|
+
*,
|
|
231
|
+
count: int,
|
|
232
|
+
start_date: date,
|
|
233
|
+
length_days: int,
|
|
234
|
+
name_pattern: str,
|
|
235
|
+
first_index: int,
|
|
236
|
+
taken_names: set[str] | None = None,
|
|
237
|
+
) -> list[CadenceRow]:
|
|
238
|
+
"""Compute ``count`` back-to-back iterations from ``start_date``.
|
|
239
|
+
|
|
240
|
+
Each iteration starts on the first working day at or after the cursor, holds
|
|
241
|
+
exactly ``length_days`` working days, and hands the cursor to the day after
|
|
242
|
+
its finish — so the series is contiguous with no manufactured gap, and every
|
|
243
|
+
boundary lands on a working day.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
project: The project whose composed calendar governs the walk.
|
|
247
|
+
count: How many iterations to lay out (bounded by the serializer).
|
|
248
|
+
start_date: Requested start of the first iteration; snapped forward.
|
|
249
|
+
length_days: Working days per iteration.
|
|
250
|
+
name_pattern: Name template containing ``{n}``.
|
|
251
|
+
first_index: Value substituted for ``{n}`` in the first iteration.
|
|
252
|
+
taken_names: Pre-read existing names; re-read from the DB when omitted.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
One :class:`CadenceRow` per iteration, in chronological order.
|
|
256
|
+
"""
|
|
257
|
+
from trueppm_api.apps.scheduling.calendars import compose_project_calendar
|
|
258
|
+
|
|
259
|
+
cal = compose_project_calendar(project)
|
|
260
|
+
taken = existing_sprint_names(project) if taken_names is None else taken_names
|
|
261
|
+
|
|
262
|
+
rows: list[CadenceRow] = []
|
|
263
|
+
cursor = start_date
|
|
264
|
+
for offset in range(count):
|
|
265
|
+
start = _next_working_day(cal, cursor)
|
|
266
|
+
finish, skipped = _window_from_start(cal, start, length_days)
|
|
267
|
+
name = render_sprint_name(name_pattern, first_index + offset)
|
|
268
|
+
rows.append(
|
|
269
|
+
CadenceRow(
|
|
270
|
+
name=name,
|
|
271
|
+
start_date=start,
|
|
272
|
+
finish_date=finish,
|
|
273
|
+
working_days=length_days,
|
|
274
|
+
non_working_days_skipped=skipped,
|
|
275
|
+
exists=name in taken,
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
cursor = finish + timedelta(days=1)
|
|
279
|
+
return rows
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def annotate_edited_rows(
|
|
283
|
+
project: Project,
|
|
284
|
+
edits: list[dict[str, Any]],
|
|
285
|
+
*,
|
|
286
|
+
taken_names: set[str] | None = None,
|
|
287
|
+
) -> list[CadenceRow]:
|
|
288
|
+
"""Turn operator-edited preview rows into :class:`CadenceRow` records.
|
|
289
|
+
|
|
290
|
+
The operator owns the dates and names once they have edited the preview —
|
|
291
|
+
this is the "editable preview" half of the contract, and re-deriving the
|
|
292
|
+
window from the generator's rules would silently undo their edit. What stays
|
|
293
|
+
server-owned is the calendar read-out and the idempotency verdict.
|
|
294
|
+
"""
|
|
295
|
+
from trueppm_api.apps.scheduling.calendars import compose_project_calendar
|
|
296
|
+
|
|
297
|
+
cal = compose_project_calendar(project)
|
|
298
|
+
taken = existing_sprint_names(project) if taken_names is None else taken_names
|
|
299
|
+
|
|
300
|
+
rows: list[CadenceRow] = []
|
|
301
|
+
for edit in edits:
|
|
302
|
+
start = edit["start_date"]
|
|
303
|
+
finish = edit["finish_date"]
|
|
304
|
+
working, skipped = working_days_in_window(cal, start, finish)
|
|
305
|
+
rows.append(
|
|
306
|
+
CadenceRow(
|
|
307
|
+
name=edit["name"],
|
|
308
|
+
start_date=start,
|
|
309
|
+
finish_date=finish,
|
|
310
|
+
working_days=working,
|
|
311
|
+
non_working_days_skipped=skipped,
|
|
312
|
+
exists=edit["name"] in taken,
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
return rows
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def capacity_hint(project: Project) -> dict[str, Any]:
|
|
319
|
+
"""A suggested first-iteration points figure, with the sentence that bounds it.
|
|
320
|
+
|
|
321
|
+
Drawn from :func:`velocity_eligible_sprints` — the canonical "counts toward
|
|
322
|
+
velocity" set (ADR-0113), so a Sprint 0 the team excluded is excluded here
|
|
323
|
+
too. Returns ``points=None`` when the team has no closed iteration to draw
|
|
324
|
+
from, rather than inventing a default: a made-up ceiling is worse than none.
|
|
325
|
+
|
|
326
|
+
This is a *suggestion*. Nothing in this module writes it anywhere; see the
|
|
327
|
+
module docstring.
|
|
328
|
+
"""
|
|
329
|
+
from trueppm_api.apps.projects.services import velocity_eligible_sprints
|
|
330
|
+
|
|
331
|
+
samples = [
|
|
332
|
+
float(s.completed_points)
|
|
333
|
+
for s in velocity_eligible_sprints(project.pk)[:CAPACITY_HINT_WINDOW]
|
|
334
|
+
if s.completed_points is not None
|
|
335
|
+
]
|
|
336
|
+
if not samples:
|
|
337
|
+
return {
|
|
338
|
+
"points": None,
|
|
339
|
+
"basis": "no_history",
|
|
340
|
+
"sprints_sampled": 0,
|
|
341
|
+
"note": CAPACITY_HINT_NOTE_NO_HISTORY,
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
"points": round(statistics.fmean(samples)),
|
|
345
|
+
"basis": "velocity_average",
|
|
346
|
+
"sprints_sampled": len(samples),
|
|
347
|
+
"note": CAPACITY_HINT_NOTE,
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@transaction.atomic
|
|
352
|
+
def commit_cadence(
|
|
353
|
+
project: Project,
|
|
354
|
+
rows: list[CadenceRow],
|
|
355
|
+
*,
|
|
356
|
+
created_by: Any,
|
|
357
|
+
first_sprint_capacity_points: int | None = None,
|
|
358
|
+
) -> tuple[list[Sprint], list[CadenceRow]]:
|
|
359
|
+
"""Persist every row that does not already exist, in one transaction.
|
|
360
|
+
|
|
361
|
+
Returns ``(created, final_rows)`` where ``final_rows`` carries the
|
|
362
|
+
re-evaluated ``exists`` verdict — the caller echoes those back so the client
|
|
363
|
+
can see exactly which names were skipped.
|
|
364
|
+
|
|
365
|
+
Two deliberate choices:
|
|
366
|
+
|
|
367
|
+
- **A row lock on the project, not a unique constraint.** ``Sprint`` has no
|
|
368
|
+
``(project, name)`` uniqueness and cannot grow one without rejecting the
|
|
369
|
+
duplicate names some projects already hold. Taking ``select_for_update``
|
|
370
|
+
on the project row serializes concurrent generate calls for that project,
|
|
371
|
+
so the second caller's existence re-read sees the first caller's inserts
|
|
372
|
+
and skips them. Two browser tabs submitting at once therefore produce one
|
|
373
|
+
cadence.
|
|
374
|
+
|
|
375
|
+
Be honest about the blast radius: under ``ATOMIC_REQUESTS`` this
|
|
376
|
+
``atomic`` is a savepoint, so the lock is held until the *request*
|
|
377
|
+
transaction commits — through response serialization, not just the loop —
|
|
378
|
+
and every synced write in the project takes that same row's lock via
|
|
379
|
+
``_next_seq``. So this briefly blocks all writes in the project, not only
|
|
380
|
+
rival generate calls. That is why the loop below is kept as short as it
|
|
381
|
+
is (one cursor allocation, ``force_insert``) and why the row count is
|
|
382
|
+
bounded.
|
|
383
|
+
- **``save()`` in a loop, not ``bulk_create``.** ``Sprint.save()`` allocates
|
|
384
|
+
the per-project ``short_id`` from the shared object sequence, and
|
|
385
|
+
``VersionedModel`` bumps ``server_version`` for the sync delta;
|
|
386
|
+
``bulk_create`` bypasses both and would emit sprints that offline clients
|
|
387
|
+
never pull and that collide on short id. The loop is bounded by
|
|
388
|
+
:data:`MAX_GENERATED_SPRINTS`.
|
|
389
|
+
|
|
390
|
+
``first_sprint_capacity_points`` is **caller-gated**, not gated here:
|
|
391
|
+
``capacity_points`` is a SCHEDULER+ field (ADR-0073) and the check lives in
|
|
392
|
+
``SprintGenerateSerializer``. Any future non-view caller of this function
|
|
393
|
+
must re-apply that check — the #2781 shape, where a service with no policy
|
|
394
|
+
gate of its own let a later caller reintroduce a hole the view had closed.
|
|
395
|
+
"""
|
|
396
|
+
from trueppm_api.apps.projects.models import Project as ProjectModel
|
|
397
|
+
from trueppm_api.apps.projects.models import Sprint, SprintState
|
|
398
|
+
from trueppm_api.apps.sync.sequence import coalesce_sync_seq
|
|
399
|
+
|
|
400
|
+
# Serializes concurrent generate calls for this project (see docstring).
|
|
401
|
+
ProjectModel.objects.select_for_update().get(pk=project.pk)
|
|
402
|
+
taken = existing_sprint_names(project)
|
|
403
|
+
|
|
404
|
+
created: list[Sprint] = []
|
|
405
|
+
final_rows: list[CadenceRow] = []
|
|
406
|
+
# One sync cursor for the whole batch (#1527). Without this every save()
|
|
407
|
+
# issues its own `UPDATE projects_project SET last_sync_version = …` — 52
|
|
408
|
+
# updates to the row this block already holds a lock on. Rows written
|
|
409
|
+
# together may share a `sync_seq`: the delta is `> since` and the checkpoint
|
|
410
|
+
# is the maximum, so a batch is wholly before or wholly after any client
|
|
411
|
+
# checkpoint and no offline client can see half a cadence.
|
|
412
|
+
with coalesce_sync_seq():
|
|
413
|
+
for index, row in enumerate(rows):
|
|
414
|
+
if row.name in taken:
|
|
415
|
+
final_rows.append(
|
|
416
|
+
CadenceRow(
|
|
417
|
+
name=row.name,
|
|
418
|
+
start_date=row.start_date,
|
|
419
|
+
finish_date=row.finish_date,
|
|
420
|
+
working_days=row.working_days,
|
|
421
|
+
non_working_days_skipped=row.non_working_days_skipped,
|
|
422
|
+
exists=True,
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
continue
|
|
426
|
+
sprint = Sprint(
|
|
427
|
+
project=project,
|
|
428
|
+
name=row.name,
|
|
429
|
+
start_date=row.start_date,
|
|
430
|
+
finish_date=row.finish_date,
|
|
431
|
+
state=SprintState.PLANNED,
|
|
432
|
+
created_by=created_by,
|
|
433
|
+
)
|
|
434
|
+
# Keyed on the row's position in the cadence — NOT on "the first row we
|
|
435
|
+
# happened to create". On a re-run where iterations 1-4 already exist,
|
|
436
|
+
# `not created` would be true at iteration 5 and would stamp a ceiling
|
|
437
|
+
# onto a mid-series sprint the operator never pointed at. Position 0 or
|
|
438
|
+
# nothing: if the operator's first iteration already exists, the capacity
|
|
439
|
+
# they typed refers to a sprint this call is not writing, and the right
|
|
440
|
+
# answer is to leave every sprint alone.
|
|
441
|
+
if index == 0 and first_sprint_capacity_points is not None:
|
|
442
|
+
sprint.capacity_points = first_sprint_capacity_points
|
|
443
|
+
# Marks the 52 history rows as one generator call rather than 52
|
|
444
|
+
# indistinguishable hand creations (the `amend`/`inbound_sync` idiom).
|
|
445
|
+
sprint._change_reason = "generated cadence" # type: ignore[attr-defined]
|
|
446
|
+
# `force_insert`: this is unambiguously an INSERT (fresh instance,
|
|
447
|
+
# UUID default pk), and it lets `VersionedModel.save` skip the
|
|
448
|
+
# `filter(pk=…).exists()` probe it would otherwise run per row.
|
|
449
|
+
sprint.save(force_insert=True)
|
|
450
|
+
taken.add(row.name)
|
|
451
|
+
created.append(sprint)
|
|
452
|
+
final_rows.append(row)
|
|
453
|
+
return created, final_rows
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Workspace → Program → Project resolution for the sprint story picker's
|
|
2
|
+
|
|
3
|
+
default "Ready only" filter (ADR-0758, #2670).
|
|
4
|
+
|
|
5
|
+
The Sprints page's story picker offers backlog stories to commit into a
|
|
6
|
+
PLANNED sprint. By default it shows only stories at ``Task.dor == READY``
|
|
7
|
+
(ADR-0105) — a story missing an estimate or an unmet acceptance criterion is
|
|
8
|
+
filtered out of the default view rather than offered for one-click commit.
|
|
9
|
+
This is a per-scope *default* for that filter, not a permission: the picker
|
|
10
|
+
always carries a "Show all" escape hatch that reveals blocked stories with
|
|
11
|
+
their reason codes, and committing a non-ready story is never hard-blocked in
|
|
12
|
+
OSS (see ``product_backlog_services.dor_blockers`` — the READY gate itself is
|
|
13
|
+
advisory, and this setting governs the picker's starting view, not what a user
|
|
14
|
+
is allowed to do).
|
|
15
|
+
|
|
16
|
+
The *effective* default is resolved here, computed-on-read (ADR-0108) — there
|
|
17
|
+
is no stored/denormalized effective column to keep in sync. Clients (web,
|
|
18
|
+
mobile, MCP) read the serializer's ``effective_sprint_picker_ready_only_default``;
|
|
19
|
+
they never re-implement this precedence.
|
|
20
|
+
|
|
21
|
+
Precedence: project override → program override → workspace value.
|
|
22
|
+
|
|
23
|
+
Mirrors ``estimation_scale``: there is **no override_policy** and no
|
|
24
|
+
enforcement seam. Unlike sharing/duration-policy, a "ready only by default"
|
|
25
|
+
setting has no meaningful non-degenerate ENFORCE behavior without also
|
|
26
|
+
building the hard commit-time block the governing issue (#2670) explicitly
|
|
27
|
+
scopes out of OSS — so this field is a plain PO/team preference, freely
|
|
28
|
+
overridable at every scope (OSS by construction). A future Enterprise ADR that
|
|
29
|
+
adds a genuine hard-block gate would introduce its own override_policy/
|
|
30
|
+
enforcement seam rather than retrofitting one here.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
from typing import TYPE_CHECKING, Literal
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from trueppm_api.apps.projects.models import Program, Project
|
|
39
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
40
|
+
|
|
41
|
+
#: The inheritable field, identically named on Workspace (non-null root),
|
|
42
|
+
#: Program, and Project (nullable override).
|
|
43
|
+
SPRINT_PICKER_READY_ONLY_FIELD = "sprint_picker_ready_only_default"
|
|
44
|
+
|
|
45
|
+
#: Which scope supplied the effective default. No ``system_default`` — the
|
|
46
|
+
#: Workspace root is non-null, so resolution always terminates in a real scope.
|
|
47
|
+
SprintPickerReadyOnlySource = Literal["project", "program", "workspace"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _program_of(obj: Program | Project) -> Program | None:
|
|
51
|
+
"""The owning program of a project, or None for a program/standalone project."""
|
|
52
|
+
from trueppm_api.apps.projects.models import Program
|
|
53
|
+
|
|
54
|
+
if isinstance(obj, Program):
|
|
55
|
+
return None
|
|
56
|
+
return obj.program if obj.program_id else None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _parent_value(obj: Program | Project, *, workspace: Workspace) -> bool:
|
|
60
|
+
"""Resolve the default from ``obj``'s parent scope up to the workspace.
|
|
61
|
+
|
|
62
|
+
A Program's parent is the workspace. A Project's parent is its program (whose
|
|
63
|
+
own override resolves up to the workspace) or, if standalone, the workspace.
|
|
64
|
+
"""
|
|
65
|
+
program = _program_of(obj)
|
|
66
|
+
if program is not None:
|
|
67
|
+
program_override = getattr(program, SPRINT_PICKER_READY_ONLY_FIELD)
|
|
68
|
+
if program_override is not None:
|
|
69
|
+
return bool(program_override)
|
|
70
|
+
return bool(getattr(workspace, SPRINT_PICKER_READY_ONLY_FIELD))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def resolve_effective_sprint_picker_ready_only(
|
|
74
|
+
obj: Program | Project,
|
|
75
|
+
*,
|
|
76
|
+
workspace: Workspace | None = None,
|
|
77
|
+
) -> bool:
|
|
78
|
+
"""Resolve the effective "Ready only" default for a program or project.
|
|
79
|
+
|
|
80
|
+
``workspace`` may be passed to avoid re-loading the singleton per object when
|
|
81
|
+
resolving a list (the serializer caches it once); otherwise it is loaded here.
|
|
82
|
+
"""
|
|
83
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
84
|
+
|
|
85
|
+
if workspace is None:
|
|
86
|
+
workspace = Workspace.load()
|
|
87
|
+
|
|
88
|
+
own = getattr(obj, SPRINT_PICKER_READY_ONLY_FIELD) # nullable override
|
|
89
|
+
if own is not None:
|
|
90
|
+
return bool(own)
|
|
91
|
+
return _parent_value(obj, workspace=workspace)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def resolve_inherited_sprint_picker_ready_only(
|
|
95
|
+
obj: Program | Project,
|
|
96
|
+
*,
|
|
97
|
+
workspace: Workspace | None = None,
|
|
98
|
+
) -> bool:
|
|
99
|
+
"""Default ``obj`` would inherit if its own override were cleared.
|
|
100
|
+
|
|
101
|
+
Drives a settings "Inherit (On/Off)" affordance — it deliberately skips
|
|
102
|
+
``obj``'s own override and resolves from the parent scope up.
|
|
103
|
+
"""
|
|
104
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
105
|
+
|
|
106
|
+
if workspace is None:
|
|
107
|
+
workspace = Workspace.load()
|
|
108
|
+
return _parent_value(obj, workspace=workspace)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def resolve_sprint_picker_ready_only_source(
|
|
112
|
+
obj: Program | Project,
|
|
113
|
+
*,
|
|
114
|
+
workspace: Workspace | None = None,
|
|
115
|
+
) -> SprintPickerReadyOnlySource:
|
|
116
|
+
"""Which scope supplied :func:`resolve_effective_sprint_picker_ready_only`.
|
|
117
|
+
|
|
118
|
+
The breadcrumb the settings UI reads to explain which scope set the
|
|
119
|
+
default, without re-deriving the precedence. ``workspace`` is accepted for
|
|
120
|
+
signature symmetry with the other resolvers (it is never needed — the
|
|
121
|
+
terminal scope is "workspace" by elimination).
|
|
122
|
+
"""
|
|
123
|
+
from trueppm_api.apps.projects.models import Program
|
|
124
|
+
|
|
125
|
+
if not isinstance(obj, Program) and getattr(obj, SPRINT_PICKER_READY_ONLY_FIELD) is not None:
|
|
126
|
+
return "project"
|
|
127
|
+
if isinstance(obj, Program) and getattr(obj, SPRINT_PICKER_READY_ONLY_FIELD) is not None:
|
|
128
|
+
return "program"
|
|
129
|
+
program = _program_of(obj)
|
|
130
|
+
if program is not None and getattr(program, SPRINT_PICKER_READY_ONLY_FIELD) is not None:
|
|
131
|
+
return "program"
|
|
132
|
+
return "workspace"
|