trueppm-api 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trueppm_api/__init__.py +27 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +346 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +483 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +184 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_squashed_0019_release_0_4.py +302 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +143 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/minter_role.py +38 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +3263 -0
- trueppm_api/apps/access/serializers.py +1007 -0
- trueppm_api/apps/access/services.py +878 -0
- trueppm_api/apps/access/signals.py +171 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +96 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1965 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0001_squashed_0003_release_0_4.py +344 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +109 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0001_squashed_0003_release_0_4.py +90 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +459 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +178 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +863 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +597 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_squashed_0014_release_0_4.py +225 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/0014_tasklink_created_by.py +27 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +635 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +528 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +734 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1158 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +280 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +551 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1276 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +439 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +462 -0
- trueppm_api/apps/notifications/email_backend.py +350 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +299 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_squashed_0011_release_0_4.py +204 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1148 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +1094 -0
- trueppm_api/apps/notifications/services.py +1489 -0
- trueppm_api/apps/notifications/tasks.py +762 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +919 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +566 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +993 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +537 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_squashed_0009_release_0_4.py +128 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +541 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +458 -0
- trueppm_api/apps/projects/backfill.py +459 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +212 -0
- trueppm_api/apps/projects/config_notice.py +1330 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +132 -0
- trueppm_api/apps/projects/decisions_views.py +97 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +423 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4278 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2001 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1827 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1909 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1410 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/key_views.py +468 -0
- trueppm_api/apps/projects/keys.py +205 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +514 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +406 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +192 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migration_helpers.py +75 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5659 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +59 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +75 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +76 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0106_squashed_0158_release_0_4.py +2357 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/0154_object_keys.py +184 -0
- trueppm_api/apps/projects/migrations/0155_task_milestone_at_day_end.py +20 -0
- trueppm_api/apps/projects/migrations/0156_program_import_job_replaced_project_ids.py +17 -0
- trueppm_api/apps/projects/migrations/0157_forecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/0158_baselinetask_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +9209 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +903 -0
- trueppm_api/apps/projects/program_schedule.py +767 -0
- trueppm_api/apps/projects/program_views.py +3445 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +492 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +891 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/demo_landing_overlay.py +532 -0
- trueppm_api/apps/projects/seed/exporter.py +1717 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2764 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +128 -0
- trueppm_api/apps/projects/seed/replay.py +1695 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1169 -0
- trueppm_api/apps/projects/serializers.py +12904 -0
- trueppm_api/apps/projects/services.py +7525 -0
- trueppm_api/apps/projects/share_serializers.py +142 -0
- trueppm_api/apps/projects/share_services.py +373 -0
- trueppm_api/apps/projects/share_views.py +395 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +910 -0
- trueppm_api/apps/projects/signal_privacy_views.py +485 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +288 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1244 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1979 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +261 -0
- trueppm_api/apps/projects/template_views.py +829 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1016 -0
- trueppm_api/apps/projects/utilization.py +909 -0
- trueppm_api/apps/projects/views.py +21225 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0014_squashed_0015_release_0_4.py +47 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1752 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +144 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/finish_reading.py +198 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/mc_admission.py +181 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0010_squashed_0016_release_0_4.py +106 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/0015_projectforecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/scheduling/migrations/0016_montecarlorun_finish_readings.py +32 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +542 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +334 -0
- trueppm_api/apps/scheduling/serializers.py +723 -0
- trueppm_api/apps/scheduling/services.py +1251 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +2248 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2908 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +446 -0
- trueppm_api/apps/sso/services.py +1473 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +369 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +548 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0003_squashed_0005_release_0_4.py +77 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/0006_boardevent_per_project_seq.py +77 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +272 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +488 -0
- trueppm_api/apps/sync/serializers.py +688 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +556 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +957 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +159 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/0004_backfill_default_teams_on_upgrade.py +49 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +440 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0001_squashed_0004_release_0_4.py +167 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +211 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +597 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +279 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +249 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +100 -0
- trueppm_api/apps/webhooks/migrations/0010_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/0011_webhookeditor.py +48 -0
- trueppm_api/apps/webhooks/migrations/0012_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +437 -0
- trueppm_api/apps/webhooks/serializers.py +326 -0
- trueppm_api/apps/webhooks/tasks.py +680 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +533 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +371 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +254 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_squashed_0027_release_0_4.py +199 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +967 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +616 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1581 -0
- trueppm_api/asgi.py +49 -0
- trueppm_api/celery.py +40 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +934 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/demo_read_only.py +314 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +1221 -0
- trueppm_api/core/password_reset.py +513 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +1034 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +514 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/core/worker_selfheal.py +205 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2605 -0
- trueppm_api/settings/dev.py +296 -0
- trueppm_api/settings/prod.py +265 -0
- trueppm_api/urls.py +217 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0.dist-info/METADATA +273 -0
- trueppm_api-0.4.0.dist-info/RECORD +799 -0
- trueppm_api-0.4.0.dist-info/WHEEL +4 -0
- trueppm_api-0.4.0.dist-info/licenses/LICENSE +201 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICE +7 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICES +104 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/GPL-3.0.txt +674 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/LGPL-3.0.txt +165 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/MPL-2.0.txt +373 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
"""Re-anchor a bundled sample's dates to today (ADR-1175, #3481).
|
|
2
|
+
|
|
3
|
+
ADR-0114 authors every seed date as an offset from an ``anchor`` resolved at
|
|
4
|
+
**import day**, so a freshly loaded demo is always current. It then ages, and
|
|
5
|
+
nothing moves it: seven weeks after loading, the Atlas sample renders an "Active"
|
|
6
|
+
sprint that completed six weeks ago, nine overdue tasks per project, and a
|
|
7
|
+
burndown ending six weeks before today — while the banner still promises "60 days
|
|
8
|
+
of history … render out of the box".
|
|
9
|
+
|
|
10
|
+
This module is the fix. :func:`shift_sample_dates` moves every dated row the
|
|
11
|
+
loader wrote forward by the drift, then asks the scheduler to recompute what it
|
|
12
|
+
deliberately did not move.
|
|
13
|
+
|
|
14
|
+
Three properties make it safe to expose as a one-click action:
|
|
15
|
+
|
|
16
|
+
**The shift quantum is a whole week.** Not ``today - anchor``, but that rounded to
|
|
17
|
+
the nearest multiple of seven. The seed resolver snaps plan dates forward onto
|
|
18
|
+
working days (``reldates.WorkingCalendar``), so a sprint that starts on a Monday
|
|
19
|
+
starts on a Monday because the fixture *means* it to. Offsetting by an arbitrary
|
|
20
|
+
47 days would restart that sprint on a Wednesday and drop task bars onto
|
|
21
|
+
Saturdays — a differently broken demo. A whole-week offset preserves every
|
|
22
|
+
weekday alignment the importer established, exactly.
|
|
23
|
+
|
|
24
|
+
**It is idempotent.** ``sample_anchor_date`` advances by the same whole-week
|
|
25
|
+
offset that was applied, so a second call computes a residual of at most three
|
|
26
|
+
days, rounds it to zero weeks, and writes nothing. The caller needs no guard.
|
|
27
|
+
|
|
28
|
+
**It cannot reach real work.** ``_replace_existing`` refuses any program holding a
|
|
29
|
+
non-sample project (#2476), so a sample program holds only sample projects —
|
|
30
|
+
and :func:`shift_sample_dates` re-checks that invariant itself rather than
|
|
31
|
+
trusting the view that called it.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import datetime as dt
|
|
37
|
+
from collections.abc import Callable
|
|
38
|
+
from dataclasses import dataclass
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
from django.db import transaction
|
|
42
|
+
from django.db.models import F, QuerySet
|
|
43
|
+
from django.utils import timezone
|
|
44
|
+
|
|
45
|
+
from trueppm_api.apps.projects.models import Program, Project
|
|
46
|
+
|
|
47
|
+
# The shift quantum. Plan dates are snapped onto working days by the seed
|
|
48
|
+
# resolver, so only a whole-week offset preserves the weekday each date was
|
|
49
|
+
# deliberately placed on.
|
|
50
|
+
_WEEK = 7
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class NotASampleProgram(ValueError):
|
|
54
|
+
"""The program holds no sample projects, so its dates are real work."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class NoAnchorRecorded(ValueError):
|
|
58
|
+
"""The program is a sample but predates #3481 and carries no anchor date."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class DateShift:
|
|
63
|
+
"""One model's contribution to a sample date shift.
|
|
64
|
+
|
|
65
|
+
``scope`` narrows the model to rows belonging to one sample program. It is a
|
|
66
|
+
callable rather than a dict of filter kwargs because several of these reach
|
|
67
|
+
the program through a subquery rather than a join path.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
label: str
|
|
71
|
+
fields: tuple[str, ...]
|
|
72
|
+
scope: Callable[[Program], QuerySet[Any]]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _tasks(program: Program) -> QuerySet[Any]:
|
|
76
|
+
from trueppm_api.apps.projects.models import Task
|
|
77
|
+
|
|
78
|
+
return Task.objects.filter(project__program=program)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# Tier 1 — offset by the delta (ADR-1175)
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
#
|
|
85
|
+
# The authored plan and the synthesized history ADR-0114 backdated to support it.
|
|
86
|
+
# Ordering is cosmetic; every entry is applied in one transaction.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _shift_specs() -> tuple[DateShift, ...]:
|
|
90
|
+
"""Build the spec table.
|
|
91
|
+
|
|
92
|
+
Deferred into a function so importing this module does not import a dozen
|
|
93
|
+
model modules at Django-setup time.
|
|
94
|
+
"""
|
|
95
|
+
from trueppm_api.apps.projects.models import (
|
|
96
|
+
AcceptanceCriterion,
|
|
97
|
+
BacklogItem,
|
|
98
|
+
Baseline,
|
|
99
|
+
BaselineTask,
|
|
100
|
+
CommentAcknowledgement,
|
|
101
|
+
CommentReaction,
|
|
102
|
+
Dependency,
|
|
103
|
+
Label,
|
|
104
|
+
Risk,
|
|
105
|
+
RiskComment,
|
|
106
|
+
Sprint,
|
|
107
|
+
SprintRetro,
|
|
108
|
+
SprintScopeChange,
|
|
109
|
+
Task,
|
|
110
|
+
TaskAttachment,
|
|
111
|
+
TaskComment,
|
|
112
|
+
TaskLabel,
|
|
113
|
+
TaskNote,
|
|
114
|
+
TaskRecurrenceRule,
|
|
115
|
+
TaskRelation,
|
|
116
|
+
)
|
|
117
|
+
from trueppm_api.apps.scheduling.models import MonteCarloRun, ProjectForecastSnapshot
|
|
118
|
+
from trueppm_api.apps.timetracking.models import TimeEntry
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
DateShift(
|
|
122
|
+
"projects.Project",
|
|
123
|
+
# NOT archived_at (a lifecycle fact, not plan), NOT deleted_at (it
|
|
124
|
+
# participates in partial unique indexes), NOT recalculated_at (nulled
|
|
125
|
+
# separately — the recalc is redone, not moved).
|
|
126
|
+
("start_date", "status_date", "status_date_floor_armed_at", "draft_started_at"),
|
|
127
|
+
lambda p: Project.objects.filter(program=p),
|
|
128
|
+
),
|
|
129
|
+
DateShift(
|
|
130
|
+
"projects.Task",
|
|
131
|
+
# CPM output (early_*, late_*, scheduled_start) is deliberately absent —
|
|
132
|
+
# see _CPM_OUTPUT_RECOMPUTED below.
|
|
133
|
+
(
|
|
134
|
+
"planned_start",
|
|
135
|
+
"actual_start",
|
|
136
|
+
"actual_finish",
|
|
137
|
+
"blocked_since",
|
|
138
|
+
"status_changed_at",
|
|
139
|
+
"recurrence_occurrence_date",
|
|
140
|
+
"seeded_at",
|
|
141
|
+
"edited_at",
|
|
142
|
+
),
|
|
143
|
+
_tasks,
|
|
144
|
+
),
|
|
145
|
+
DateShift(
|
|
146
|
+
"projects.Sprint",
|
|
147
|
+
(
|
|
148
|
+
"start_date",
|
|
149
|
+
"finish_date",
|
|
150
|
+
"milestone_bound_at",
|
|
151
|
+
"activated_at",
|
|
152
|
+
"closed_at",
|
|
153
|
+
"created_at",
|
|
154
|
+
"updated_at",
|
|
155
|
+
),
|
|
156
|
+
lambda p: Sprint.objects.filter(project__program=p),
|
|
157
|
+
),
|
|
158
|
+
DateShift(
|
|
159
|
+
"projects.Baseline",
|
|
160
|
+
("created_at",),
|
|
161
|
+
lambda p: Baseline.objects.filter(project__program=p),
|
|
162
|
+
),
|
|
163
|
+
DateShift(
|
|
164
|
+
# A baseline is a frozen PLAN, and baseline-vs-actual variance is a
|
|
165
|
+
# RELATIVE quantity. Moving both sides by one delta preserves every
|
|
166
|
+
# variance exactly. Leaving baselines pinned while the plan moves would
|
|
167
|
+
# manufacture a fabricated multi-week slip on every task — the very
|
|
168
|
+
# "demo shows an overdue program" defect this exists to remove.
|
|
169
|
+
"projects.BaselineTask",
|
|
170
|
+
("start", "finish", "actual_start", "actual_finish"),
|
|
171
|
+
lambda p: BaselineTask.objects.filter(baseline__project__program=p),
|
|
172
|
+
),
|
|
173
|
+
DateShift(
|
|
174
|
+
"projects.AcceptanceCriterion",
|
|
175
|
+
("met_at",),
|
|
176
|
+
lambda p: AcceptanceCriterion.objects.filter(task__project__program=p),
|
|
177
|
+
),
|
|
178
|
+
DateShift(
|
|
179
|
+
# Program-scoped, not project-scoped (there is no per-project backlog).
|
|
180
|
+
"projects.BacklogItem",
|
|
181
|
+
("pulled_at", "created_at", "updated_at"),
|
|
182
|
+
lambda p: BacklogItem.objects.filter(program=p),
|
|
183
|
+
),
|
|
184
|
+
DateShift(
|
|
185
|
+
"projects.Risk",
|
|
186
|
+
("mitigation_due_date", "created_at", "updated_at"),
|
|
187
|
+
lambda p: Risk.objects.filter(project__program=p),
|
|
188
|
+
),
|
|
189
|
+
DateShift(
|
|
190
|
+
"projects.RiskComment",
|
|
191
|
+
("created_at",),
|
|
192
|
+
lambda p: RiskComment.objects.filter(risk__project__program=p),
|
|
193
|
+
),
|
|
194
|
+
DateShift(
|
|
195
|
+
# Reached from the predecessor side; a cross-project edge inside one
|
|
196
|
+
# sample program is covered either way, and an edge reaching OUT of the
|
|
197
|
+
# program cannot exist (a sample program holds only sample projects).
|
|
198
|
+
# deleted_at excluded — a tombstone is not plan.
|
|
199
|
+
"projects.Dependency",
|
|
200
|
+
("accepted_at",),
|
|
201
|
+
lambda p: Dependency.objects.filter(predecessor__project__program=p),
|
|
202
|
+
),
|
|
203
|
+
DateShift(
|
|
204
|
+
"projects.TaskComment",
|
|
205
|
+
("created_at", "edited_at"),
|
|
206
|
+
lambda p: TaskComment.objects.filter(task__project__program=p),
|
|
207
|
+
),
|
|
208
|
+
DateShift(
|
|
209
|
+
"projects.TaskNote",
|
|
210
|
+
("created_at", "edited_at"),
|
|
211
|
+
lambda p: TaskNote.objects.filter(task__project__program=p),
|
|
212
|
+
),
|
|
213
|
+
DateShift(
|
|
214
|
+
"projects.TaskLabel",
|
|
215
|
+
("created_at",),
|
|
216
|
+
lambda p: TaskLabel.objects.filter(task__project__program=p),
|
|
217
|
+
),
|
|
218
|
+
DateShift(
|
|
219
|
+
"projects.TaskAttachment",
|
|
220
|
+
("created_at",),
|
|
221
|
+
lambda p: TaskAttachment.objects.filter(task__project__program=p),
|
|
222
|
+
),
|
|
223
|
+
DateShift(
|
|
224
|
+
"projects.TaskRelation",
|
|
225
|
+
("created_at",),
|
|
226
|
+
lambda p: TaskRelation.objects.filter(source__project__program=p),
|
|
227
|
+
),
|
|
228
|
+
DateShift(
|
|
229
|
+
"projects.CommentReaction",
|
|
230
|
+
("created_at",),
|
|
231
|
+
lambda p: CommentReaction.objects.filter(comment__task__project__program=p),
|
|
232
|
+
),
|
|
233
|
+
DateShift(
|
|
234
|
+
"projects.CommentAcknowledgement",
|
|
235
|
+
("created_at",),
|
|
236
|
+
lambda p: CommentAcknowledgement.objects.filter(comment__task__project__program=p),
|
|
237
|
+
),
|
|
238
|
+
DateShift(
|
|
239
|
+
"projects.SprintRetro",
|
|
240
|
+
("created_at", "updated_at"),
|
|
241
|
+
lambda p: SprintRetro.objects.filter(sprint__project__program=p),
|
|
242
|
+
),
|
|
243
|
+
DateShift(
|
|
244
|
+
"projects.SprintScopeChange",
|
|
245
|
+
("added_at",),
|
|
246
|
+
lambda p: SprintScopeChange.objects.filter(sprint__project__program=p),
|
|
247
|
+
),
|
|
248
|
+
DateShift(
|
|
249
|
+
"projects.Label",
|
|
250
|
+
("created_at", "updated_at"),
|
|
251
|
+
lambda p: Label.objects.filter(project__program=p),
|
|
252
|
+
),
|
|
253
|
+
DateShift(
|
|
254
|
+
# No FK of its own — reached through the tasks that use it.
|
|
255
|
+
"projects.TaskRecurrenceRule",
|
|
256
|
+
("end_date", "generated_through"),
|
|
257
|
+
lambda p: TaskRecurrenceRule.objects.filter(
|
|
258
|
+
pk__in=Task.objects.filter(project__program=p)
|
|
259
|
+
.exclude(recurrence_rule=None)
|
|
260
|
+
.values("recurrence_rule")
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
DateShift(
|
|
264
|
+
"timetracking.TimeEntry",
|
|
265
|
+
("entry_date", "created_at"),
|
|
266
|
+
lambda p: TimeEntry.objects.filter(task__project__program=p),
|
|
267
|
+
),
|
|
268
|
+
DateShift(
|
|
269
|
+
# Synthesized by seed/forecast_backfill.py, never produced by a real
|
|
270
|
+
# engine run. Offsetting the capture timestamp and the percentile dates
|
|
271
|
+
# by one delta preserves the trend's SHAPE exactly, which is what the
|
|
272
|
+
# forecast chart renders and what the banner promises.
|
|
273
|
+
"scheduling.MonteCarloRun",
|
|
274
|
+
("taken_at", "status_date", "cpm_finish", "p50", "p80", "p95"),
|
|
275
|
+
lambda p: MonteCarloRun.objects.filter(project__program=p),
|
|
276
|
+
),
|
|
277
|
+
DateShift(
|
|
278
|
+
"scheduling.ProjectForecastSnapshot",
|
|
279
|
+
("captured_at", "cpm_finish", "mc_p50_finish", "mc_p80_finish", "mc_p95_finish"),
|
|
280
|
+
lambda p: ProjectForecastSnapshot.objects.filter(project__program=p),
|
|
281
|
+
),
|
|
282
|
+
DateShift(
|
|
283
|
+
"projects.Program",
|
|
284
|
+
("target_date",),
|
|
285
|
+
lambda p: Program.objects.filter(pk=p.pk),
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _history_specs() -> tuple[DateShift, ...]:
|
|
291
|
+
"""``history_date`` on the simple-history rows the replayer backdated.
|
|
292
|
+
|
|
293
|
+
ADR-0114's whole contribution was **backdated** synthesized history. Leaving
|
|
294
|
+
``history_date`` pinned while the plan moves would place a task's "went
|
|
295
|
+
IN_PROGRESS" event weeks before its own planned start — the activity timeline
|
|
296
|
+
would contradict the schedule it describes.
|
|
297
|
+
|
|
298
|
+
These rows are synthesized demo content, labeled as such per ADR-0114 §5.
|
|
299
|
+
Offsetting them falsifies no record of a real act.
|
|
300
|
+
"""
|
|
301
|
+
from trueppm_api.apps.projects.models import Program as _Program
|
|
302
|
+
from trueppm_api.apps.projects.models import Project as _Project
|
|
303
|
+
from trueppm_api.apps.projects.models import Sprint, Task
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
DateShift(
|
|
307
|
+
"projects.HistoricalTask",
|
|
308
|
+
("history_date",),
|
|
309
|
+
lambda p: Task.history.filter(project__program=p),
|
|
310
|
+
),
|
|
311
|
+
DateShift(
|
|
312
|
+
"projects.HistoricalProject",
|
|
313
|
+
("history_date",),
|
|
314
|
+
lambda p: _Project.history.filter(program=p),
|
|
315
|
+
),
|
|
316
|
+
DateShift(
|
|
317
|
+
"projects.HistoricalSprint",
|
|
318
|
+
("history_date",),
|
|
319
|
+
lambda p: Sprint.history.filter(project__program=p),
|
|
320
|
+
),
|
|
321
|
+
DateShift(
|
|
322
|
+
"projects.HistoricalProgram",
|
|
323
|
+
("history_date",),
|
|
324
|
+
lambda p: _Program.history.filter(id=p.pk),
|
|
325
|
+
),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# ---------------------------------------------------------------------------
|
|
330
|
+
# Tier 2 — recomputed, never offset
|
|
331
|
+
# ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
#: CPM engine output on ``Task``. Derived, not authored — which is why
|
|
334
|
+
#: ``_HISTORY_EXCLUDED_TASK`` already excludes every one of them, and why
|
|
335
|
+
#: ADR-1152 clears rather than migrates them. ``early_start``/``early_finish``
|
|
336
|
+
#: are the REMAINING-work window (ADR-0752), not the planned span, and
|
|
337
|
+
#: utilization reads them: offsetting these would silently change computed load
|
|
338
|
+
#: and forecast. The shift moves the INPUTS and re-runs the engine.
|
|
339
|
+
_CPM_OUTPUT_RECOMPUTED: frozenset[str] = frozenset(
|
|
340
|
+
{
|
|
341
|
+
"early_start",
|
|
342
|
+
"early_finish",
|
|
343
|
+
"late_start",
|
|
344
|
+
"late_finish",
|
|
345
|
+
"scheduled_start",
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# ---------------------------------------------------------------------------
|
|
351
|
+
# Tier 3 — deliberately untouched
|
|
352
|
+
# ---------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
_CPM_OUTPUT_REASON = "CPM output — recomputed (Tier 2)."
|
|
355
|
+
_TOMBSTONE_REASON = "Soft-delete tombstone."
|
|
356
|
+
|
|
357
|
+
#: Dated columns the loader writes that this shift does **not** move, each with
|
|
358
|
+
#: the reason. Every entry here is a decision, not an oversight — the test suite
|
|
359
|
+
#: asserts that every dated field on every loader-written model appears in
|
|
360
|
+
#: exactly one of the three tiers, so a new column cannot be added silently.
|
|
361
|
+
_DELIBERATELY_UNSHIFTED: dict[str, str] = {
|
|
362
|
+
"projects.Project.archived_at": "Lifecycle fact, not plan.",
|
|
363
|
+
"projects.Project.deleted_at": (
|
|
364
|
+
"Soft-delete tombstone. Participates in the partial unique index "
|
|
365
|
+
"unique_task_wbs_path_per_project_live (migration 0148) and its siblings; "
|
|
366
|
+
"rewriting it risks a constraint violation for no demo benefit."
|
|
367
|
+
),
|
|
368
|
+
"projects.Project.recalculated_at": (
|
|
369
|
+
"Nulled rather than moved — the CPM pass is re-run, not relocated."
|
|
370
|
+
),
|
|
371
|
+
"projects.Task.deleted_at": "Soft-delete tombstone; see Project.deleted_at.",
|
|
372
|
+
"projects.Task.early_start": _CPM_OUTPUT_REASON,
|
|
373
|
+
"projects.Task.early_finish": _CPM_OUTPUT_REASON,
|
|
374
|
+
"projects.Task.late_start": _CPM_OUTPUT_REASON,
|
|
375
|
+
"projects.Task.late_finish": _CPM_OUTPUT_REASON,
|
|
376
|
+
"projects.Task.scheduled_start": _CPM_OUTPUT_REASON,
|
|
377
|
+
"projects.Dependency.deleted_at": _TOMBSTONE_REASON,
|
|
378
|
+
"projects.TaskComment.deleted_at": _TOMBSTONE_REASON,
|
|
379
|
+
"projects.TaskNote.deleted_at": _TOMBSTONE_REASON,
|
|
380
|
+
"projects.TaskAttachment.deleted_at": _TOMBSTONE_REASON,
|
|
381
|
+
"projects.TaskRelation.deleted_at": _TOMBSTONE_REASON,
|
|
382
|
+
"timetracking.TimeEntry.deleted_at": _TOMBSTONE_REASON,
|
|
383
|
+
"projects.Program.closed_at": "Lifecycle fact, not plan.",
|
|
384
|
+
"projects.Program.created_at": (
|
|
385
|
+
"When the program row was minted. Moving it would claim the demo was "
|
|
386
|
+
"loaded on a day it was not, and nothing renders it."
|
|
387
|
+
),
|
|
388
|
+
"projects.Program.updated_at": "auto_now bookkeeping, not plan.",
|
|
389
|
+
"projects.Program.sample_anchor_date": (
|
|
390
|
+
"Advanced by the shift itself, not offset with the rest — it IS the "
|
|
391
|
+
"bookkeeping that makes the shift idempotent."
|
|
392
|
+
),
|
|
393
|
+
"projects.CalendarException.exc_start": (
|
|
394
|
+
"A Calendar is shared: Project.calendar is nullable and unset projects "
|
|
395
|
+
"inherit the workspace calendar, so the exceptions reachable from a sample "
|
|
396
|
+
"project are frequently the WORKSPACE's holidays. Shifting them would move "
|
|
397
|
+
"real projects' non-working days. A demo's holidays drifting is cosmetic; "
|
|
398
|
+
"corrupting the shared calendar is not."
|
|
399
|
+
),
|
|
400
|
+
"projects.CalendarException.exc_end": "See exc_start.",
|
|
401
|
+
"timetracking.TimesheetSubmission.week_start": (
|
|
402
|
+
"TimesheetSubmission has no project or program FK — only `user`. It cannot "
|
|
403
|
+
"be scoped to one sample program, and shifting by user would reach a real "
|
|
404
|
+
"person's real submissions if a persona account is ever taken over."
|
|
405
|
+
),
|
|
406
|
+
"timetracking.TimesheetSubmission.submitted_at": "See week_start.",
|
|
407
|
+
"projects.CeremonyTemplate.created_at": "auto_now_add bookkeeping; not rendered as plan.",
|
|
408
|
+
"projects.CeremonyTemplate.updated_at": "auto_now bookkeeping.",
|
|
409
|
+
"agents.AgentAction.occurred_at": (
|
|
410
|
+
"HASH-CHAINED audit evidence (ADR-0112 RC1/RC2). Every row carries "
|
|
411
|
+
"prev_hash/record_hash and the chain is INSTANCE-wide, not program-scoped, so "
|
|
412
|
+
"a sample's rows are interleaved with real agent actions. Rewriting "
|
|
413
|
+
"occurred_at would either invalidate every downstream record_hash or require "
|
|
414
|
+
"re-chaining rows this program does not own. A demo's agent-action timestamps "
|
|
415
|
+
"drifting is cosmetic; corrupting the audit chain is not. The seeded rows are "
|
|
416
|
+
"already marked as sample data in their hashed fields "
|
|
417
|
+
"(actor_token_prefix='sample', '[Sample data]' summary prefix, ADR-0114 §5), "
|
|
418
|
+
"so they cannot be mistaken for production evidence either way."
|
|
419
|
+
),
|
|
420
|
+
"agents.AgentActionChainHead.updated_at": (
|
|
421
|
+
"Singleton chain head for the whole instance — not program-scoped and not the "
|
|
422
|
+
"loader's to move. See AgentAction.occurred_at."
|
|
423
|
+
),
|
|
424
|
+
"projects.RetroActionItem.created_at": (
|
|
425
|
+
"Reached only through SprintRetro, whose own timestamps move; the action "
|
|
426
|
+
"item's creation stamp is not rendered on any dated surface."
|
|
427
|
+
),
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@dataclass(frozen=True)
|
|
432
|
+
class ShiftReport:
|
|
433
|
+
"""What one call to :func:`shift_sample_dates` actually did.
|
|
434
|
+
|
|
435
|
+
``rows_shifted`` counts **rows** touched, not date columns: every column of a
|
|
436
|
+
model moves in one statement, and a row with an unset date is still a row the
|
|
437
|
+
sweep rewrote. Reporting columns would mean either a second pass to count
|
|
438
|
+
non-NULLs or a number inflated by every NULL — and the figure exists to tell
|
|
439
|
+
an evaluator the scope of what just happened, which rows answer honestly.
|
|
440
|
+
"""
|
|
441
|
+
|
|
442
|
+
shifted: bool
|
|
443
|
+
days: int
|
|
444
|
+
anchor_date: dt.date
|
|
445
|
+
rows_shifted: int
|
|
446
|
+
projects: int
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def whole_week_delta(anchor: dt.date, today: dt.date) -> int:
|
|
450
|
+
"""Drift between ``anchor`` and ``today``, rounded to whole weeks.
|
|
451
|
+
|
|
452
|
+
Rounds to the NEAREST multiple of seven, so the residual is at most three
|
|
453
|
+
days and a sample is never shifted past today by more than that. Negative
|
|
454
|
+
drift (an anchor in the future — only reachable from a hand-edited fixture)
|
|
455
|
+
returns 0: shifting a demo backwards is never what anyone asked for.
|
|
456
|
+
"""
|
|
457
|
+
raw = (today - anchor).days
|
|
458
|
+
if raw <= 0:
|
|
459
|
+
return 0
|
|
460
|
+
return round(raw / _WEEK) * _WEEK
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def shift_sample_dates(program: Program, *, actor: Any = None) -> ShiftReport:
|
|
464
|
+
"""Move every dated row of a sample program forward to today (ADR-1175).
|
|
465
|
+
|
|
466
|
+
Raises:
|
|
467
|
+
NotASampleProgram: the program holds no ``is_sample`` project. Checked
|
|
468
|
+
HERE and not only at the view — a view can gate what its service does
|
|
469
|
+
not, and any non-view caller would reopen the hole.
|
|
470
|
+
NoAnchorRecorded: the program is a sample loaded before #3481 and carries
|
|
471
|
+
no anchor, so no drift is computable. The caller reloads instead.
|
|
472
|
+
"""
|
|
473
|
+
from trueppm_api.apps.scheduling.models import ScheduleRequestReason
|
|
474
|
+
from trueppm_api.apps.scheduling.services import enqueue_recalculate
|
|
475
|
+
|
|
476
|
+
if not Project.objects.filter(program=program, is_sample=True, is_deleted=False).exists():
|
|
477
|
+
raise NotASampleProgram("This program is not sample data.")
|
|
478
|
+
if program.sample_anchor_date is None:
|
|
479
|
+
raise NoAnchorRecorded(
|
|
480
|
+
"This demo was loaded before date shifting was available, so its "
|
|
481
|
+
"original dates are not recorded. Remove and reload it to get "
|
|
482
|
+
"current dates."
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
project_ids: list[str] = []
|
|
486
|
+
rows = 0
|
|
487
|
+
days = 0
|
|
488
|
+
|
|
489
|
+
with transaction.atomic():
|
|
490
|
+
# Re-read the anchor under a row lock: two owners pressing the button at
|
|
491
|
+
# once must not both compute the same delta and apply it twice.
|
|
492
|
+
locked = Program.objects.select_for_update().get(pk=program.pk)
|
|
493
|
+
anchor = locked.sample_anchor_date
|
|
494
|
+
if anchor is None: # pragma: no cover — re-checked under the lock
|
|
495
|
+
raise NoAnchorRecorded("No anchor recorded.")
|
|
496
|
+
days = whole_week_delta(anchor, timezone.localdate())
|
|
497
|
+
project_ids = [
|
|
498
|
+
str(pk)
|
|
499
|
+
for pk in Project.objects.filter(program=locked, is_deleted=False).values_list(
|
|
500
|
+
"id", flat=True
|
|
501
|
+
)
|
|
502
|
+
]
|
|
503
|
+
if days == 0:
|
|
504
|
+
# Already current (or within the sub-week residual). Nothing to write,
|
|
505
|
+
# and deliberately NOT an error — a second press is a no-op, which is
|
|
506
|
+
# what makes the action safe to offer without a guard.
|
|
507
|
+
return ShiftReport(
|
|
508
|
+
shifted=False,
|
|
509
|
+
days=0,
|
|
510
|
+
anchor_date=anchor,
|
|
511
|
+
rows_shifted=0,
|
|
512
|
+
projects=len(project_ids),
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
offset = dt.timedelta(days=days)
|
|
516
|
+
for spec in (*_shift_specs(), *_history_specs()):
|
|
517
|
+
# ALL of a model's columns move in ONE statement. Updating them one at
|
|
518
|
+
# a time walks the row through states its own CHECK constraints forbid:
|
|
519
|
+
# shifting Sprint.start_date before Sprint.finish_date puts start after
|
|
520
|
+
# finish and trips `sprint_finish_after_start` mid-sweep. A single
|
|
521
|
+
# UPDATE is checked once, against the final row.
|
|
522
|
+
#
|
|
523
|
+
# A NULL column stays NULL (NULL + interval is NULL), so unset dates
|
|
524
|
+
# need no filtering — which is also why the report counts ROWS rather
|
|
525
|
+
# than columns.
|
|
526
|
+
rows += spec.scope(locked).update(**{field: F(field) + offset for field in spec.fields})
|
|
527
|
+
|
|
528
|
+
# Tier 2: the engine output was left alone on purpose. Null the completion
|
|
529
|
+
# marker so the Schedule view shows its existing "recalculating" badge
|
|
530
|
+
# (#1053) instead of rendering stale CPM columns as though they were fresh.
|
|
531
|
+
Project.objects.filter(program=locked).update(recalculated_at=None)
|
|
532
|
+
|
|
533
|
+
locked.sample_anchor_date = anchor + offset
|
|
534
|
+
locked.save(update_fields=["sample_anchor_date"])
|
|
535
|
+
program.sample_anchor_date = locked.sample_anchor_date
|
|
536
|
+
|
|
537
|
+
new_anchor = locked.sample_anchor_date
|
|
538
|
+
|
|
539
|
+
# Outside the atomic block: enqueue_recalculate opens its own transaction and
|
|
540
|
+
# attempts an immediate dispatch, falling back to the 30 s drain if the broker
|
|
541
|
+
# is down (ADR-1175 Durable Execution §1/§2).
|
|
542
|
+
for project_id in project_ids:
|
|
543
|
+
enqueue_recalculate(project_id, reason=ScheduleRequestReason.MANUAL)
|
|
544
|
+
|
|
545
|
+
return ShiftReport(
|
|
546
|
+
shifted=True,
|
|
547
|
+
days=days,
|
|
548
|
+
anchor_date=new_anchor,
|
|
549
|
+
rows_shifted=rows,
|
|
550
|
+
projects=len(project_ids),
|
|
551
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Resolve seed v2 relative dates and timestamps (ADR-0114).
|
|
2
|
+
|
|
3
|
+
A v2 seed authors dates as offsets from an ``anchor`` resolved at import day, so
|
|
4
|
+
a bundled demo always looks current instead of aging into a museum piece:
|
|
5
|
+
|
|
6
|
+
"A-120" anchor minus 120 calendar days, snapped forward to a working day
|
|
7
|
+
"A+15" anchor plus 15 days
|
|
8
|
+
"A-120!" the trailing "!" opts out of weekend-snapping (land exactly)
|
|
9
|
+
"A-87T14:10" an event timestamp (never snapped — an event occurs when it occurs)
|
|
10
|
+
|
|
11
|
+
ISO literals (``"2026-01-05"``, ``"2026-01-05T14:10"``) remain valid so a v1
|
|
12
|
+
fixture migrated field-by-field still parses. The grammar is enforced by the v2
|
|
13
|
+
JSON Schema (``seedDate`` / ``seedTimestamp``); this module assumes well-formed
|
|
14
|
+
input and turns it into concrete ``date`` / aware ``datetime`` values.
|
|
15
|
+
|
|
16
|
+
Weekend-snapping uses the project ``Calendar`` convention directly — the
|
|
17
|
+
``working_days`` bitmask (Mon=1 … Sun=64) plus ``CalendarException`` non-working
|
|
18
|
+
ranges — rather than importing the scheduler, so the resolver stays a pure
|
|
19
|
+
function with no engine dependency. A snapped date advances forward to the next
|
|
20
|
+
working day; a milestone that must land exactly authors the ``!`` suffix.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
from datetime import date, datetime, timedelta, tzinfo
|
|
27
|
+
from typing import Any
|
|
28
|
+
from zoneinfo import ZoneInfo
|
|
29
|
+
|
|
30
|
+
# Mon=0 … Sun=6 (date.weekday) → bit Mon=1 … Sun=64 (Calendar.working_days).
|
|
31
|
+
_DEFAULT_WORKING_DAYS = 31 # Mon-Fri
|
|
32
|
+
_MAX_SNAP_DAYS = 366 # guard against a calendar with no reachable working day
|
|
33
|
+
|
|
34
|
+
_REL_DATE = re.compile(r"^A(?P<sign>[+-])(?P<days>\d+)(?P<bang>!?)$")
|
|
35
|
+
_REL_TS = re.compile(r"^A(?P<sign>[+-])(?P<days>\d+)(?:T(?P<h>\d{2}):(?P<m>\d{2}))?$")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class WorkingCalendar:
|
|
39
|
+
"""The minimal calendar facts the resolver needs to snap a date forward.
|
|
40
|
+
|
|
41
|
+
Constructed from a ``Calendar`` model (or defaulted) so the resolver never
|
|
42
|
+
touches the ORM itself — the importer hands it the bitmask and the set of
|
|
43
|
+
non-working dates already materialized from ``CalendarException`` ranges.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
working_days: int = _DEFAULT_WORKING_DAYS,
|
|
49
|
+
exception_dates: frozenset[date] | None = None,
|
|
50
|
+
tz: tzinfo | None = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
# A zero/invalid mask would make every day non-working and snapping
|
|
53
|
+
# pointless; fall back to Mon-Fri (the validator already rejects 0).
|
|
54
|
+
self.working_days = working_days or _DEFAULT_WORKING_DAYS
|
|
55
|
+
self.exception_dates = exception_dates or frozenset()
|
|
56
|
+
self.tz = tz or ZoneInfo("UTC")
|
|
57
|
+
|
|
58
|
+
def is_working_day(self, d: date) -> bool:
|
|
59
|
+
if d in self.exception_dates:
|
|
60
|
+
return False
|
|
61
|
+
return bool(self.working_days & (1 << d.weekday()))
|
|
62
|
+
|
|
63
|
+
def snap_forward(self, d: date) -> date:
|
|
64
|
+
"""Advance ``d`` to the next working day (returns ``d`` if already one)."""
|
|
65
|
+
for _ in range(_MAX_SNAP_DAYS):
|
|
66
|
+
if self.is_working_day(d):
|
|
67
|
+
return d
|
|
68
|
+
d += timedelta(days=1)
|
|
69
|
+
return d # unreachable in practice; never loop forever
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def resolve_anchor(payload: dict[str, Any], today: date) -> date:
|
|
73
|
+
"""Resolve the program anchor: an explicit ``anchor`` date, else import day.
|
|
74
|
+
|
|
75
|
+
An explicit anchor pins a fixed-date demo (discouraged); the common case is
|
|
76
|
+
no ``anchor`` key, which resolves to ``today`` so the demo is always current.
|
|
77
|
+
"""
|
|
78
|
+
raw = payload.get("anchor")
|
|
79
|
+
return date.fromisoformat(raw) if raw else today
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def resolve_date(
|
|
83
|
+
value: str,
|
|
84
|
+
*,
|
|
85
|
+
anchor: date,
|
|
86
|
+
calendar: WorkingCalendar | None = None,
|
|
87
|
+
snap: bool = True,
|
|
88
|
+
) -> date:
|
|
89
|
+
"""Resolve a ``seedDate`` (ISO literal or ``A±N[!]``) to a concrete date.
|
|
90
|
+
|
|
91
|
+
Relative dates snap forward to the next working day unless the ``!`` suffix
|
|
92
|
+
is present or ``snap=False`` (the caller already knows the field is exact).
|
|
93
|
+
"""
|
|
94
|
+
m = _REL_DATE.match(value)
|
|
95
|
+
if m is None:
|
|
96
|
+
return date.fromisoformat(value) # ISO literal
|
|
97
|
+
offset = int(m["days"]) * (-1 if m["sign"] == "-" else 1)
|
|
98
|
+
resolved = anchor + timedelta(days=offset)
|
|
99
|
+
if m["bang"] == "!" or not snap or calendar is None:
|
|
100
|
+
return resolved
|
|
101
|
+
return calendar.snap_forward(resolved)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def resolve_timestamp(
|
|
105
|
+
value: str,
|
|
106
|
+
*,
|
|
107
|
+
anchor: date,
|
|
108
|
+
tz: tzinfo | None = None,
|
|
109
|
+
) -> datetime:
|
|
110
|
+
"""Resolve a ``seedTimestamp`` to an aware datetime. Never weekend-snapped.
|
|
111
|
+
|
|
112
|
+
A relative timestamp with no ``THH:MM`` resolves to midnight. ISO literals
|
|
113
|
+
are accepted for v1-compatibility. The result is made timezone-aware in
|
|
114
|
+
``tz`` (default UTC) so it is a valid ``history_date``.
|
|
115
|
+
"""
|
|
116
|
+
tz = tz or ZoneInfo("UTC")
|
|
117
|
+
m = _REL_TS.match(value)
|
|
118
|
+
if m is None:
|
|
119
|
+
dt = datetime.fromisoformat(value)
|
|
120
|
+
else:
|
|
121
|
+
offset = int(m["days"]) * (-1 if m["sign"] == "-" else 1)
|
|
122
|
+
day = anchor + timedelta(days=offset)
|
|
123
|
+
hour = int(m["h"]) if m["h"] is not None else 0
|
|
124
|
+
minute = int(m["m"]) if m["m"] is not None else 0
|
|
125
|
+
dt = datetime(day.year, day.month, day.day, hour, minute)
|
|
126
|
+
return dt.replace(tzinfo=tz) if dt.tzinfo is None else dt
|