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,555 @@
|
|
|
1
|
+
"""Inbound task-sync upsert logic (ADR-0068 / issue #500).
|
|
2
|
+
|
|
3
|
+
Kept in its own module to keep ``views.py`` focused on routing. The single
|
|
4
|
+
public entry point is :func:`upsert_inbound_task`, called from
|
|
5
|
+
``TaskSyncView.post`` after authentication, throttling, and IDOR validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from django.contrib.auth import get_user_model
|
|
15
|
+
from django.db import transaction
|
|
16
|
+
from django.db.models import F
|
|
17
|
+
from simple_history.utils import get_history_manager_for_model
|
|
18
|
+
|
|
19
|
+
from trueppm_api.apps.projects.models import (
|
|
20
|
+
ApiTokenAuditAction,
|
|
21
|
+
ApiTokenAuditEntry,
|
|
22
|
+
InboundTaskLink,
|
|
23
|
+
ProjectApiToken,
|
|
24
|
+
Task,
|
|
25
|
+
TaskStatus,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from trueppm_api.apps.projects.models import Project
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
# Default status_map used when the token has no override. Lower-case keys
|
|
34
|
+
# match the canonical Jira/Linear/GitHub status vocabulary; values are
|
|
35
|
+
# TaskStatus.value strings. Documented in ADR-0068 §1.
|
|
36
|
+
DEFAULT_STATUS_MAP: dict[str, str] = {
|
|
37
|
+
"todo": TaskStatus.NOT_STARTED.value,
|
|
38
|
+
"to do": TaskStatus.NOT_STARTED.value,
|
|
39
|
+
"open": TaskStatus.NOT_STARTED.value,
|
|
40
|
+
"backlog": TaskStatus.BACKLOG.value,
|
|
41
|
+
"in_progress": TaskStatus.IN_PROGRESS.value,
|
|
42
|
+
"in progress": TaskStatus.IN_PROGRESS.value,
|
|
43
|
+
"doing": TaskStatus.IN_PROGRESS.value,
|
|
44
|
+
"review": TaskStatus.REVIEW.value,
|
|
45
|
+
"in_review": TaskStatus.REVIEW.value,
|
|
46
|
+
"in review": TaskStatus.REVIEW.value,
|
|
47
|
+
"done": TaskStatus.COMPLETE.value,
|
|
48
|
+
"closed": TaskStatus.COMPLETE.value,
|
|
49
|
+
"complete": TaskStatus.COMPLETE.value,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class UpsertResult:
|
|
55
|
+
task: Task
|
|
56
|
+
link: InboundTaskLink
|
|
57
|
+
created: bool
|
|
58
|
+
assignee_resolved: bool
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class _Resolved:
|
|
63
|
+
"""One inbound payload, normalized and resolved against the project.
|
|
64
|
+
|
|
65
|
+
Everything here is derived before the ``select_for_update()`` row lock is
|
|
66
|
+
taken, so the create and update branches operate on identical values and
|
|
67
|
+
neither needs the raw payload. ``pending_email`` is the assignee address
|
|
68
|
+
that did *not* resolve to a project member — kept on the link row so a later
|
|
69
|
+
push (or an invite acceptance) can complete the assignment.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
source: str
|
|
73
|
+
external_id: str
|
|
74
|
+
name: str
|
|
75
|
+
description: str
|
|
76
|
+
story_points: int | None
|
|
77
|
+
external_url: str | None
|
|
78
|
+
parent_external_id: str | None
|
|
79
|
+
assignee_email: str | None
|
|
80
|
+
status: str
|
|
81
|
+
assignee: Any | None
|
|
82
|
+
pending_email: str | None
|
|
83
|
+
parent_wbs: str | None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _resolve_payload(
|
|
87
|
+
project: Project, token: ProjectApiToken, payload: dict[str, Any]
|
|
88
|
+
) -> _Resolved:
|
|
89
|
+
"""Normalize the payload and resolve status, assignee, and parent WBS."""
|
|
90
|
+
source = payload["source"]
|
|
91
|
+
external_id = payload["external_id"]
|
|
92
|
+
parent_external_id = payload.get("parent_external_id") or None
|
|
93
|
+
assignee_email = payload.get("assignee_email") or None
|
|
94
|
+
assignee_user, pending_email = _resolve_assignee(assignee_email, project)
|
|
95
|
+
return _Resolved(
|
|
96
|
+
source=source,
|
|
97
|
+
external_id=external_id,
|
|
98
|
+
name=payload.get("name") or external_id, # name is recommended but not required
|
|
99
|
+
description=payload.get("description") or "",
|
|
100
|
+
story_points=payload.get("story_points"),
|
|
101
|
+
external_url=payload.get("external_url") or None,
|
|
102
|
+
parent_external_id=parent_external_id,
|
|
103
|
+
assignee_email=assignee_email,
|
|
104
|
+
status=_resolve_status(payload.get("status"), token),
|
|
105
|
+
assignee=assignee_user,
|
|
106
|
+
pending_email=pending_email,
|
|
107
|
+
parent_wbs=_resolve_parent_wbs(project, source, parent_external_id),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _allocate_wbs(project: Project, parent_wbs: str | None) -> tuple[str | None, bool]:
|
|
112
|
+
"""Pick the WBS path for a newly-created inbound task.
|
|
113
|
+
|
|
114
|
+
We don't reuse ``Task.save()``'s WBS logic because inbound tasks always land
|
|
115
|
+
in the project's flat backlog area — they aren't part of the PM's planned WBS
|
|
116
|
+
structure unless they have a resolved parent.
|
|
117
|
+
"""
|
|
118
|
+
if parent_wbs is None:
|
|
119
|
+
# Let Task.save() leave WBS unset; backlog tasks don't need a path.
|
|
120
|
+
return None, False
|
|
121
|
+
|
|
122
|
+
# Count direct children of the parent (one segment deeper, e.g. "1.2.X"
|
|
123
|
+
# for parent "1.2") via a regex that matches the exact depth. The
|
|
124
|
+
# earlier startswith+exclude version was broken — `wbs_path__contains="."`
|
|
125
|
+
# was true for every multi-segment path, so it never excluded anything.
|
|
126
|
+
# Scope is project_id+regex; the project_id filter is indexed so the
|
|
127
|
+
# regex runs only over this project's tasks.
|
|
128
|
+
import re
|
|
129
|
+
|
|
130
|
+
child_pattern = rf"^{re.escape(parent_wbs)}\.[^.]+$"
|
|
131
|
+
child_count = Task.objects.filter(
|
|
132
|
+
project=project,
|
|
133
|
+
wbs_path__regex=child_pattern,
|
|
134
|
+
is_deleted=False,
|
|
135
|
+
).count()
|
|
136
|
+
return f"{parent_wbs}.{child_count + 1}", True
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _create_inbound_task(
|
|
140
|
+
project: Project, token: ProjectApiToken, resolved: _Resolved
|
|
141
|
+
) -> tuple[Task, InboundTaskLink]:
|
|
142
|
+
"""Create the Task and its link row for a first-time external id."""
|
|
143
|
+
wbs_path, is_subtask = _allocate_wbs(project, resolved.parent_wbs)
|
|
144
|
+
|
|
145
|
+
# Hybrid-by-construction note (#1665): this agent write path creates only
|
|
146
|
+
# Tasks — never Dependency edges — so there is no dependency graph to
|
|
147
|
+
# validate here and the shared cycle/self-reference guard
|
|
148
|
+
# (scheduling.graph_guard.validate_task_graph) is vacuous. If inbound sync
|
|
149
|
+
# ever grows dependency writes, route the proposed edges through that guard
|
|
150
|
+
# before persisting, exactly as the offline importer (#1664) does, so an
|
|
151
|
+
# agent principal is governed identically to the human write path.
|
|
152
|
+
task = Task.objects.create(
|
|
153
|
+
project=project,
|
|
154
|
+
name=resolved.name[:512],
|
|
155
|
+
assignee=resolved.assignee,
|
|
156
|
+
status=resolved.status,
|
|
157
|
+
story_points=resolved.story_points,
|
|
158
|
+
wbs_path=wbs_path,
|
|
159
|
+
is_subtask=is_subtask,
|
|
160
|
+
notes=resolved.description,
|
|
161
|
+
)
|
|
162
|
+
link = InboundTaskLink.objects.create(
|
|
163
|
+
project=project,
|
|
164
|
+
task=task,
|
|
165
|
+
source=resolved.source,
|
|
166
|
+
external_id=resolved.external_id,
|
|
167
|
+
external_url=resolved.external_url,
|
|
168
|
+
parent_external_id=resolved.parent_external_id,
|
|
169
|
+
pending_assignee_email=resolved.pending_email,
|
|
170
|
+
created_via_token=token,
|
|
171
|
+
last_synced_via_token=token,
|
|
172
|
+
)
|
|
173
|
+
return task, link
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _status_transition_fields(
|
|
177
|
+
task: Task, old_status: str, new_status: str, story_points: int | None
|
|
178
|
+
) -> dict[str, Any]:
|
|
179
|
+
"""Re-apply the actual-date / percent coercions the bulk UPDATE bypasses.
|
|
180
|
+
|
|
181
|
+
``Task.save()`` and ``TaskSerializer.update()`` perform these on the REST
|
|
182
|
+
path; the write-through ``.update()`` below skips both (#1767). Without them,
|
|
183
|
+
a task marked "Done" by an external system lands in the Done column at 0%
|
|
184
|
+
with no actual dates — EVM/burndown under-count it.
|
|
185
|
+
|
|
186
|
+
REVIEW mirrors ``TaskSerializer._apply_transition_actuals`` (ADR-1153,
|
|
187
|
+
#3709): before this, the same transition recorded a finish over REST and
|
|
188
|
+
none over inbound sync, so a task an external system moved to "In Review"
|
|
189
|
+
took a full-duration planning position in CPM instead of its real pin.
|
|
190
|
+
"""
|
|
191
|
+
from django.utils import timezone as _tz
|
|
192
|
+
|
|
193
|
+
today = _tz.localdate()
|
|
194
|
+
fields: dict[str, Any] = {"status": new_status, "status_changed_at": _tz.now()}
|
|
195
|
+
|
|
196
|
+
# Reopening out of a sign-off state clears the finish stamp unless the
|
|
197
|
+
# destination is still a sign-off state — REVIEW joined COMPLETE here in
|
|
198
|
+
# ADR-1153 (decision 4), mirroring ``_apply_transition_actuals``'
|
|
199
|
+
# leaving_signoff/entering_signoff pair: stamping a finish on the way into
|
|
200
|
+
# REVIEW below without this would strand a stale ``actual_finish`` on a
|
|
201
|
+
# REVIEW → IN_PROGRESS reopen, and COMPLETE ⇄ REVIEW must keep the recorded
|
|
202
|
+
# finish rather than clearing and re-stamping it.
|
|
203
|
+
leaving_signoff = old_status in (TaskStatus.COMPLETE.value, TaskStatus.REVIEW.value)
|
|
204
|
+
entering_signoff = new_status in (TaskStatus.COMPLETE.value, TaskStatus.REVIEW.value)
|
|
205
|
+
if leaving_signoff and not entering_signoff:
|
|
206
|
+
fields["actual_finish"] = None
|
|
207
|
+
|
|
208
|
+
# Remaining-effort restoration on reopen stays COMPLETE-only, mirroring
|
|
209
|
+
# TaskSerializer._apply_status_transition_side_effects — untouched by
|
|
210
|
+
# ADR-1153, which only widened the actual_finish clear above. Uses the
|
|
211
|
+
# story_points being written on this push (falls back to the stored value)
|
|
212
|
+
# so burndown counts the reopened work again.
|
|
213
|
+
if old_status == TaskStatus.COMPLETE.value and new_status != TaskStatus.COMPLETE.value:
|
|
214
|
+
fields["remaining_points"] = story_points if story_points is not None else task.story_points
|
|
215
|
+
|
|
216
|
+
if new_status == TaskStatus.IN_PROGRESS.value and task.actual_start is None:
|
|
217
|
+
fields["actual_start"] = today
|
|
218
|
+
elif new_status == TaskStatus.REVIEW.value:
|
|
219
|
+
# Guarded by ``task.actual_finish is None`` rather than the serializer's
|
|
220
|
+
# bare "not explicitly provided this write" check: this write-through
|
|
221
|
+
# path has no partial-payload concept, so "already recorded" is the
|
|
222
|
+
# only signal available to avoid clobbering a real date — the same rule
|
|
223
|
+
# the COMPLETE branch below already applies.
|
|
224
|
+
if task.actual_finish is None:
|
|
225
|
+
fields["actual_finish"] = today
|
|
226
|
+
elif new_status == TaskStatus.COMPLETE.value:
|
|
227
|
+
if task.actual_finish is None:
|
|
228
|
+
fields["actual_finish"] = today
|
|
229
|
+
fields["remaining_points"] = 0
|
|
230
|
+
if new_status in (TaskStatus.REVIEW.value, TaskStatus.COMPLETE.value):
|
|
231
|
+
fields["percent_complete"] = 100.0
|
|
232
|
+
return fields
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _update_inbound_task(
|
|
236
|
+
link: InboundTaskLink, token: ProjectApiToken, resolved: _Resolved
|
|
237
|
+
) -> Task:
|
|
238
|
+
"""Write through the mutable fields onto an already-linked task.
|
|
239
|
+
|
|
240
|
+
Uses a bulk UPDATE so we increment ``server_version`` exactly once and avoid
|
|
241
|
+
the ``status_changed_at`` side effect (``Task.save()`` resets it on a status
|
|
242
|
+
transition; we want "when status flipped in the external system", not "when
|
|
243
|
+
the webhook arrived").
|
|
244
|
+
"""
|
|
245
|
+
task = link.task
|
|
246
|
+
old_status = task.status
|
|
247
|
+
new_status = resolved.status
|
|
248
|
+
status_changed = old_status != new_status
|
|
249
|
+
|
|
250
|
+
# The bulk UPDATE bypasses VersionedModel.save(), so the sync delta cursor
|
|
251
|
+
# must be drawn explicitly — otherwise this write moves server_version but
|
|
252
|
+
# not sync_seq, and the row stays below the project's checkpoint and is never
|
|
253
|
+
# delivered to an offline client (ADR-0686).
|
|
254
|
+
from trueppm_api.apps.sync.sequence import allocate_for_projects
|
|
255
|
+
|
|
256
|
+
update_fields: dict[str, Any] = {
|
|
257
|
+
"name": resolved.name[:512],
|
|
258
|
+
"notes": resolved.description,
|
|
259
|
+
"story_points": resolved.story_points,
|
|
260
|
+
"server_version": F("server_version") + 1,
|
|
261
|
+
"sync_seq": allocate_for_projects([task.project_id]),
|
|
262
|
+
}
|
|
263
|
+
# Only set assignee on resolve — never overwrite an existing assignment.
|
|
264
|
+
if task.assignee_id is None and resolved.assignee is not None:
|
|
265
|
+
update_fields["assignee"] = resolved.assignee
|
|
266
|
+
if status_changed:
|
|
267
|
+
update_fields.update(
|
|
268
|
+
_status_transition_fields(task, old_status, new_status, resolved.story_points)
|
|
269
|
+
)
|
|
270
|
+
Task.objects.filter(pk=task.pk).update(**update_fields)
|
|
271
|
+
# Refresh task to get post-update field values for downstream callers.
|
|
272
|
+
task.refresh_from_db()
|
|
273
|
+
|
|
274
|
+
# The bulk .update() above is deliberate (single server_version bump,
|
|
275
|
+
# externally-supplied status_changed_at preserved — see the write-through
|
|
276
|
+
# docstring), but it also bypasses django-simple-history's post_save
|
|
277
|
+
# receiver, so without this no HistoricalTask row is written and
|
|
278
|
+
# external-system/agent edits are invisible in every activity/history
|
|
279
|
+
# surface (#1876). Record the post-update state explicitly through
|
|
280
|
+
# simple_history's own bulk machinery (the same path
|
|
281
|
+
# bulk_update_with_history uses): it respects the model's
|
|
282
|
+
# excluded_fields and only INSERTs a history row — the task row itself
|
|
283
|
+
# is untouched, so server_version is not double-bumped and
|
|
284
|
+
# status_changed_at keeps its .update() semantics. history_user resolves
|
|
285
|
+
# through simple_history's middleware context to request.user, which
|
|
286
|
+
# ProjectApiTokenAuthentication sets to token.created_by — identical to
|
|
287
|
+
# the attribution the create branch's post_save "+" row already gets
|
|
288
|
+
# (and None outside a request). The change reason carries the external
|
|
289
|
+
# source tag so the feed can tell an inbound edit from a direct one.
|
|
290
|
+
get_history_manager_for_model(Task).bulk_history_create(
|
|
291
|
+
[task],
|
|
292
|
+
update=True,
|
|
293
|
+
default_change_reason=f"inbound sync: {resolved.source}",
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if status_changed:
|
|
297
|
+
# task_status_changed is the OSS extension point Task.save() emits on a
|
|
298
|
+
# status transition — it drives real-time burndown upserts today and is
|
|
299
|
+
# the documented hook other OSS/Enterprise receivers register against.
|
|
300
|
+
# The bulk .update() above skips it, so fire it explicitly here (#1767).
|
|
301
|
+
# send_robust: a raising third-party/Enterprise receiver must never
|
|
302
|
+
# propagate out of and break this OSS write path (mirrors Task.save()).
|
|
303
|
+
from trueppm_api.apps.projects.signals import task_status_changed
|
|
304
|
+
from trueppm_api.core.extension_signals import dispatch_extension_signal
|
|
305
|
+
|
|
306
|
+
dispatch_extension_signal(
|
|
307
|
+
task_status_changed,
|
|
308
|
+
sender=Task,
|
|
309
|
+
task=task,
|
|
310
|
+
old_status=old_status,
|
|
311
|
+
new_status=new_status,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Link row updates: refresh URL + last_synced_via_token, clear
|
|
315
|
+
# pending_assignee_email if we just resolved it.
|
|
316
|
+
link.external_url = resolved.external_url
|
|
317
|
+
link.last_synced_via_token = token
|
|
318
|
+
if resolved.assignee is not None and link.pending_assignee_email == resolved.assignee_email:
|
|
319
|
+
link.pending_assignee_email = None
|
|
320
|
+
link.save()
|
|
321
|
+
return task
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _defer_side_effects(
|
|
325
|
+
project: Project, task: Task, resolved: _Resolved, *, created: bool
|
|
326
|
+
) -> None:
|
|
327
|
+
"""Queue the post-commit CPM recalc, board broadcast, and webhook dispatch."""
|
|
328
|
+
from trueppm_api.apps.scheduling.services import (
|
|
329
|
+
enqueue_recalculate as _enqueue_recalculate,
|
|
330
|
+
)
|
|
331
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
332
|
+
from trueppm_api.apps.webhooks.dispatch import dispatch_webhooks
|
|
333
|
+
|
|
334
|
+
project_id = str(project.pk)
|
|
335
|
+
task_id = str(task.pk)
|
|
336
|
+
event_type = "task_created" if created else "task_updated"
|
|
337
|
+
webhook_event = "task.created" if created else "task.updated"
|
|
338
|
+
webhook_payload = {
|
|
339
|
+
"id": task_id,
|
|
340
|
+
"short_id": task.short_id,
|
|
341
|
+
"name": task.name,
|
|
342
|
+
"status": task.status,
|
|
343
|
+
"source": resolved.source, # X-Source semantics per ADR-0065 addendum
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
# Bind each lambda's captured values via default arguments so closure
|
|
347
|
+
# late-binding can never substitute a different value if this function
|
|
348
|
+
# is extended later to register multiple branches per call.
|
|
349
|
+
def _recalc(pid: str = project_id) -> None:
|
|
350
|
+
_enqueue_recalculate(pid)
|
|
351
|
+
|
|
352
|
+
def _broadcast(
|
|
353
|
+
pid: str = project_id, et: str = event_type, tid: str = task_id, src: str = resolved.source
|
|
354
|
+
) -> None:
|
|
355
|
+
broadcast_board_event(pid, et, {"id": tid, "source": src})
|
|
356
|
+
|
|
357
|
+
def _dispatch(
|
|
358
|
+
pid: str = project_id,
|
|
359
|
+
we: str = webhook_event,
|
|
360
|
+
wp: dict[str, Any] = webhook_payload,
|
|
361
|
+
) -> None:
|
|
362
|
+
dispatch_webhooks(pid, we, wp)
|
|
363
|
+
|
|
364
|
+
transaction.on_commit(_recalc)
|
|
365
|
+
transaction.on_commit(_broadcast)
|
|
366
|
+
transaction.on_commit(_dispatch)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _resolve_status(payload_status: str | None, token: ProjectApiToken) -> str:
|
|
370
|
+
"""Translate the external status string into a TaskStatus value.
|
|
371
|
+
|
|
372
|
+
Lookup order: token.status_map (case-insensitive) → DEFAULT_STATUS_MAP
|
|
373
|
+
(case-insensitive) → BACKLOG fallback. Empty/missing input returns
|
|
374
|
+
BACKLOG so a payload with no status field lands in the backlog rather
|
|
375
|
+
than silently becoming NOT_STARTED (which would imply a PM commitment).
|
|
376
|
+
"""
|
|
377
|
+
if not payload_status:
|
|
378
|
+
return TaskStatus.BACKLOG.value
|
|
379
|
+
key = payload_status.strip().lower()
|
|
380
|
+
# Token overrides may use their own casing; normalize on lookup.
|
|
381
|
+
override = {k.lower(): v for k, v in (token.status_map or {}).items()}
|
|
382
|
+
if key in override:
|
|
383
|
+
candidate = override[key]
|
|
384
|
+
elif key in DEFAULT_STATUS_MAP:
|
|
385
|
+
candidate = DEFAULT_STATUS_MAP[key]
|
|
386
|
+
else:
|
|
387
|
+
candidate = TaskStatus.BACKLOG.value
|
|
388
|
+
# Defensive: if a token's status_map is misconfigured with an invalid
|
|
389
|
+
# TaskStatus, fall back to BACKLOG rather than raise — the inbound caller
|
|
390
|
+
# should not be punished for a token-config bug they can't see.
|
|
391
|
+
if candidate not in TaskStatus.values:
|
|
392
|
+
# No secret logged: token.token_prefix is the short non-secret identifier
|
|
393
|
+
# prefix (the full token value is never read here), plus status strings.
|
|
394
|
+
# nosemgrep: python-logger-credential-disclosure
|
|
395
|
+
logger.warning(
|
|
396
|
+
"inbound_sync: token %s mapped status %r to invalid TaskStatus %r",
|
|
397
|
+
token.token_prefix,
|
|
398
|
+
payload_status,
|
|
399
|
+
candidate,
|
|
400
|
+
)
|
|
401
|
+
return str(TaskStatus.BACKLOG.value)
|
|
402
|
+
return str(candidate)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _resolve_assignee(
|
|
406
|
+
assignee_email: str | None, project: Project
|
|
407
|
+
) -> tuple[Any | None, str | None]:
|
|
408
|
+
"""Resolve an email against project membership.
|
|
409
|
+
|
|
410
|
+
Returns ``(user, None)`` if the email matches a member, else
|
|
411
|
+
``(None, pending_email)`` so the link row carries the unresolved email
|
|
412
|
+
for later resolution. Empty/missing input returns ``(None, None)``.
|
|
413
|
+
"""
|
|
414
|
+
if not assignee_email:
|
|
415
|
+
return (None, None)
|
|
416
|
+
User = get_user_model()
|
|
417
|
+
# Lookup by email (case-insensitive) and confirm the user is a non-soft-deleted
|
|
418
|
+
# member. Avoids leaking project membership across emails — only matches if
|
|
419
|
+
# the email belongs to a current member.
|
|
420
|
+
user = (
|
|
421
|
+
User.objects.filter(email__iexact=assignee_email)
|
|
422
|
+
.filter(
|
|
423
|
+
memberships__project=project,
|
|
424
|
+
memberships__is_deleted=False,
|
|
425
|
+
)
|
|
426
|
+
.first()
|
|
427
|
+
)
|
|
428
|
+
if user is not None:
|
|
429
|
+
return (user, None)
|
|
430
|
+
return (None, assignee_email)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _resolve_parent_wbs(
|
|
434
|
+
project: Project, source: str, parent_external_id: str | None
|
|
435
|
+
) -> str | None:
|
|
436
|
+
"""Find the WBS path of a parent task from its external id.
|
|
437
|
+
|
|
438
|
+
Match is scoped to the same (project, source) so a Jira epic can't be
|
|
439
|
+
reparented under a Linear story (downgrade attack from STRIDE §EoP #4).
|
|
440
|
+
Returns ``None`` if no match — caller treats this as a flat BACKLOG item.
|
|
441
|
+
"""
|
|
442
|
+
if not parent_external_id:
|
|
443
|
+
return None
|
|
444
|
+
parent_link = (
|
|
445
|
+
InboundTaskLink.objects.filter(
|
|
446
|
+
project=project,
|
|
447
|
+
source=source,
|
|
448
|
+
external_id=parent_external_id,
|
|
449
|
+
is_deleted=False,
|
|
450
|
+
)
|
|
451
|
+
.select_related("task")
|
|
452
|
+
.first()
|
|
453
|
+
)
|
|
454
|
+
if parent_link is None or parent_link.task.wbs_path is None:
|
|
455
|
+
return None
|
|
456
|
+
return str(parent_link.task.wbs_path)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@transaction.atomic
|
|
460
|
+
def upsert_inbound_task(
|
|
461
|
+
*,
|
|
462
|
+
project: Project,
|
|
463
|
+
token: ProjectApiToken,
|
|
464
|
+
payload: dict[str, Any],
|
|
465
|
+
source_ip: str | None,
|
|
466
|
+
) -> UpsertResult:
|
|
467
|
+
"""Idempotent upsert by (project, source, external_id).
|
|
468
|
+
|
|
469
|
+
On match: write-through of name, description, story_points, external_url,
|
|
470
|
+
and status (translated via the token's status_map). ``assignee`` is set
|
|
471
|
+
only on resolve — a previously-resolved assignee is *not* overwritten by
|
|
472
|
+
a re-push (prevents a compromised token from silently rewriting human
|
|
473
|
+
ownership decisions).
|
|
474
|
+
|
|
475
|
+
On no match: creates the Task with ``status`` from the status_map (or
|
|
476
|
+
BACKLOG fallback), attaches under ``parent_external_id`` if a matching
|
|
477
|
+
parent link exists, and writes the link row.
|
|
478
|
+
|
|
479
|
+
Side effects (all in transaction.on_commit()):
|
|
480
|
+
- broadcast_board_event(project_id, "task_created" | "task_updated", ...)
|
|
481
|
+
- dispatch_webhooks(project_id, "task.created" | "task.updated", payload)
|
|
482
|
+
- enqueue_recalculate(project_id) for CPM (idempotent — coalesced by
|
|
483
|
+
the ScheduleRequest outbox)
|
|
484
|
+
|
|
485
|
+
Writes an ApiTokenAuditEntry "used" row inside the same transaction.
|
|
486
|
+
|
|
487
|
+
Refuses an archived project with ``PermissionDenied`` before touching a row
|
|
488
|
+
(#3413). ``TaskSyncView`` carries ``IsProjectNotArchived`` as well, and this
|
|
489
|
+
repeats it on purpose: archived is *lifecycle state, not authority* — a
|
|
490
|
+
property of the plan rather than of the caller — so it does not belong to the
|
|
491
|
+
view layer alone (!2318 / #3354). This function takes its ``project`` by
|
|
492
|
+
argument and is reachable from anything that can construct one: a Celery task,
|
|
493
|
+
a management command, or a future ingest that resolves its project from a
|
|
494
|
+
webhook body rather than a URL kwarg, none of which DRF's permission stack
|
|
495
|
+
sees. The check re-reads the flag rather than trusting the passed instance,
|
|
496
|
+
which matters here because a deferred or retried ingest can hold a ``Project``
|
|
497
|
+
that was live when it was fetched and archived by the time it writes.
|
|
498
|
+
|
|
499
|
+
What the re-read does **not** close, deliberately: it is a read, not a lock.
|
|
500
|
+
Under READ COMMITTED an archive that commits after this ``SELECT`` is invisible
|
|
501
|
+
to the rest of the transaction, so a push already in flight when the PM archives
|
|
502
|
+
still lands. Closing that would mean taking a row lock on the project for every
|
|
503
|
+
inbound push, on an endpoint throttled at 1000 req/min during the backfill
|
|
504
|
+
window — the wrong trade for a race whose loser is one task on a plan that was
|
|
505
|
+
live when the request arrived. The stale-instance case above is the one this
|
|
506
|
+
guards; the concurrent-archive race is accepted.
|
|
507
|
+
"""
|
|
508
|
+
from trueppm_api.apps.access.permissions import assert_project_not_archived
|
|
509
|
+
|
|
510
|
+
assert_project_not_archived(project.pk)
|
|
511
|
+
|
|
512
|
+
resolved = _resolve_payload(project, token, payload)
|
|
513
|
+
|
|
514
|
+
link = (
|
|
515
|
+
InboundTaskLink.objects.select_for_update()
|
|
516
|
+
.select_related("task")
|
|
517
|
+
.filter(
|
|
518
|
+
project=project,
|
|
519
|
+
source=resolved.source,
|
|
520
|
+
external_id=resolved.external_id,
|
|
521
|
+
is_deleted=False,
|
|
522
|
+
)
|
|
523
|
+
.first()
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
created = link is None
|
|
527
|
+
if link is None:
|
|
528
|
+
task, link = _create_inbound_task(project, token, resolved)
|
|
529
|
+
else:
|
|
530
|
+
task = _update_inbound_task(link, token, resolved)
|
|
531
|
+
|
|
532
|
+
# Audit row — same transaction; survives even if downstream broadcast fails.
|
|
533
|
+
ApiTokenAuditEntry.objects.create(
|
|
534
|
+
project=project,
|
|
535
|
+
token=token,
|
|
536
|
+
token_prefix=token.token_prefix,
|
|
537
|
+
actor=None, # inbound use; no Django user
|
|
538
|
+
action=ApiTokenAuditAction.USED.value,
|
|
539
|
+
source_ip=source_ip,
|
|
540
|
+
detail={
|
|
541
|
+
"source": resolved.source,
|
|
542
|
+
"external_id": resolved.external_id,
|
|
543
|
+
"created": created,
|
|
544
|
+
"assignee_resolved": resolved.assignee is not None,
|
|
545
|
+
},
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
_defer_side_effects(project, task, resolved, created=created)
|
|
549
|
+
|
|
550
|
+
return UpsertResult(
|
|
551
|
+
task=task,
|
|
552
|
+
link=link,
|
|
553
|
+
created=created,
|
|
554
|
+
assignee_resolved=resolved.assignee is not None,
|
|
555
|
+
)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Workspace → Program → Project iteration-label resolution (ADR-0116, #1106).
|
|
2
|
+
|
|
3
|
+
The displayed iteration-container noun ("Sprint" / "Iteration" / "PI" / custom)
|
|
4
|
+
can be set at three scopes. The *effective* label for a project is resolved here,
|
|
5
|
+
computed-on-read (ADR-0108) — there is no stored/denormalized effective column to
|
|
6
|
+
keep in sync. Clients (web, mobile, MCP) read the serializer's
|
|
7
|
+
``effective_iteration_label``; they never re-implement this precedence.
|
|
8
|
+
|
|
9
|
+
Precedence: project override → program override → workspace default → ``"Sprint"``.
|
|
10
|
+
|
|
11
|
+
``ENFORCE`` (a workspace admin locking the term so lower scopes cannot override it)
|
|
12
|
+
is an Enterprise capability. OSS ships the neutral hook below and registers no
|
|
13
|
+
provider, so ``ENFORCE`` degrades to ``SUGGEST`` (no lock). trueppm-enterprise
|
|
14
|
+
registers a provider in its ``AppConfig.ready()`` (trueppm-enterprise#154) — the
|
|
15
|
+
integrations-registry idiom (ADR-0029/0049), mirroring
|
|
16
|
+
``signal_privacy_services.register_default_posture_provider``. OSS never imports
|
|
17
|
+
enterprise.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
from trueppm_api.core.extension_providers import guard_single_provider
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from trueppm_api.apps.projects.models import Project
|
|
29
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
30
|
+
|
|
31
|
+
#: System default when nothing up the chain sets a label. Mirrors the web
|
|
32
|
+
#: ``DEFAULT_ITERATION_LABEL`` so the server and client agree on the backstop.
|
|
33
|
+
DEFAULT_ITERATION_LABEL = "Sprint"
|
|
34
|
+
|
|
35
|
+
# Enterprise registers a zero-arg predicate that returns True when terminology
|
|
36
|
+
# enforcement is licensed/active. OSS leaves it None → enforcement inactive, so
|
|
37
|
+
# ENFORCE behaves as SUGGEST (no lock).
|
|
38
|
+
_ENFORCEMENT_PROVIDER: Callable[[], bool] | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def register_terminology_enforcement_provider(provider: Callable[[], bool] | None) -> None:
|
|
42
|
+
"""Register (or clear) the terminology-enforcement provider. Enterprise calls this."""
|
|
43
|
+
global _ENFORCEMENT_PROVIDER
|
|
44
|
+
_ENFORCEMENT_PROVIDER = guard_single_provider(
|
|
45
|
+
_ENFORCEMENT_PROVIDER, provider, "terminology enforcement"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def terminology_enforcement_active() -> bool:
|
|
50
|
+
"""True only when an enterprise provider is registered AND reports active.
|
|
51
|
+
|
|
52
|
+
OSS has no provider → always False, so a workspace ``ENFORCE`` policy never
|
|
53
|
+
locks downstream overrides in the community edition.
|
|
54
|
+
"""
|
|
55
|
+
return _ENFORCEMENT_PROVIDER is not None and bool(_ENFORCEMENT_PROVIDER())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def resolve_effective_iteration_label(
|
|
59
|
+
project: Project, *, workspace: Workspace | None = None
|
|
60
|
+
) -> str:
|
|
61
|
+
"""Resolve the iteration-container label a project should display.
|
|
62
|
+
|
|
63
|
+
``workspace`` may be passed to avoid re-loading the singleton per project when
|
|
64
|
+
resolving a list (the serializer caches it once); otherwise it is loaded here.
|
|
65
|
+
"""
|
|
66
|
+
from trueppm_api.apps.workspace.models import TermOverridePolicy, Workspace
|
|
67
|
+
|
|
68
|
+
if workspace is None:
|
|
69
|
+
workspace = Workspace.load()
|
|
70
|
+
|
|
71
|
+
if (
|
|
72
|
+
workspace.iteration_label_override_policy == TermOverridePolicy.ENFORCE
|
|
73
|
+
and terminology_enforcement_active()
|
|
74
|
+
):
|
|
75
|
+
# Enterprise lock: the workspace term wins regardless of lower overrides.
|
|
76
|
+
return workspace.iteration_label or DEFAULT_ITERATION_LABEL
|
|
77
|
+
|
|
78
|
+
program = project.program if project.program_id else None
|
|
79
|
+
program_label = program.iteration_label if program else None
|
|
80
|
+
return (
|
|
81
|
+
project.iteration_label
|
|
82
|
+
or program_label
|
|
83
|
+
or workspace.iteration_label
|
|
84
|
+
or DEFAULT_ITERATION_LABEL
|
|
85
|
+
)
|