trueppm-api 0.4.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"""Project template structure: extract from a project, materialize into one (ADR-0789, #2729).
|
|
2
|
+
|
|
3
|
+
The whole point of this module is the pair of functions at the bottom, and the
|
|
4
|
+
asymmetry between them:
|
|
5
|
+
|
|
6
|
+
* :func:`extract_structure` reads a project and produces a **frozen document**. It
|
|
7
|
+
is the gate that strips moment and person — every field it declines to copy is a
|
|
8
|
+
live defect if carried, not merely noise (ADR-0789 §1).
|
|
9
|
+
* :func:`materialize_structure` reads that document and writes rows into a
|
|
10
|
+
*different* project, resolving durations against *that* project's calendar and
|
|
11
|
+
start date.
|
|
12
|
+
|
|
13
|
+
Nothing here calls Celery, touches a request, or knows about permissions. The task
|
|
14
|
+
in ``template_tasks.py`` supplies the transaction and the claim; the viewset
|
|
15
|
+
supplies the RBAC. Keeping this layer pure is what makes the seeding logic testable
|
|
16
|
+
without a broker.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import uuid
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import TYPE_CHECKING, Any
|
|
24
|
+
|
|
25
|
+
from django.utils import timezone
|
|
26
|
+
|
|
27
|
+
from trueppm_api.apps.projects.wbs_paths import offset_wbs_path, root_ordinal_offset
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from trueppm_api.apps.projects.models import Project, ProjectTemplate, Task
|
|
31
|
+
|
|
32
|
+
#: Current structure-document version. Bumped when the shape changes
|
|
33
|
+
#: incompatibly; ``materialize_structure`` refuses a version it does not know
|
|
34
|
+
#: rather than guessing at a field it has never seen.
|
|
35
|
+
STRUCTURE_VERSION = 1
|
|
36
|
+
|
|
37
|
+
#: Hard ceiling on nodes in one template, mirroring the seed importer's
|
|
38
|
+
#: ``MAX_SEED_NODES``. ``structure`` is JSONB, so the seeding job could otherwise
|
|
39
|
+
#: be handed an arbitrarily large document by any path that reaches the database.
|
|
40
|
+
MAX_TEMPLATE_NODES = 2000
|
|
41
|
+
|
|
42
|
+
#: What a template is allowed to carry, and therefore what ``carries`` reports.
|
|
43
|
+
#: This list is the human-readable face of the strip rules — it is published with
|
|
44
|
+
#: the template so an adopter can see what they are getting *before* they adopt.
|
|
45
|
+
CARRIES_STRUCTURE = "structure"
|
|
46
|
+
CARRIES_DEPENDENCIES = "dependencies"
|
|
47
|
+
CARRIES_DURATIONS = "durations"
|
|
48
|
+
CARRIES_MILESTONES = "milestones"
|
|
49
|
+
CARRIES_DELIVERY_MODES = "delivery_modes"
|
|
50
|
+
CARRIES_SPRINT_LENGTH = "sprint_length"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TemplateStructureError(ValueError):
|
|
54
|
+
"""The structure document is unusable — malformed, too large, or a version we don't know."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class MaterializeResult:
|
|
59
|
+
"""What one apply wrote — the undo set, plus the counts the seed banner shows.
|
|
60
|
+
|
|
61
|
+
``milestones_created`` / ``dependencies_created`` are read off the rows and edges
|
|
62
|
+
:func:`materialize_structure` already built in memory (ADR-0799 §2) — never a
|
|
63
|
+
second query. The seed banner (#2731) reads these off
|
|
64
|
+
``TemplateApplication.result_summary`` rather than re-deriving them client-side
|
|
65
|
+
from the full task list, which would be a second, driftable definition of the same
|
|
66
|
+
counts the server already knows at write time.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
task_ids: list[uuid.UUID]
|
|
70
|
+
milestones_created: int
|
|
71
|
+
dependencies_created: int
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _task_node(task: Any) -> dict[str, Any]:
|
|
75
|
+
"""Serialize one task into its template node — shape only.
|
|
76
|
+
|
|
77
|
+
Every field NOT copied here is a deliberate strip, and the reasons are
|
|
78
|
+
different for each class (ADR-0789 §1):
|
|
79
|
+
|
|
80
|
+
* ``assignee`` / ``TaskResource`` — the publisher's teammates are not the
|
|
81
|
+
adopter's. A user id from another workspace is unresolvable, or resolvable to
|
|
82
|
+
the wrong person.
|
|
83
|
+
* ``planned_start`` / ``actual_*`` / ``early_*`` / ``baseline_*`` — a template
|
|
84
|
+
applied in November must not schedule to the publisher's March. Dates come
|
|
85
|
+
from the adopter's Start sheet and calendar, computed at apply time.
|
|
86
|
+
* ``percent_complete`` / ``status`` — progress on work nobody has done.
|
|
87
|
+
* comments, files, time entries — somebody else's conversation and evidence,
|
|
88
|
+
re-attributed to work that has not happened.
|
|
89
|
+
|
|
90
|
+
``duration`` IS carried: it is an estimate of effort, a property of the shape,
|
|
91
|
+
not of when the shape ran.
|
|
92
|
+
"""
|
|
93
|
+
return {
|
|
94
|
+
"ref": str(task.id),
|
|
95
|
+
"name": task.name,
|
|
96
|
+
"wbs_path": str(task.wbs_path) if task.wbs_path else None,
|
|
97
|
+
"duration": task.duration,
|
|
98
|
+
"is_milestone": task.is_milestone,
|
|
99
|
+
"delivery_mode": task.delivery_mode,
|
|
100
|
+
"governance_class": task.governance_class,
|
|
101
|
+
"type": task.type,
|
|
102
|
+
"is_subtask": task.is_subtask,
|
|
103
|
+
"notes": task.notes or "",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def extract_structure(project: Project) -> dict[str, Any]:
|
|
108
|
+
"""Freeze ``project``'s shape into a template structure document.
|
|
109
|
+
|
|
110
|
+
Reads committed, non-deleted tasks in WBS order plus the dependency edges
|
|
111
|
+
between them. Edges are recorded in **ref space** (the source task ids used as
|
|
112
|
+
opaque labels), not in pk space — ``materialize_structure`` relabels them onto
|
|
113
|
+
the rows it creates, so the document never carries a foreign key into a project
|
|
114
|
+
the adopter cannot see.
|
|
115
|
+
|
|
116
|
+
Recurring tasks are excluded for the same reason CPM excludes them (ADR-0090):
|
|
117
|
+
they are calendar-driven parallel activities, not schedulable shape, and a
|
|
118
|
+
template that seeded them would seed a recurrence rule with no owner and no
|
|
119
|
+
anchor date.
|
|
120
|
+
"""
|
|
121
|
+
from trueppm_api.apps.projects.models import Dependency, Task
|
|
122
|
+
|
|
123
|
+
tasks = list(
|
|
124
|
+
Task.objects.filter(project=project, is_deleted=False, is_recurring=False).order_by(
|
|
125
|
+
"wbs_path", "name"
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
if len(tasks) > MAX_TEMPLATE_NODES:
|
|
129
|
+
raise TemplateStructureError(
|
|
130
|
+
f"Project has {len(tasks)} tasks; a template carries at most {MAX_TEMPLATE_NODES}."
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
task_ids = {t.id for t in tasks}
|
|
134
|
+
edges = [
|
|
135
|
+
{
|
|
136
|
+
"predecessor": str(dep.predecessor_id),
|
|
137
|
+
"successor": str(dep.successor_id),
|
|
138
|
+
"dep_type": dep.dep_type,
|
|
139
|
+
"lag": dep.lag,
|
|
140
|
+
}
|
|
141
|
+
# Both endpoints must be inside the extracted set. A cross-project edge has
|
|
142
|
+
# no meaning in a template — the other project is not coming along — so it is
|
|
143
|
+
# dropped rather than dangled.
|
|
144
|
+
for dep in Dependency.objects.filter(
|
|
145
|
+
predecessor__project=project, is_deleted=False
|
|
146
|
+
).select_related(None)
|
|
147
|
+
if dep.predecessor_id in task_ids and dep.successor_id in task_ids
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
carries = [CARRIES_STRUCTURE]
|
|
151
|
+
if edges:
|
|
152
|
+
carries.append(CARRIES_DEPENDENCIES)
|
|
153
|
+
if any(t.duration for t in tasks):
|
|
154
|
+
carries.append(CARRIES_DURATIONS)
|
|
155
|
+
if any(t.is_milestone for t in tasks):
|
|
156
|
+
carries.append(CARRIES_MILESTONES)
|
|
157
|
+
if any(t.delivery_mode for t in tasks):
|
|
158
|
+
carries.append(CARRIES_DELIVERY_MODES)
|
|
159
|
+
|
|
160
|
+
sprint_length = getattr(project, "sprint_length_days", None)
|
|
161
|
+
if sprint_length:
|
|
162
|
+
carries.append(CARRIES_SPRINT_LENGTH)
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
"version": STRUCTURE_VERSION,
|
|
166
|
+
"extracted_at": timezone.now().isoformat(),
|
|
167
|
+
"methodology": project.methodology,
|
|
168
|
+
"sprint_length_days": sprint_length,
|
|
169
|
+
"tasks": [_task_node(t) for t in tasks],
|
|
170
|
+
"dependencies": edges,
|
|
171
|
+
"carries": carries,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def summarize_structure(structure: dict[str, Any]) -> dict[str, Any]:
|
|
176
|
+
"""The counts a publisher is shown before, and inside, the publish form (#2909).
|
|
177
|
+
|
|
178
|
+
Derived from the frozen document rather than re-queried from the project, for
|
|
179
|
+
the same reason ``task_count`` is: the document is what ``apply`` will
|
|
180
|
+
actually write, so a number read off it can never disagree with the outcome.
|
|
181
|
+
Computing these in the client would let a collapsed or filtered Grid report a
|
|
182
|
+
wrong count in the one place people trust it.
|
|
183
|
+
|
|
184
|
+
A *phase* is a node with at least one structural descendant, mirroring
|
|
185
|
+
``task_is_phase`` — read here off ``wbs_path`` prefixes, since the document
|
|
186
|
+
carries paths and not parent links. A *gate* is a gated milestone: the
|
|
187
|
+
hold points a PMO is actually counting.
|
|
188
|
+
"""
|
|
189
|
+
tasks = [t for t in structure.get("tasks", []) if isinstance(t, dict)]
|
|
190
|
+
paths = [str(t.get("wbs_path") or "") for t in tasks]
|
|
191
|
+
|
|
192
|
+
phases = 0
|
|
193
|
+
for node in tasks:
|
|
194
|
+
path = str(node.get("wbs_path") or "")
|
|
195
|
+
if not path or node.get("is_subtask"):
|
|
196
|
+
continue
|
|
197
|
+
if any(other != path and other.startswith(f"{path}.") for other in paths):
|
|
198
|
+
phases += 1
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
"task_count": len(tasks),
|
|
202
|
+
"phase_count": phases,
|
|
203
|
+
"gate_count": sum(
|
|
204
|
+
1 for t in tasks if t.get("is_milestone") and t.get("governance_class") == "gated"
|
|
205
|
+
),
|
|
206
|
+
"milestone_count": sum(1 for t in tasks if t.get("is_milestone")),
|
|
207
|
+
"dependency_count": len(structure.get("dependencies") or []),
|
|
208
|
+
"methodology": structure.get("methodology"),
|
|
209
|
+
"carries": list(structure.get("carries") or []),
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _validate_structure_envelope(structure: Any) -> tuple[dict[str, Any], list[Any]]:
|
|
214
|
+
"""Check the document wrapper; return the narrowed document and its task list."""
|
|
215
|
+
if not isinstance(structure, dict):
|
|
216
|
+
raise TemplateStructureError("Structure must be an object.")
|
|
217
|
+
version = structure.get("version")
|
|
218
|
+
if version != STRUCTURE_VERSION:
|
|
219
|
+
# Refuse rather than guess: an unknown version means fields we have never
|
|
220
|
+
# seen, and materializing it would write rows nobody designed.
|
|
221
|
+
raise TemplateStructureError(
|
|
222
|
+
f"Unsupported structure version {version!r} (this server writes v{STRUCTURE_VERSION})."
|
|
223
|
+
)
|
|
224
|
+
tasks = structure.get("tasks")
|
|
225
|
+
if not isinstance(tasks, list):
|
|
226
|
+
raise TemplateStructureError("Structure has no task list.")
|
|
227
|
+
if len(tasks) > MAX_TEMPLATE_NODES:
|
|
228
|
+
raise TemplateStructureError(
|
|
229
|
+
f"Structure carries {len(tasks)} tasks; the ceiling is {MAX_TEMPLATE_NODES}."
|
|
230
|
+
)
|
|
231
|
+
return structure, tasks
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _validate_task_nodes(tasks: list[Any]) -> set[str]:
|
|
235
|
+
"""Check every task node and return the set of refs they declare."""
|
|
236
|
+
refs: set[str] = set()
|
|
237
|
+
for node in tasks:
|
|
238
|
+
if not isinstance(node, dict) or not node.get("name"):
|
|
239
|
+
raise TemplateStructureError("Every task node needs a name.")
|
|
240
|
+
ref = node.get("ref")
|
|
241
|
+
if not ref or ref in refs:
|
|
242
|
+
raise TemplateStructureError("Every task node needs a unique ref.")
|
|
243
|
+
refs.add(str(ref))
|
|
244
|
+
return refs
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _validate_dependency_edges(edges: Any, refs: set[str]) -> None:
|
|
248
|
+
"""Check every dependency edge resolves to a task node in the same document."""
|
|
249
|
+
for edge in edges or []:
|
|
250
|
+
if not isinstance(edge, dict):
|
|
251
|
+
raise TemplateStructureError("Every dependency must be an object.")
|
|
252
|
+
# A dangling edge would silently vanish at materialize time, so the
|
|
253
|
+
# template would apply "successfully" while quietly dropping the structure
|
|
254
|
+
# the adopter chose it for.
|
|
255
|
+
if str(edge.get("predecessor")) not in refs or str(edge.get("successor")) not in refs:
|
|
256
|
+
raise TemplateStructureError("Dependency references a task not in this template.")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def validate_structure(structure: Any) -> dict[str, Any]:
|
|
260
|
+
"""Check a structure document before it is turned into somebody's rows.
|
|
261
|
+
|
|
262
|
+
Run at publish **and again at apply**. The second run is not belt-and-braces:
|
|
263
|
+
``structure`` is a JSONB column, so a template row can be edited by any path
|
|
264
|
+
that reaches the database, and the apply job is the one that turns it into rows
|
|
265
|
+
in a project. Validating only on the way in would trust a value that a
|
|
266
|
+
different writer may have replaced.
|
|
267
|
+
"""
|
|
268
|
+
document, tasks = _validate_structure_envelope(structure)
|
|
269
|
+
refs = _validate_task_nodes(tasks)
|
|
270
|
+
_validate_dependency_edges(document.get("dependencies"), refs)
|
|
271
|
+
return document
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _build_template_task_row(
|
|
275
|
+
node: dict[str, Any],
|
|
276
|
+
*,
|
|
277
|
+
project: Project,
|
|
278
|
+
template: ProjectTemplate,
|
|
279
|
+
short_id: str,
|
|
280
|
+
seeded_at: Any,
|
|
281
|
+
wbs_offset: int = 0,
|
|
282
|
+
) -> Task:
|
|
283
|
+
"""Build one unsaved ``Task`` from a template node.
|
|
284
|
+
|
|
285
|
+
No dates are written: durations are carried and the schedule comes from the
|
|
286
|
+
adopter's calendar via the CPM pass the caller enqueues afterwards. Writing the
|
|
287
|
+
publisher's dates here is the defect ADR-0789 §1 exists to prevent.
|
|
288
|
+
"""
|
|
289
|
+
from trueppm_api.apps.projects.models import Task, TaskSource
|
|
290
|
+
|
|
291
|
+
row = Task(
|
|
292
|
+
project=project,
|
|
293
|
+
name=str(node["name"])[:512],
|
|
294
|
+
duration=int(node.get("duration") or 0),
|
|
295
|
+
is_milestone=bool(node.get("is_milestone")),
|
|
296
|
+
notes=str(node.get("notes") or ""),
|
|
297
|
+
short_id=short_id,
|
|
298
|
+
wbs_path=offset_wbs_path(node.get("wbs_path"), wbs_offset),
|
|
299
|
+
server_version=1,
|
|
300
|
+
)
|
|
301
|
+
for field in ("delivery_mode", "governance_class", "type"):
|
|
302
|
+
value = node.get(field)
|
|
303
|
+
if value:
|
|
304
|
+
setattr(row, field, value)
|
|
305
|
+
if node.get("is_subtask"):
|
|
306
|
+
row.is_subtask = True
|
|
307
|
+
# Seed provenance (ADR-0786, #2730). edited_at is deliberately left null:
|
|
308
|
+
# a machine wrote this row and nobody has touched it, which is exactly what
|
|
309
|
+
# makes it eligible for the "Delete untouched rows (N)" sweep — and what
|
|
310
|
+
# lets undo tell a seeded row from one somebody has since typed into.
|
|
311
|
+
row.source_kind = TaskSource.TEMPLATE
|
|
312
|
+
row.source_id = template.id
|
|
313
|
+
row.source_version = str(template.version)
|
|
314
|
+
row.seeded_at = seeded_at
|
|
315
|
+
return row
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def materialize_structure(
|
|
319
|
+
template: ProjectTemplate,
|
|
320
|
+
project: Project,
|
|
321
|
+
*,
|
|
322
|
+
applied_by: Any = None,
|
|
323
|
+
) -> MaterializeResult:
|
|
324
|
+
"""Write ``template``'s shape into ``project`` and return what was written.
|
|
325
|
+
|
|
326
|
+
Returns ids rather than objects because the caller stores them on the
|
|
327
|
+
``TemplateApplication`` row — they are the undo set, and undo must be exact. The
|
|
328
|
+
milestone/dependency counts travel alongside for the same reason (ADR-0799 §2):
|
|
329
|
+
the caller writes them straight into ``result_summary`` for the seed banner.
|
|
330
|
+
|
|
331
|
+
Three things this deliberately does NOT do:
|
|
332
|
+
|
|
333
|
+
* **No dates are written.** Durations are carried; the schedule comes from the
|
|
334
|
+
adopter's calendar via the CPM pass the caller enqueues afterwards. Writing
|
|
335
|
+
the publisher's dates here is the defect ADR-0789 §1 exists to prevent.
|
|
336
|
+
* **No `save()` per row.** ``bulk_create`` for the same reason the seed importer
|
|
337
|
+
uses it — but that bypasses ``VersionedModel.save()``, so ``server_version``
|
|
338
|
+
and the ``sync_seq`` delta cursor are stamped explicitly below. A row left at
|
|
339
|
+
``sync_seq = 0`` sits below every checkpoint and is invisible to every offline
|
|
340
|
+
client forever, and it fails silently.
|
|
341
|
+
* **No CPM.** The caller enqueues a recalculation through
|
|
342
|
+
``scheduling.services.enqueue_recalculate`` once the rows commit.
|
|
343
|
+
"""
|
|
344
|
+
from django.db.models import F
|
|
345
|
+
|
|
346
|
+
from trueppm_api.apps.projects.models import Dependency, bulk_create_tasks
|
|
347
|
+
from trueppm_api.apps.projects.models import (
|
|
348
|
+
Project as ProjectModel,
|
|
349
|
+
)
|
|
350
|
+
from trueppm_api.apps.sync.sequence import allocate_for_projects
|
|
351
|
+
|
|
352
|
+
structure = validate_structure(template.structure)
|
|
353
|
+
nodes = structure["tasks"]
|
|
354
|
+
if not nodes:
|
|
355
|
+
return MaterializeResult(task_ids=[], milestones_created=0, dependencies_created=0)
|
|
356
|
+
|
|
357
|
+
# Allocate a contiguous short_id block in one UPDATE, exactly as the MS Project
|
|
358
|
+
# importer does — per-row allocation would be N round-trips and the per-project
|
|
359
|
+
# unique constraint rejects the second blank one.
|
|
360
|
+
count = len(nodes)
|
|
361
|
+
ProjectModel.objects.filter(pk=project.pk).update(object_sequence=F("object_sequence") + count)
|
|
362
|
+
end_seq: int = ProjectModel.objects.values_list("object_sequence", flat=True).get(pk=project.pk)
|
|
363
|
+
start_seq = end_seq - count + 1
|
|
364
|
+
|
|
365
|
+
seeded_at = timezone.now()
|
|
366
|
+
# Second and later adoptions append past the rows already there rather than
|
|
367
|
+
# writing onto their paths (#3061). Zero for an empty project, so a first
|
|
368
|
+
# adoption keeps the document's paths verbatim.
|
|
369
|
+
wbs_offset = root_ordinal_offset(
|
|
370
|
+
project.pk, document_paths=[node.get("wbs_path") for node in nodes]
|
|
371
|
+
)
|
|
372
|
+
ref_to_task: dict[str, Task] = {}
|
|
373
|
+
rows: list[Task] = []
|
|
374
|
+
for i, node in enumerate(nodes):
|
|
375
|
+
row = _build_template_task_row(
|
|
376
|
+
node,
|
|
377
|
+
project=project,
|
|
378
|
+
template=template,
|
|
379
|
+
short_id=f"{start_seq + i:06X}"[-8:],
|
|
380
|
+
seeded_at=seeded_at,
|
|
381
|
+
wbs_offset=wbs_offset,
|
|
382
|
+
)
|
|
383
|
+
rows.append(row)
|
|
384
|
+
ref_to_task[str(node["ref"])] = row
|
|
385
|
+
|
|
386
|
+
# One cursor for the whole batch: the rows are created together and the delta
|
|
387
|
+
# filter is `> since`, which ``coalesce_sync_seq``'s contract explicitly permits.
|
|
388
|
+
seq = allocate_for_projects([str(project.pk)])
|
|
389
|
+
for row in rows:
|
|
390
|
+
row.sync_seq = seq
|
|
391
|
+
|
|
392
|
+
# `bulk_create_tasks`, not `Task.objects.bulk_create`: a template's phase rows
|
|
393
|
+
# have children the moment they land, and `bulk_create` never reaches the
|
|
394
|
+
# declaration that `Task.save()` paths run — so a materialized template used to
|
|
395
|
+
# arrive with every phase as `structure_role='work'` (#3030). With #2909's
|
|
396
|
+
# bundled starters this is a first-run path.
|
|
397
|
+
bulk_create_tasks(rows, batch_size=500)
|
|
398
|
+
|
|
399
|
+
# Relabel the ref-space edges onto the rows just created. A missing endpoint is
|
|
400
|
+
# impossible here — validate_structure rejects a dangling edge — but the lookup
|
|
401
|
+
# is guarded anyway so a malformed row can never raise mid-batch and abort an
|
|
402
|
+
# otherwise good application.
|
|
403
|
+
edges = [
|
|
404
|
+
Dependency(
|
|
405
|
+
predecessor=ref_to_task[str(e["predecessor"])],
|
|
406
|
+
successor=ref_to_task[str(e["successor"])],
|
|
407
|
+
dep_type=e.get("dep_type") or "FS",
|
|
408
|
+
lag=int(e.get("lag") or 0),
|
|
409
|
+
server_version=1,
|
|
410
|
+
sync_seq=seq,
|
|
411
|
+
)
|
|
412
|
+
for e in structure.get("dependencies") or []
|
|
413
|
+
if str(e.get("predecessor")) in ref_to_task and str(e.get("successor")) in ref_to_task
|
|
414
|
+
]
|
|
415
|
+
if edges:
|
|
416
|
+
Dependency.objects.bulk_create(edges, batch_size=500, ignore_conflicts=True)
|
|
417
|
+
|
|
418
|
+
return MaterializeResult(
|
|
419
|
+
task_ids=[row.id for row in rows],
|
|
420
|
+
milestones_created=sum(1 for row in rows if row.is_milestone),
|
|
421
|
+
dependencies_created=len(edges),
|
|
422
|
+
)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Signal receivers for the projects app.
|
|
2
|
+
|
|
3
|
+
Burndown UPSERT on task status change is the primary connection here. When a
|
|
4
|
+
task in an ACTIVE sprint changes status, today's ``SprintBurnSnapshot`` row
|
|
5
|
+
is recomputed and written via the unique ``(sprint, snapshot_date)`` index —
|
|
6
|
+
naturally idempotent under concurrent writes.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from django.db.models.signals import post_save
|
|
15
|
+
from django.dispatch import receiver
|
|
16
|
+
|
|
17
|
+
from trueppm_api.apps.projects.signals import task_status_changed
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@receiver(task_status_changed)
|
|
23
|
+
def _on_task_status_changed(
|
|
24
|
+
sender: Any,
|
|
25
|
+
task: Any,
|
|
26
|
+
old_status: str | None,
|
|
27
|
+
new_status: str,
|
|
28
|
+
**kwargs: Any,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Update today's burn snapshot when a sprint-tracked task moves columns.
|
|
31
|
+
|
|
32
|
+
No-op when the task is not in any sprint, or when its sprint is not in
|
|
33
|
+
ACTIVE state. A FAILED upsert is logged but never raised — burndown is a
|
|
34
|
+
secondary observation and must not block the primary status write.
|
|
35
|
+
"""
|
|
36
|
+
from trueppm_api.apps.projects.models import SprintState
|
|
37
|
+
from trueppm_api.apps.projects.seed.replay_ctx import is_seed_replay_active
|
|
38
|
+
from trueppm_api.apps.projects.services import upsert_burndown_for_sprint
|
|
39
|
+
|
|
40
|
+
# During seed event-replay (ADR-0114) the timeline drives backdated burndown
|
|
41
|
+
# per simulated day; the live receiver would instead stamp *today's* row and
|
|
42
|
+
# collapse the curve to a single point, so skip it.
|
|
43
|
+
if is_seed_replay_active():
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
sprint = getattr(task, "sprint", None)
|
|
47
|
+
if sprint is None or sprint.state != SprintState.ACTIVE:
|
|
48
|
+
return
|
|
49
|
+
try:
|
|
50
|
+
upsert_burndown_for_sprint(sprint)
|
|
51
|
+
except Exception:
|
|
52
|
+
logger.exception(
|
|
53
|
+
"task_status_changed: burndown upsert failed for sprint=%s task=%s",
|
|
54
|
+
sprint.pk,
|
|
55
|
+
task.pk,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _register_task_soft_delete_receiver() -> None:
|
|
60
|
+
"""Register the Task post_save receiver that frees RetroActionItem links.
|
|
61
|
+
|
|
62
|
+
Wrapped in a function so the import of Task (which would create an import
|
|
63
|
+
cycle if done at module top) happens after the models app is fully loaded.
|
|
64
|
+
Called from ``ProjectsConfig.ready()``.
|
|
65
|
+
"""
|
|
66
|
+
from trueppm_api.apps.projects.models import Task
|
|
67
|
+
|
|
68
|
+
@receiver(post_save, sender=Task)
|
|
69
|
+
def _on_task_save_reset_promoted(
|
|
70
|
+
sender: Any,
|
|
71
|
+
instance: Any,
|
|
72
|
+
created: bool,
|
|
73
|
+
**kwargs: Any,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Reset RetroActionItem.promoted_task_id when its Task is soft-deleted.
|
|
76
|
+
|
|
77
|
+
ADR-0071 §2 rollback: when the promoted Task is soft-deleted, the
|
|
78
|
+
originating action item must become re-promotable. VersionedModel
|
|
79
|
+
soft_delete sets is_deleted=True and saves; this receiver detects
|
|
80
|
+
that transition.
|
|
81
|
+
|
|
82
|
+
No-op for INSERT and for any save where is_deleted=False — the common
|
|
83
|
+
case of a normal Task update.
|
|
84
|
+
"""
|
|
85
|
+
if created or not getattr(instance, "is_deleted", False):
|
|
86
|
+
return
|
|
87
|
+
from trueppm_api.apps.projects.retro_services import (
|
|
88
|
+
reset_promoted_task_on_delete,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
reset_promoted_task_on_delete(str(instance.pk))
|
|
93
|
+
except Exception:
|
|
94
|
+
logger.exception(
|
|
95
|
+
"task soft-delete: failed to reset promoted_task_id for task=%s",
|
|
96
|
+
instance.pk,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _register_program_rollup_seed_receiver() -> None:
|
|
101
|
+
"""Register the Program post_save receiver that seeds rollup config on create.
|
|
102
|
+
|
|
103
|
+
ADR-0169 / #527: a newly-created Program gets methodology-aware default
|
|
104
|
+
rollup config (which KPIs roll up, which aggregation policy to use). The
|
|
105
|
+
backfill of existing rows happens in migration 0041; this receiver covers
|
|
106
|
+
every Program created after the migration.
|
|
107
|
+
|
|
108
|
+
Idempotent: only fires when ``created=True`` AND the config is still empty
|
|
109
|
+
(the migration-default ``[]``). Re-saves of an existing Program never
|
|
110
|
+
rewrite user-customized values.
|
|
111
|
+
"""
|
|
112
|
+
from trueppm_api.apps.projects.models import Program
|
|
113
|
+
|
|
114
|
+
@receiver(post_save, sender=Program)
|
|
115
|
+
def _on_program_create_seed_rollup(
|
|
116
|
+
sender: Any,
|
|
117
|
+
instance: Any,
|
|
118
|
+
created: bool,
|
|
119
|
+
**kwargs: Any,
|
|
120
|
+
) -> None:
|
|
121
|
+
if not created:
|
|
122
|
+
return
|
|
123
|
+
if instance.rollup_enabled_kpis:
|
|
124
|
+
# Already seeded — service-layer create may pre-populate before
|
|
125
|
+
# save() in a future change; respect any value already present.
|
|
126
|
+
return
|
|
127
|
+
from trueppm_api.apps.projects.services import rollup_config_defaults
|
|
128
|
+
|
|
129
|
+
enabled, policy = rollup_config_defaults(instance.methodology)
|
|
130
|
+
# update() skips this signal (no save() recursion) and avoids bumping
|
|
131
|
+
# server_version a second time on the same logical create.
|
|
132
|
+
Program.objects.filter(pk=instance.pk).update(
|
|
133
|
+
rollup_enabled_kpis=enabled,
|
|
134
|
+
rollup_aggregation_policy=policy,
|
|
135
|
+
)
|
|
136
|
+
instance.rollup_enabled_kpis = enabled
|
|
137
|
+
instance.rollup_aggregation_policy = policy
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _register_milestone_rollup_receiver() -> None:
|
|
141
|
+
"""Register the Task post_save receiver that fires the milestone rollup.
|
|
142
|
+
|
|
143
|
+
ADR-0074: when a task assigned to a sprint changes (status, story points,
|
|
144
|
+
sprint membership), and that sprint targets a milestone, recompute the
|
|
145
|
+
milestone rollup live. This is the "instant Gantt update" path — the
|
|
146
|
+
authoritative recompute on sprint close still runs inside the drain.
|
|
147
|
+
|
|
148
|
+
Bails out early for the common case (no sprint, no target milestone) so
|
|
149
|
+
the receiver adds negligible overhead to non-sprint task writes. Errors
|
|
150
|
+
are logged and swallowed; the rollup is a cosmetic side effect and must
|
|
151
|
+
never block a primary task write.
|
|
152
|
+
|
|
153
|
+
Wrapped in a function for the same import-timing reason as the soft-delete
|
|
154
|
+
receiver above. Called from ``ProjectsConfig.ready()``.
|
|
155
|
+
"""
|
|
156
|
+
from trueppm_api.apps.projects.models import Task
|
|
157
|
+
|
|
158
|
+
@receiver(post_save, sender=Task, dispatch_uid="milestone_rollup_on_task_save")
|
|
159
|
+
def _on_task_save_recompute_rollup(
|
|
160
|
+
sender: Any,
|
|
161
|
+
instance: Any,
|
|
162
|
+
created: bool,
|
|
163
|
+
**kwargs: Any,
|
|
164
|
+
) -> None:
|
|
165
|
+
sprint_id = getattr(instance, "sprint_id", None)
|
|
166
|
+
if sprint_id is None or getattr(instance, "is_deleted", False):
|
|
167
|
+
return
|
|
168
|
+
from trueppm_api.apps.projects.seed.replay_ctx import is_seed_replay_active
|
|
169
|
+
|
|
170
|
+
# During seed event-replay (ADR-0114) the rollup is computed-on-read and
|
|
171
|
+
# the post-import CPM pass refreshes it; firing here per backdated beat
|
|
172
|
+
# would queue redundant milestone_rollup_updated broadcasts on commit.
|
|
173
|
+
if is_seed_replay_active():
|
|
174
|
+
return
|
|
175
|
+
from trueppm_api.apps.projects.models import Sprint
|
|
176
|
+
|
|
177
|
+
# One small query — index hit on PK. The cheap filter is paying for
|
|
178
|
+
# itself by short-circuiting the common "task not linked to a
|
|
179
|
+
# sprint-with-milestone" case before any rollup work.
|
|
180
|
+
milestone_id = (
|
|
181
|
+
Sprint.objects.filter(pk=sprint_id, is_deleted=False)
|
|
182
|
+
.values_list("target_milestone_id", flat=True)
|
|
183
|
+
.first()
|
|
184
|
+
)
|
|
185
|
+
if milestone_id is None:
|
|
186
|
+
return
|
|
187
|
+
from trueppm_api.apps.projects.services import recompute_milestone_rollup
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
recompute_milestone_rollup(milestone_id)
|
|
191
|
+
except Exception:
|
|
192
|
+
logger.exception(
|
|
193
|
+
"milestone rollup: recompute failed for task=%s milestone=%s",
|
|
194
|
+
instance.pk,
|
|
195
|
+
milestone_id,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _register_backlog_pull_rollback_receiver() -> None:
|
|
200
|
+
"""Register the Task post_save receiver that rolls back a pulled BacklogItem.
|
|
201
|
+
|
|
202
|
+
ADR-0069 rollback: when the Task created by a backlog pull is soft-deleted,
|
|
203
|
+
the originating BacklogItem must become re-pullable. TruePPM Tasks are
|
|
204
|
+
soft-deleted (VersionedModel sets is_deleted=True and saves), so this hooks
|
|
205
|
+
post_save and reacts to the is_deleted transition — mirroring the existing
|
|
206
|
+
RetroActionItem soft-delete receiver above. ``pulled_task`` is SET_NULL, so
|
|
207
|
+
deletion never cascades to the item; this restores its PROPOSED status.
|
|
208
|
+
|
|
209
|
+
No-op for INSERT and for any save where is_deleted is False (the common
|
|
210
|
+
Task-update case). Wrapped in a function for the import-timing reason noted
|
|
211
|
+
on the other receivers. Called from ``ProjectsConfig.ready()``.
|
|
212
|
+
"""
|
|
213
|
+
from trueppm_api.apps.projects.models import Task
|
|
214
|
+
|
|
215
|
+
@receiver(post_save, sender=Task, dispatch_uid="backlog_pull_rollback_on_task_save")
|
|
216
|
+
def _on_task_soft_delete_reset_backlog_item(
|
|
217
|
+
sender: Any,
|
|
218
|
+
instance: Any,
|
|
219
|
+
created: bool,
|
|
220
|
+
**kwargs: Any,
|
|
221
|
+
) -> None:
|
|
222
|
+
if created or not getattr(instance, "is_deleted", False):
|
|
223
|
+
return
|
|
224
|
+
from trueppm_api.apps.projects.backlog_services import (
|
|
225
|
+
reset_pulled_item_on_task_delete,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
reset_pulled_item_on_task_delete(str(instance.pk))
|
|
230
|
+
except Exception:
|
|
231
|
+
logger.exception(
|
|
232
|
+
"backlog rollback: failed to reset BacklogItem for deleted task=%s",
|
|
233
|
+
instance.pk,
|
|
234
|
+
)
|