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,368 @@
|
|
|
1
|
+
"""Live retro board + team-health pulse service layer (ADR-0117).
|
|
2
|
+
|
|
3
|
+
Every board-item write goes through here so the single broadcast point — a
|
|
4
|
+
best-effort ``broadcast_board_event`` deferred with ``transaction.on_commit``
|
|
5
|
+
(ADR-0117 §DE.1) — lives in one place and the viewset stays thin. Concurrency is
|
|
6
|
+
per-item last-write-wins on ``server_version``: writes always apply and bump the
|
|
7
|
+
version; the broadcast carries the winning state so a client whose in-flight edit
|
|
8
|
+
was superseded reconciles (and offers a non-destructive undo) on its side.
|
|
9
|
+
|
|
10
|
+
The pulse path deliberately does **not** broadcast: a project-board event reaches
|
|
11
|
+
every connected client including the PM band, so even a content-free "pulse
|
|
12
|
+
answered" ping is a read-receipt signal Morgan's hard-NO forbids. The trend is a
|
|
13
|
+
reflective view, refetched on retro open — not a live race — and is gated on read
|
|
14
|
+
by ADR-0104's ``pulse`` signal (team + coach only; PM/PMO omitted entirely;
|
|
15
|
+
non-member denied).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import TYPE_CHECKING, Any
|
|
21
|
+
|
|
22
|
+
from django.db import transaction
|
|
23
|
+
from rest_framework.exceptions import ValidationError
|
|
24
|
+
|
|
25
|
+
# Server-side cap on a sticky body (ADR-0117 §1). Bounds storage and the broadcast
|
|
26
|
+
# fan-out — a sticky is a short note, not a document.
|
|
27
|
+
MAX_STICKY_LEN = 2000
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from django.contrib.auth.models import User
|
|
31
|
+
from rest_framework.request import Request
|
|
32
|
+
|
|
33
|
+
from trueppm_api.apps.projects.models import (
|
|
34
|
+
PulseResponse,
|
|
35
|
+
RetroActionItem,
|
|
36
|
+
RetroBoardItem,
|
|
37
|
+
Sprint,
|
|
38
|
+
SprintRetro,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _assert_board_writable(sprint: Sprint) -> None:
|
|
43
|
+
"""Guard the editable window (ADR-0117 §6). PLANNED/CANCELLED reject writes.
|
|
44
|
+
|
|
45
|
+
The live ceremony usually runs at/just-after close, so a hard ACTIVE-only lock
|
|
46
|
+
would shut the board at the exact moment teams use it — ACTIVE *and* COMPLETED
|
|
47
|
+
are writable; CANCELLED (and not-yet-started PLANNED) are read-only.
|
|
48
|
+
"""
|
|
49
|
+
from trueppm_api.apps.projects.models import SprintState
|
|
50
|
+
|
|
51
|
+
if sprint.state not in (SprintState.ACTIVE, SprintState.COMPLETED):
|
|
52
|
+
raise ValidationError(
|
|
53
|
+
f"The retro board is read-only while the sprint is {sprint.state}; "
|
|
54
|
+
"it opens when the sprint is active and stays editable after close."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _get_or_create_retro(sprint: Sprint, actor: User) -> SprintRetro:
|
|
59
|
+
"""Lazily create the SprintRetro the first sticky/pulse attaches to.
|
|
60
|
+
|
|
61
|
+
Mirrors the single-author retro upsert's lazy creation so an existing sprint
|
|
62
|
+
needs no data migration — a retro simply springs into being on first use.
|
|
63
|
+
"""
|
|
64
|
+
from trueppm_api.apps.projects.models import SprintRetro
|
|
65
|
+
|
|
66
|
+
retro, _ = SprintRetro.objects.get_or_create(
|
|
67
|
+
sprint=sprint,
|
|
68
|
+
defaults={"created_by": actor},
|
|
69
|
+
)
|
|
70
|
+
return retro
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _broadcast(project_id: Any, event_type: str, payload: dict[str, Any]) -> None:
|
|
74
|
+
"""Defer a best-effort board broadcast until the surrounding tx commits."""
|
|
75
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
76
|
+
|
|
77
|
+
pid = str(project_id)
|
|
78
|
+
transaction.on_commit(lambda: broadcast_board_event(pid, event_type, payload))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# Board stickies — concurrent multi-writer CRUD (ADR-0117 §1, §3)
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def create_board_item(
|
|
87
|
+
sprint: Sprint,
|
|
88
|
+
*,
|
|
89
|
+
column: str,
|
|
90
|
+
text: str,
|
|
91
|
+
color: str,
|
|
92
|
+
author: User,
|
|
93
|
+
) -> RetroBoardItem:
|
|
94
|
+
"""Create a sticky, appended to the end of its column.
|
|
95
|
+
|
|
96
|
+
Position is ``max(column) + 1`` so a new card lands at the bottom; a later
|
|
97
|
+
drag computes a fractional midpoint via :func:`move_board_item`.
|
|
98
|
+
"""
|
|
99
|
+
from django.db.models import Max
|
|
100
|
+
|
|
101
|
+
from trueppm_api.apps.projects.models import RetroBoardItem, RetroColumn
|
|
102
|
+
|
|
103
|
+
_assert_board_writable(sprint)
|
|
104
|
+
body = (text or "").strip()
|
|
105
|
+
if not body:
|
|
106
|
+
raise ValidationError({"text": "A sticky cannot be empty."})
|
|
107
|
+
if len(body) > MAX_STICKY_LEN:
|
|
108
|
+
raise ValidationError({"text": f"A sticky cannot exceed {MAX_STICKY_LEN} characters."})
|
|
109
|
+
if column not in RetroColumn.values:
|
|
110
|
+
raise ValidationError({"column": f"Unknown column '{column}'."})
|
|
111
|
+
|
|
112
|
+
with transaction.atomic():
|
|
113
|
+
retro = _get_or_create_retro(sprint, author)
|
|
114
|
+
last = (
|
|
115
|
+
RetroBoardItem.objects.filter(retro=retro, column=column, is_deleted=False)
|
|
116
|
+
.aggregate(m=Max("position"))
|
|
117
|
+
.get("m")
|
|
118
|
+
)
|
|
119
|
+
item = RetroBoardItem.objects.create(
|
|
120
|
+
retro=retro,
|
|
121
|
+
column=column,
|
|
122
|
+
text=body,
|
|
123
|
+
color=color or "",
|
|
124
|
+
author=author,
|
|
125
|
+
position=(last or 0.0) + 1.0,
|
|
126
|
+
)
|
|
127
|
+
_broadcast(
|
|
128
|
+
sprint.project_id,
|
|
129
|
+
"retro_item_created",
|
|
130
|
+
{
|
|
131
|
+
"id": str(item.id),
|
|
132
|
+
"retro_id": str(retro.id),
|
|
133
|
+
"sprint_id": str(sprint.id),
|
|
134
|
+
"column": item.column,
|
|
135
|
+
"author_id": author.pk,
|
|
136
|
+
},
|
|
137
|
+
)
|
|
138
|
+
return item
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def update_board_item(
|
|
142
|
+
item: RetroBoardItem, *, text: str | None, color: str | None
|
|
143
|
+
) -> RetroBoardItem:
|
|
144
|
+
"""Edit a sticky's text/color (last-write-wins; ADR-0117 §3).
|
|
145
|
+
|
|
146
|
+
Always applies — no version precondition. The bumped ``server_version`` and
|
|
147
|
+
the broadcast let a client whose edit was superseded detect the collision and
|
|
148
|
+
offer its non-destructive undo. Editing never moves the sticky (see
|
|
149
|
+
:func:`move_board_item`).
|
|
150
|
+
"""
|
|
151
|
+
_assert_board_writable(item.retro.sprint)
|
|
152
|
+
fields: list[str] = []
|
|
153
|
+
if text is not None:
|
|
154
|
+
body = text.strip()
|
|
155
|
+
if not body:
|
|
156
|
+
raise ValidationError({"text": "A sticky cannot be empty."})
|
|
157
|
+
if len(body) > MAX_STICKY_LEN:
|
|
158
|
+
raise ValidationError({"text": f"A sticky cannot exceed {MAX_STICKY_LEN} characters."})
|
|
159
|
+
item.text = body
|
|
160
|
+
fields.append("text")
|
|
161
|
+
if color is not None:
|
|
162
|
+
item.color = color
|
|
163
|
+
fields.append("color")
|
|
164
|
+
if fields:
|
|
165
|
+
# Couple the save to its broadcast in one transaction (#1323), matching
|
|
166
|
+
# create_board_item: the on_commit broadcast then fires only if the save
|
|
167
|
+
# commits, and is deferred to that commit rather than the surrounding
|
|
168
|
+
# autocommit boundary.
|
|
169
|
+
with transaction.atomic():
|
|
170
|
+
item.save(update_fields=[*fields, "updated_at", "server_version"])
|
|
171
|
+
_broadcast(
|
|
172
|
+
item.retro.sprint.project_id,
|
|
173
|
+
"retro_item_updated",
|
|
174
|
+
{"id": str(item.id), "retro_id": str(item.retro_id)},
|
|
175
|
+
)
|
|
176
|
+
return item
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def move_board_item(item: RetroBoardItem, *, column: str, position: float) -> RetroBoardItem:
|
|
180
|
+
"""Move a sticky to a column + fractional position (drag-reorder; ADR-0117 §3)."""
|
|
181
|
+
from trueppm_api.apps.projects.models import RetroColumn
|
|
182
|
+
|
|
183
|
+
_assert_board_writable(item.retro.sprint)
|
|
184
|
+
if column not in RetroColumn.values:
|
|
185
|
+
raise ValidationError({"column": f"Unknown column '{column}'."})
|
|
186
|
+
item.column = column
|
|
187
|
+
item.position = position
|
|
188
|
+
# Couple the save to its broadcast in one transaction (#1323).
|
|
189
|
+
with transaction.atomic():
|
|
190
|
+
item.save(update_fields=["column", "position", "updated_at", "server_version"])
|
|
191
|
+
_broadcast(
|
|
192
|
+
item.retro.sprint.project_id,
|
|
193
|
+
"retro_item_moved",
|
|
194
|
+
{"id": str(item.id), "retro_id": str(item.retro_id), "column": column},
|
|
195
|
+
)
|
|
196
|
+
return item
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def delete_board_item(item: RetroBoardItem) -> None:
|
|
200
|
+
"""Soft-delete a sticky and broadcast its removal."""
|
|
201
|
+
_assert_board_writable(item.retro.sprint)
|
|
202
|
+
project_id = item.retro.sprint.project_id
|
|
203
|
+
retro_id = item.retro_id
|
|
204
|
+
item_id = item.id
|
|
205
|
+
item.is_deleted = True
|
|
206
|
+
# Couple the soft-delete to its broadcast in one transaction (#1323).
|
|
207
|
+
with transaction.atomic():
|
|
208
|
+
item.save(update_fields=["is_deleted", "updated_at", "server_version"])
|
|
209
|
+
_broadcast(
|
|
210
|
+
project_id,
|
|
211
|
+
"retro_item_deleted",
|
|
212
|
+
{"id": str(item_id), "retro_id": str(retro_id)},
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def convert_to_action(item: RetroBoardItem, actor: User) -> RetroActionItem:
|
|
217
|
+
"""Distil a discussion sticky into a RetroActionItem (ADR-0117 §1).
|
|
218
|
+
|
|
219
|
+
Idempotent (ADR-0117 §DE.7): a sticky already converted returns its existing
|
|
220
|
+
action item rather than creating a duplicate. The new action item then uses the
|
|
221
|
+
unchanged #858 promote flow.
|
|
222
|
+
"""
|
|
223
|
+
from trueppm_api.apps.projects.models import RetroActionItem
|
|
224
|
+
|
|
225
|
+
_assert_board_writable(item.retro.sprint)
|
|
226
|
+
with transaction.atomic():
|
|
227
|
+
locked = item.__class__.objects.select_for_update().get(pk=item.pk)
|
|
228
|
+
if locked.converted_action_item_id is not None:
|
|
229
|
+
existing = RetroActionItem.objects.filter(
|
|
230
|
+
pk=locked.converted_action_item_id, is_deleted=False
|
|
231
|
+
).first()
|
|
232
|
+
if existing is not None:
|
|
233
|
+
return existing
|
|
234
|
+
action = RetroActionItem.objects.create(retro=locked.retro, text=locked.text)
|
|
235
|
+
locked.converted_action_item_id = action.pk
|
|
236
|
+
locked.save(update_fields=["converted_action_item_id", "updated_at", "server_version"])
|
|
237
|
+
_broadcast(
|
|
238
|
+
locked.retro.sprint.project_id,
|
|
239
|
+
"retro_item_updated",
|
|
240
|
+
{"id": str(locked.id), "retro_id": str(locked.retro_id)},
|
|
241
|
+
)
|
|
242
|
+
return action
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
# Team-health pulse (ADR-0117 §5; ADR-0104 pulse gate consumed verbatim)
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def upsert_pulse_response(
|
|
251
|
+
sprint: Sprint,
|
|
252
|
+
*,
|
|
253
|
+
respondent: User,
|
|
254
|
+
mood: int | None,
|
|
255
|
+
energy: int | None,
|
|
256
|
+
confidence: int | None,
|
|
257
|
+
) -> PulseResponse:
|
|
258
|
+
"""One-tap upsert of the requester's own pulse response (ADR-0117 §5).
|
|
259
|
+
|
|
260
|
+
Keyed on ``unique(retro, respondent)`` so a re-tap updates rather than
|
|
261
|
+
duplicates. Deliberately does **not** broadcast — a pulse event would reach
|
|
262
|
+
PM-band clients as a read-receipt (Morgan 🔴). Validates each dimension to 1..5.
|
|
263
|
+
"""
|
|
264
|
+
from trueppm_api.apps.projects.models import PulseResponse
|
|
265
|
+
|
|
266
|
+
_assert_board_writable(sprint)
|
|
267
|
+
# Explicit per-field guards (not a loop) so the type narrows to ``int`` for the
|
|
268
|
+
# update_or_create below — mypy --strict + django-stubs reject ``int | None`` on
|
|
269
|
+
# the non-null model fields.
|
|
270
|
+
if mood is None or not (1 <= mood <= 5):
|
|
271
|
+
raise ValidationError({"mood": "Must be an integer 1-5."})
|
|
272
|
+
if energy is None or not (1 <= energy <= 5):
|
|
273
|
+
raise ValidationError({"energy": "Must be an integer 1-5."})
|
|
274
|
+
if confidence is not None and not (1 <= confidence <= 5):
|
|
275
|
+
raise ValidationError({"confidence": "Must be an integer 1-5 or omitted."})
|
|
276
|
+
|
|
277
|
+
with transaction.atomic():
|
|
278
|
+
retro = _get_or_create_retro(sprint, respondent)
|
|
279
|
+
response, _ = PulseResponse.objects.update_or_create(
|
|
280
|
+
retro=retro,
|
|
281
|
+
respondent=respondent,
|
|
282
|
+
is_deleted=False,
|
|
283
|
+
defaults={"mood": mood, "energy": energy, "confidence": confidence},
|
|
284
|
+
)
|
|
285
|
+
return response
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def my_pulse_response(sprint: Sprint, respondent: User) -> PulseResponse | None:
|
|
289
|
+
"""The requester's own current response for this sprint, echoed so they can change it."""
|
|
290
|
+
from trueppm_api.apps.projects.models import PulseResponse, SprintRetro
|
|
291
|
+
|
|
292
|
+
retro = SprintRetro.objects.filter(sprint=sprint, is_deleted=False).first()
|
|
293
|
+
if retro is None:
|
|
294
|
+
return None
|
|
295
|
+
return PulseResponse.objects.filter(
|
|
296
|
+
retro=retro, respondent=respondent, is_deleted=False
|
|
297
|
+
).first()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def pulse_trend(request: Request, sprint: Sprint) -> dict[str, Any]:
|
|
301
|
+
"""Cross-sprint pulse trend, gated by ADR-0104's ``pulse`` signal (the 🔴).
|
|
302
|
+
|
|
303
|
+
Returns ``{"gated": True}`` and **no data** for any requester outside the
|
|
304
|
+
signal's audience (PM/PMO band by default; non-member always) — a redacted
|
|
305
|
+
aggregate is no pulse (ADR-0104 §2.3). For the team + coach band, returns
|
|
306
|
+
aggregate-only per-sprint points (never an individual's raw answer) ordered by
|
|
307
|
+
sprint start, plus a server-computed ``energy_declining`` flag (the
|
|
308
|
+
"two-sprints-before-velocity" signal), the response count, and the requester's
|
|
309
|
+
own current response echoed back.
|
|
310
|
+
"""
|
|
311
|
+
from django.db.models import Avg, Count
|
|
312
|
+
|
|
313
|
+
from trueppm_api.apps.projects.models import PulseResponse
|
|
314
|
+
from trueppm_api.apps.projects.signal_privacy_services import can_read_signal
|
|
315
|
+
|
|
316
|
+
if not can_read_signal(request, sprint.project_id, "pulse"):
|
|
317
|
+
# Omit entirely — no count, no points, nothing to infer from (ADR-0104 §2.3).
|
|
318
|
+
return {"gated": True}
|
|
319
|
+
|
|
320
|
+
# Aggregate per sprint across the whole project, oldest → newest. Grouping by
|
|
321
|
+
# the sprint behind each retro keeps individual rows invisible — only means and
|
|
322
|
+
# counts leave the server.
|
|
323
|
+
rows = (
|
|
324
|
+
PulseResponse.objects.filter(
|
|
325
|
+
retro__sprint__project_id=sprint.project_id,
|
|
326
|
+
is_deleted=False,
|
|
327
|
+
)
|
|
328
|
+
.values("retro__sprint_id", "retro__sprint__name", "retro__sprint__start_date")
|
|
329
|
+
.annotate(
|
|
330
|
+
avg_mood=Avg("mood"),
|
|
331
|
+
avg_energy=Avg("energy"),
|
|
332
|
+
avg_confidence=Avg("confidence"),
|
|
333
|
+
response_count=Count("id"),
|
|
334
|
+
)
|
|
335
|
+
.order_by("retro__sprint__start_date", "retro__sprint__name")
|
|
336
|
+
)
|
|
337
|
+
points = [
|
|
338
|
+
{
|
|
339
|
+
"sprint_id": str(r["retro__sprint_id"]),
|
|
340
|
+
"sprint_name": r["retro__sprint__name"],
|
|
341
|
+
"avg_mood": round(r["avg_mood"], 2) if r["avg_mood"] is not None else None,
|
|
342
|
+
"avg_energy": round(r["avg_energy"], 2) if r["avg_energy"] is not None else None,
|
|
343
|
+
"avg_confidence": (
|
|
344
|
+
round(r["avg_confidence"], 2) if r["avg_confidence"] is not None else None
|
|
345
|
+
),
|
|
346
|
+
"response_count": r["response_count"],
|
|
347
|
+
}
|
|
348
|
+
for r in rows
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
# energy_declining: the last two points both fell — the coach's early-warning
|
|
352
|
+
# signal. Computed server-side so the client renders a fact, not a derivation.
|
|
353
|
+
energy_declining = False
|
|
354
|
+
energies = [p["avg_energy"] for p in points if p["avg_energy"] is not None]
|
|
355
|
+
if len(energies) >= 3:
|
|
356
|
+
energy_declining = energies[-1] < energies[-2] < energies[-3]
|
|
357
|
+
|
|
358
|
+
mine = my_pulse_response(sprint, request.user) # type: ignore[arg-type]
|
|
359
|
+
return {
|
|
360
|
+
"gated": False,
|
|
361
|
+
"points": points,
|
|
362
|
+
"energy_declining": energy_declining,
|
|
363
|
+
"my_response": (
|
|
364
|
+
{"mood": mine.mood, "energy": mine.energy, "confidence": mine.confidence}
|
|
365
|
+
if mine is not None
|
|
366
|
+
else None
|
|
367
|
+
),
|
|
368
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Retrospective service layer (ADR-0071).
|
|
2
|
+
|
|
3
|
+
Two operations: promote a ``RetroActionItem`` into a project-backlog ``Task``
|
|
4
|
+
(``promote_retro_action_item``) and atomically promote + pull into a
|
|
5
|
+
``PLANNED`` sprint as one SCHEDULER+ action (``pull_carryover_item_to_sprint``).
|
|
6
|
+
|
|
7
|
+
Sprint sovereignty is enforced at this layer: ``promote_retro_action_item``
|
|
8
|
+
never accepts a sprint argument; the resulting Task is unconditionally created
|
|
9
|
+
with ``sprint=None``. The only path that puts a retro action item into a
|
|
10
|
+
sprint is ``pull_carryover_item_to_sprint``, which requires SCHEDULER+ role
|
|
11
|
+
and an explicit ``target_sprint`` argument.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from django.core.exceptions import ValidationError
|
|
21
|
+
from django.db import transaction
|
|
22
|
+
from django.utils import timezone
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from django.contrib.auth.models import User
|
|
26
|
+
|
|
27
|
+
from trueppm_api.apps.projects.models import (
|
|
28
|
+
RetroActionItem,
|
|
29
|
+
Sprint,
|
|
30
|
+
Task,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AlreadyPromotedError(ValidationError):
|
|
37
|
+
"""Raised when promote is attempted on an action item that already has a Task.
|
|
38
|
+
|
|
39
|
+
Carries ``existing_task_id`` so the view layer can include it in the 409
|
|
40
|
+
response payload (the client can navigate to the existing task instead of
|
|
41
|
+
retrying).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, existing_task_id: str) -> None:
|
|
45
|
+
super().__init__("Action item already promoted.")
|
|
46
|
+
self.existing_task_id = existing_task_id
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def promote_retro_action_item(
|
|
50
|
+
action_item: RetroActionItem,
|
|
51
|
+
actor: User,
|
|
52
|
+
) -> Task:
|
|
53
|
+
"""Promote a RetroActionItem into a project-backlog Task.
|
|
54
|
+
|
|
55
|
+
Atomic:
|
|
56
|
+
1. SELECT FOR UPDATE on the action item; assert promoted_task_id IS NULL
|
|
57
|
+
(else raise AlreadyPromotedError).
|
|
58
|
+
2. Create Task(status=BACKLOG, sprint=None) in the action item's project.
|
|
59
|
+
3. If the action item's assignee differs from the actor, create a
|
|
60
|
+
TaskSuggestedAssignee (PENDING) instead of setting Task.assignee.
|
|
61
|
+
Self-claim binds Task.assignee directly.
|
|
62
|
+
4. Stamp action_item.promoted_task_id = task.pk and save.
|
|
63
|
+
5. On commit: broadcast ``task_created`` + enqueue CPM recalculate.
|
|
64
|
+
|
|
65
|
+
The new Task is unconditionally ``sprint=None``. Sprint assignment is a
|
|
66
|
+
separate SCHEDULER+ action (see ``pull_carryover_item_to_sprint``).
|
|
67
|
+
"""
|
|
68
|
+
from trueppm_api.apps.projects.models import (
|
|
69
|
+
RetroActionItem,
|
|
70
|
+
SuggestionSource,
|
|
71
|
+
SuggestionState,
|
|
72
|
+
Task,
|
|
73
|
+
TaskStatus,
|
|
74
|
+
TaskSuggestedAssignee,
|
|
75
|
+
)
|
|
76
|
+
from trueppm_api.apps.scheduling.models import ScheduleRequestReason
|
|
77
|
+
from trueppm_api.apps.scheduling.services import enqueue_recalculate
|
|
78
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
79
|
+
|
|
80
|
+
with transaction.atomic():
|
|
81
|
+
# Re-read with row lock; the in-memory action_item passed in may be stale.
|
|
82
|
+
locked = (
|
|
83
|
+
RetroActionItem.objects.select_for_update()
|
|
84
|
+
.select_related("retro__sprint__project")
|
|
85
|
+
.get(pk=action_item.pk)
|
|
86
|
+
)
|
|
87
|
+
if locked.promoted_task_id is not None:
|
|
88
|
+
raise AlreadyPromotedError(str(locked.promoted_task_id))
|
|
89
|
+
|
|
90
|
+
project = locked.retro.sprint.project
|
|
91
|
+
retro_short_id = locked.retro.sprint.short_id or str(locked.retro.sprint_id)[:8]
|
|
92
|
+
|
|
93
|
+
# Self-claim binds Task.assignee directly; assigning someone else
|
|
94
|
+
# creates a soft suggestion the assignee must accept on My Work.
|
|
95
|
+
is_self_claim = locked.assignee_id is not None and locked.assignee_id == actor.pk
|
|
96
|
+
bound_assignee = locked.assignee if is_self_claim else None
|
|
97
|
+
|
|
98
|
+
task = Task.objects.create(
|
|
99
|
+
project=project,
|
|
100
|
+
name=locked.text[:255],
|
|
101
|
+
duration=1,
|
|
102
|
+
status=TaskStatus.BACKLOG,
|
|
103
|
+
sprint=None,
|
|
104
|
+
assignee=bound_assignee,
|
|
105
|
+
story_points=locked.story_points,
|
|
106
|
+
notes=f'source: "retrospective" (from Sprint {retro_short_id} retro)',
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
suggestion: TaskSuggestedAssignee | None = None
|
|
110
|
+
if locked.assignee_id is not None and not is_self_claim:
|
|
111
|
+
# locked.assignee_id guard above narrows locked.assignee to non-None
|
|
112
|
+
# at runtime; cast for mypy since FK type still includes None.
|
|
113
|
+
suggested_user = locked.assignee
|
|
114
|
+
assert suggested_user is not None
|
|
115
|
+
suggestion = TaskSuggestedAssignee.objects.create(
|
|
116
|
+
task=task,
|
|
117
|
+
suggested_user=suggested_user,
|
|
118
|
+
suggested_by=actor,
|
|
119
|
+
source=SuggestionSource.RETROSPECTIVE,
|
|
120
|
+
state=SuggestionState.PENDING,
|
|
121
|
+
reason=f"from retro of Sprint {retro_short_id}",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
locked.promoted_task_id = task.pk
|
|
125
|
+
locked.save(update_fields=["promoted_task_id"])
|
|
126
|
+
|
|
127
|
+
# Snapshot every broadcast value to a plain scalar *at registration time*
|
|
128
|
+
# so the deferred dispatch can never read a later-mutated (or by-then
|
|
129
|
+
# garbage-collected) ORM instance (#1323). `suggestion.pk` and
|
|
130
|
+
# `locked.assignee_id` were the last live ORM reads left in the closure.
|
|
131
|
+
project_id = str(project.pk)
|
|
132
|
+
task_id = str(task.pk)
|
|
133
|
+
action_item_id = str(locked.pk)
|
|
134
|
+
retro_id = str(locked.retro_id)
|
|
135
|
+
suggestion_id = str(suggestion.pk) if suggestion is not None else None
|
|
136
|
+
suggested_user_id = locked.assignee_id
|
|
137
|
+
|
|
138
|
+
def _on_commit_dispatch() -> None:
|
|
139
|
+
# Same ``id``-omission defect as the carryover ``task_updated`` below
|
|
140
|
+
# (#2843), found by the payload-shape guard rather than by the audit:
|
|
141
|
+
# every other ``task_created`` site emits ``{"id": ...}``, and a client
|
|
142
|
+
# keying on ``payload.id`` cannot see this one at all. ``task_id`` stays
|
|
143
|
+
# for one release; drop it after 0.5.
|
|
144
|
+
broadcast_board_event(
|
|
145
|
+
project_id,
|
|
146
|
+
"task_created",
|
|
147
|
+
{
|
|
148
|
+
"id": task_id,
|
|
149
|
+
"task_id": task_id,
|
|
150
|
+
"source": "retrospective",
|
|
151
|
+
"retro_id": retro_id,
|
|
152
|
+
"action_item_id": action_item_id,
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
if suggestion_id is not None:
|
|
156
|
+
broadcast_board_event(
|
|
157
|
+
project_id,
|
|
158
|
+
"suggestion_created",
|
|
159
|
+
{
|
|
160
|
+
"task_id": task_id,
|
|
161
|
+
"suggestion_id": suggestion_id,
|
|
162
|
+
"suggested_user_id": suggested_user_id,
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
enqueue_recalculate(
|
|
166
|
+
project_id,
|
|
167
|
+
reason=ScheduleRequestReason.TASK_CHANGE,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
transaction.on_commit(_on_commit_dispatch)
|
|
171
|
+
|
|
172
|
+
return task
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def pull_carryover_item_to_sprint(
|
|
176
|
+
action_item: RetroActionItem,
|
|
177
|
+
target_sprint: Sprint,
|
|
178
|
+
actor: User,
|
|
179
|
+
) -> Task:
|
|
180
|
+
"""Atomically promote an action item and assign the resulting Task to a sprint.
|
|
181
|
+
|
|
182
|
+
SCHEDULER+ role is enforced at the view layer; this service trusts the
|
|
183
|
+
caller has been gated. The target sprint must be in the same project as
|
|
184
|
+
the action item's source retro (raises ValidationError otherwise).
|
|
185
|
+
|
|
186
|
+
Behaviour:
|
|
187
|
+
- If the action item is already promoted, reuses the existing Task and
|
|
188
|
+
sets ``task.sprint = target_sprint``.
|
|
189
|
+
- Otherwise calls ``promote_retro_action_item`` first, then sets
|
|
190
|
+
``task.sprint = target_sprint``.
|
|
191
|
+
- Fires a single ``task_updated`` broadcast at commit (in addition to
|
|
192
|
+
the ``task_created`` broadcast from promotion, if the promote happened
|
|
193
|
+
in this call) — clients can dedupe by task_id.
|
|
194
|
+
|
|
195
|
+
Returns the Task in its post-assign state (sprint set).
|
|
196
|
+
"""
|
|
197
|
+
from trueppm_api.apps.projects.models import (
|
|
198
|
+
RetroActionItem,
|
|
199
|
+
Sprint,
|
|
200
|
+
SprintState,
|
|
201
|
+
Task,
|
|
202
|
+
)
|
|
203
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
204
|
+
|
|
205
|
+
with transaction.atomic():
|
|
206
|
+
locked = (
|
|
207
|
+
RetroActionItem.objects.select_for_update()
|
|
208
|
+
.select_related("retro__sprint__project")
|
|
209
|
+
.get(pk=action_item.pk)
|
|
210
|
+
)
|
|
211
|
+
source_project_id = locked.retro.sprint.project_id
|
|
212
|
+
|
|
213
|
+
target_locked = Sprint.objects.select_for_update().get(pk=target_sprint.pk)
|
|
214
|
+
if target_locked.project_id != source_project_id:
|
|
215
|
+
raise ValidationError(
|
|
216
|
+
"Target sprint must be in the same project as the retro action item."
|
|
217
|
+
)
|
|
218
|
+
if target_locked.state != SprintState.PLANNED:
|
|
219
|
+
raise ValidationError(
|
|
220
|
+
"Carryover pull is only permitted into PLANNED sprints; "
|
|
221
|
+
f"target sprint state is {target_locked.state}."
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if locked.promoted_task_id is None:
|
|
225
|
+
# Promote first; the service handles broadcast + recalc enqueue.
|
|
226
|
+
# Note: promote_retro_action_item opens its own atomic block but
|
|
227
|
+
# uses the outer transaction (Django nests via savepoints).
|
|
228
|
+
task = promote_retro_action_item(locked, actor)
|
|
229
|
+
else:
|
|
230
|
+
task = Task.objects.select_for_update().get(pk=locked.promoted_task_id)
|
|
231
|
+
|
|
232
|
+
task.sprint = target_locked
|
|
233
|
+
task.save(update_fields=["sprint", "server_version"])
|
|
234
|
+
|
|
235
|
+
project_id = str(source_project_id)
|
|
236
|
+
task_id = str(task.pk)
|
|
237
|
+
sprint_id = str(target_locked.pk)
|
|
238
|
+
|
|
239
|
+
def _broadcast_carryover() -> None:
|
|
240
|
+
# ``id`` is the task identity every other ``task_updated`` site emits and
|
|
241
|
+
# the only one websockets.md documents (#2843). This was the single site
|
|
242
|
+
# in the tree using ``task_id``, which made the frontend's
|
|
243
|
+
# ``typeof payload.id === 'string' ? payload.id : null`` resolve to null:
|
|
244
|
+
# the ADR-0152 per-task version dedup was skipped and the history
|
|
245
|
+
# invalidation widened from one task to the whole project.
|
|
246
|
+
#
|
|
247
|
+
# ``task_id`` is kept alongside for one release in case anything reads
|
|
248
|
+
# it; drop it after 0.5.
|
|
249
|
+
broadcast_board_event(
|
|
250
|
+
project_id,
|
|
251
|
+
"task_updated",
|
|
252
|
+
{
|
|
253
|
+
"id": task_id,
|
|
254
|
+
"task_id": task_id,
|
|
255
|
+
"sprint_id": sprint_id,
|
|
256
|
+
"source": "retro_carryover",
|
|
257
|
+
},
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
transaction.on_commit(_broadcast_carryover)
|
|
261
|
+
|
|
262
|
+
return task
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def reset_promoted_task_on_delete(task_id: str) -> None:
|
|
266
|
+
"""Reset RetroActionItem.promoted_task_id to NULL when its Task is soft-deleted.
|
|
267
|
+
|
|
268
|
+
Wired to the ``Task`` post_delete (or post_save when ``is_deleted=True``)
|
|
269
|
+
signal so a re-deleted Task automatically frees the originating action
|
|
270
|
+
item to be re-promoted. ADR-0071 §2 rollback.
|
|
271
|
+
"""
|
|
272
|
+
from trueppm_api.apps.projects.models import RetroActionItem
|
|
273
|
+
|
|
274
|
+
updated = RetroActionItem.objects.filter(promoted_task_id=task_id).update(promoted_task_id=None)
|
|
275
|
+
if updated:
|
|
276
|
+
logger.info(
|
|
277
|
+
"reset_promoted_task_on_delete: freed %s action items linked to task %s",
|
|
278
|
+
updated,
|
|
279
|
+
task_id,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def now_utc() -> datetime:
|
|
284
|
+
"""Centralized timezone-aware now for tests to monkey-patch."""
|
|
285
|
+
return timezone.now()
|