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,597 @@
|
|
|
1
|
+
"""REST views for the time-tracking app (ADR-0185 §4).
|
|
2
|
+
|
|
3
|
+
Task-nested create/list, user-scoped (`/me/`) detail, weekly rollup, and the running
|
|
4
|
+
timer. Nothing here calls ``broadcast_board_event()`` — a time entry mutates no shared
|
|
5
|
+
board state (ADR-0185 §5), so the realtime board channel is deliberately untouched.
|
|
6
|
+
|
|
7
|
+
RBAC posture:
|
|
8
|
+
* cross-project / nonexistent task → **404** (the task is resolved against a
|
|
9
|
+
membership-scoped queryset, so existence is never leaked — IDOR-safe);
|
|
10
|
+
* member-but-Viewer on an accessible task → **403** (``CanLogTime`` object check);
|
|
11
|
+
* another user's entry → **404** (detail queryset is scoped to ``request.user``,
|
|
12
|
+
so author-only edit/delete is an existence-oracle close, not a 403);
|
|
13
|
+
* archived project, or a membership revoked/demoted below Member since the
|
|
14
|
+
write started → **403** on every write path (``IsProjectNotArchived`` /
|
|
15
|
+
``CanLogTime``, re-checked live rather than trusted from when the entry or
|
|
16
|
+
timer was created — #4318).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from datetime import date, datetime, timedelta
|
|
22
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
23
|
+
|
|
24
|
+
from django.db.models import Sum
|
|
25
|
+
from django.shortcuts import get_object_or_404
|
|
26
|
+
from django.utils import timezone
|
|
27
|
+
from drf_spectacular.types import OpenApiTypes
|
|
28
|
+
from drf_spectacular.utils import (
|
|
29
|
+
OpenApiParameter,
|
|
30
|
+
OpenApiResponse,
|
|
31
|
+
extend_schema,
|
|
32
|
+
inline_serializer,
|
|
33
|
+
)
|
|
34
|
+
from rest_framework import serializers, status
|
|
35
|
+
from rest_framework.exceptions import ErrorDetail, ValidationError
|
|
36
|
+
from rest_framework.permissions import IsAuthenticated
|
|
37
|
+
from rest_framework.request import Request
|
|
38
|
+
from rest_framework.response import Response
|
|
39
|
+
from rest_framework.views import APIView
|
|
40
|
+
|
|
41
|
+
from trueppm_api.apps.access.permissions import CanLogTime, IsProjectNotArchived
|
|
42
|
+
from trueppm_api.apps.idempotency.mixins import IdempotencyMixin
|
|
43
|
+
from trueppm_api.apps.projects.models import Task
|
|
44
|
+
from trueppm_api.apps.timetracking import services
|
|
45
|
+
from trueppm_api.apps.timetracking.models import ActiveTimer, TimeEntry, TimesheetSubmission
|
|
46
|
+
from trueppm_api.apps.timetracking.serializers import (
|
|
47
|
+
ActiveTimerSerializer,
|
|
48
|
+
TimeEntrySerializer,
|
|
49
|
+
TimeEntryWeeklySerializer,
|
|
50
|
+
TimerStartSerializer,
|
|
51
|
+
)
|
|
52
|
+
from trueppm_api.core.openapi import state_refusal_400
|
|
53
|
+
|
|
54
|
+
if TYPE_CHECKING:
|
|
55
|
+
from django.contrib.auth.models import User as _User
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _member_task_or_404(request: Request, task_pk: Any) -> Task:
|
|
59
|
+
"""Resolve a task scoped to projects the caller is an active member of.
|
|
60
|
+
|
|
61
|
+
Returns the task or raises ``Http404`` — a non-member (cross-project IDOR) and a
|
|
62
|
+
nonexistent task are indistinguishable to the caller (no existence oracle). A Viewer
|
|
63
|
+
is a member, so the task resolves for them; the role gate (``CanLogTime``) then
|
|
64
|
+
yields the 403, not this lookup.
|
|
65
|
+
"""
|
|
66
|
+
user = cast("_User", request.user)
|
|
67
|
+
qs = Task.objects.filter(
|
|
68
|
+
pk=task_pk,
|
|
69
|
+
is_deleted=False,
|
|
70
|
+
project__is_deleted=False,
|
|
71
|
+
project__memberships__user=user,
|
|
72
|
+
project__memberships__is_deleted=False,
|
|
73
|
+
).select_related("project")
|
|
74
|
+
return get_object_or_404(qs)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TaskTimeEntryView(IdempotencyMixin, APIView):
|
|
78
|
+
"""``/api/v1/tasks/{task_pk}/time-entries/`` — create and list caller-scoped entries.
|
|
79
|
+
|
|
80
|
+
POST creates an entry owned by ``request.user`` (server-set; the body can never
|
|
81
|
+
spoof ``user``). GET returns the caller's **own** entries on the task plus their own
|
|
82
|
+
``total_logged_minutes`` — the cross-contributor rollup is Enterprise/#100 and is
|
|
83
|
+
deliberately not exposed here (no surveillance surface, ADR-0185 §Consequences).
|
|
84
|
+
|
|
85
|
+
Inherits :class:`IdempotencyMixin` (ADR-0170) so a create honours an
|
|
86
|
+
``Idempotency-Key`` and a retried POST never double-logs.
|
|
87
|
+
|
|
88
|
+
Carries ``IsProjectNotArchived`` alongside ``CanLogTime`` (#4318) — an archived
|
|
89
|
+
project is hard read-only project-wide, and a time entry is a write like any
|
|
90
|
+
other. Both are object-level checks invoked via ``check_object_permissions``
|
|
91
|
+
after the task resolves; GET is unaffected (reads always pass both).
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
permission_classes = [IsAuthenticated, CanLogTime, IsProjectNotArchived]
|
|
95
|
+
|
|
96
|
+
@extend_schema(
|
|
97
|
+
responses={
|
|
98
|
+
200: OpenApiResponse(
|
|
99
|
+
response=inline_serializer(
|
|
100
|
+
name="TaskTimeEntryListResponse",
|
|
101
|
+
fields={
|
|
102
|
+
"results": TimeEntrySerializer(many=True),
|
|
103
|
+
"total_logged_minutes": serializers.IntegerField(),
|
|
104
|
+
},
|
|
105
|
+
),
|
|
106
|
+
description=(
|
|
107
|
+
"The caller's own time entries on this task plus their own "
|
|
108
|
+
"total: {results: [<entry>], total_logged_minutes: int}."
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
def get(self, request: Request, task_pk: str) -> Response:
|
|
114
|
+
user = cast("_User", request.user)
|
|
115
|
+
task = _member_task_or_404(request, task_pk)
|
|
116
|
+
# Object-level read gate: any project member (Viewer+) may read their own
|
|
117
|
+
# (possibly empty) entries; a non-member already 404'd above.
|
|
118
|
+
self.check_object_permissions(request, task)
|
|
119
|
+
entries = TimeEntry.objects.filter(task=task, user=user, is_deleted=False).order_by(
|
|
120
|
+
"-entry_date", "-created_at"
|
|
121
|
+
)
|
|
122
|
+
total = (
|
|
123
|
+
TimeEntry.objects.filter(task=task, user=user, is_deleted=False).aggregate(
|
|
124
|
+
total=Sum("minutes")
|
|
125
|
+
)["total"]
|
|
126
|
+
or 0
|
|
127
|
+
)
|
|
128
|
+
return Response(
|
|
129
|
+
{
|
|
130
|
+
"results": TimeEntrySerializer(entries, many=True).data,
|
|
131
|
+
"total_logged_minutes": int(total),
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@extend_schema(
|
|
136
|
+
request=TimeEntrySerializer,
|
|
137
|
+
responses={201: TimeEntrySerializer, 403: OpenApiTypes.OBJECT},
|
|
138
|
+
)
|
|
139
|
+
def post(self, request: Request, task_pk: str) -> Response:
|
|
140
|
+
task = _member_task_or_404(request, task_pk)
|
|
141
|
+
# Role gate: Member+ may log; Viewer is denied 403 (CanLogTime object check).
|
|
142
|
+
self.check_object_permissions(request, task)
|
|
143
|
+
# Pass the nested-route task in context so the serializer can reject a
|
|
144
|
+
# time-log against a phase (ADR-0293) — the task is not in the request body.
|
|
145
|
+
serializer = TimeEntrySerializer(
|
|
146
|
+
data=request.data, context={"request": request, "task": task}
|
|
147
|
+
)
|
|
148
|
+
serializer.is_valid(raise_exception=True)
|
|
149
|
+
entry = services.log_time(
|
|
150
|
+
user=cast("_User", request.user),
|
|
151
|
+
task=task,
|
|
152
|
+
minutes=serializer.validated_data["minutes"],
|
|
153
|
+
entry_date=serializer.validated_data.get("entry_date"),
|
|
154
|
+
note=serializer.validated_data.get("note", ""),
|
|
155
|
+
)
|
|
156
|
+
return Response(
|
|
157
|
+
TimeEntrySerializer(entry, context={"request": request}).data,
|
|
158
|
+
status=status.HTTP_201_CREATED,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class MeTimeEntryDetailView(IdempotencyMixin, APIView):
|
|
163
|
+
"""``/api/v1/me/time-entries/{id}/`` — author-only PATCH / DELETE (ADR-0185 §4).
|
|
164
|
+
|
|
165
|
+
The queryset is hard-scoped to ``user=request.user``, so PATCH or DELETE on another
|
|
166
|
+
user's entry returns **404** (existence-oracle close, matches #996) — never 403.
|
|
167
|
+
DELETE is a ``soft_delete`` so the undo toast and mobile sync tombstone both work.
|
|
168
|
+
|
|
169
|
+
**No ``TimesheetSubmission`` check, deliberately.** ADR-0224 ships submission as a
|
|
170
|
+
week-level *marker*: "no approver, no lock, no return". Entries stay editable after
|
|
171
|
+
submit — that is why un-submit exists at all, so a fat-fingered Submit cannot strand
|
|
172
|
+
a week. Do not add a submitted-week guard here: it would pre-empt the approval and
|
|
173
|
+
locking design that #100 owns at 0.5, and it would silently break the un-submit
|
|
174
|
+
escape hatch. The timesheet grid renders a submitted week read-only as a UI
|
|
175
|
+
convention only, and says so on the page (#2701).
|
|
176
|
+
|
|
177
|
+
**Re-checks live Member+ and the archived-project gate on every write (#4318).**
|
|
178
|
+
The queryset above only proves the entry is *owned by the caller* — it says
|
|
179
|
+
nothing about whether that caller is still a project member, or whether the
|
|
180
|
+
project has since been archived. A former member who still holds a valid
|
|
181
|
+
session could otherwise edit or delete their own historical entries in a
|
|
182
|
+
project they no longer belong to, or one that is now read-only. Both checks run
|
|
183
|
+
against the entry's own ``task`` (object-level, via ``CanLogTime`` and
|
|
184
|
+
``IsProjectNotArchived`` on ``permission_classes``) — a revoked member or an
|
|
185
|
+
archived project answers **403**, matching the sibling write paths; this never
|
|
186
|
+
leaks existence (the entry already resolved as the caller's own), so there is
|
|
187
|
+
no 404 case to preserve here.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
permission_classes = [IsAuthenticated, CanLogTime, IsProjectNotArchived]
|
|
191
|
+
|
|
192
|
+
def _own_entry_or_404(self, request: Request, pk: str) -> TimeEntry:
|
|
193
|
+
user = cast("_User", request.user)
|
|
194
|
+
qs = TimeEntry.objects.filter(user=user, is_deleted=False).select_related("task")
|
|
195
|
+
return get_object_or_404(qs, pk=pk)
|
|
196
|
+
|
|
197
|
+
@extend_schema(
|
|
198
|
+
request=TimeEntrySerializer,
|
|
199
|
+
responses={200: TimeEntrySerializer, 403: OpenApiTypes.OBJECT},
|
|
200
|
+
)
|
|
201
|
+
def patch(self, request: Request, pk: str) -> Response:
|
|
202
|
+
entry = self._own_entry_or_404(request, pk)
|
|
203
|
+
# Object-level re-check (#4318): resolves the task's live membership role
|
|
204
|
+
# and the project's archive flag, not whatever was true when the entry
|
|
205
|
+
# was logged.
|
|
206
|
+
self.check_object_permissions(request, entry.task)
|
|
207
|
+
serializer = TimeEntrySerializer(
|
|
208
|
+
entry, data=request.data, partial=True, context={"request": request}
|
|
209
|
+
)
|
|
210
|
+
serializer.is_valid(raise_exception=True)
|
|
211
|
+
serializer.save()
|
|
212
|
+
return Response(serializer.data)
|
|
213
|
+
|
|
214
|
+
@extend_schema(
|
|
215
|
+
responses={
|
|
216
|
+
204: OpenApiResponse(description="Entry soft-deleted; empty body."),
|
|
217
|
+
403: OpenApiTypes.OBJECT,
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
def delete(self, request: Request, pk: str) -> Response:
|
|
221
|
+
entry = self._own_entry_or_404(request, pk)
|
|
222
|
+
# Object-level re-check (#4318) — see the ``patch`` method above.
|
|
223
|
+
self.check_object_permissions(request, entry.task)
|
|
224
|
+
# Record the actor so the task activity stream can attribute the synthesized
|
|
225
|
+
# ``time_deleted`` event (issue #1888). Always the owner here — entries are
|
|
226
|
+
# self-scoped — but stamping it keeps a consistent actor shape across events.
|
|
227
|
+
entry.soft_delete(actor=request.user)
|
|
228
|
+
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _parse_iso_date(raw: str, field: str) -> date:
|
|
232
|
+
try:
|
|
233
|
+
return datetime.strptime(raw, "%Y-%m-%d").date()
|
|
234
|
+
except (ValueError, TypeError) as err:
|
|
235
|
+
raise ValidationError({field: "Must be an ISO date (YYYY-MM-DD)."}) from err
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _monday_of(d: date) -> date:
|
|
239
|
+
"""Canonicalize a date to the Monday of its ISO week (ADR-0224).
|
|
240
|
+
|
|
241
|
+
The submission unit is *a week*; normalizing to Monday (rather than rejecting a
|
|
242
|
+
non-Monday) is forgiving of client drift and makes the ``(user, week_start)`` marker
|
|
243
|
+
impossible to fragment into off-by-a-day rows.
|
|
244
|
+
"""
|
|
245
|
+
return d - timedelta(days=d.weekday())
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class MeTimeEntryWeeklyView(APIView):
|
|
249
|
+
"""``/api/v1/me/time-entries/?from=&to=`` — weekly cross-project rollup (ADR-0185 §4).
|
|
250
|
+
|
|
251
|
+
Drives both the #1435 grid and the #1234 header rollup. Returns the caller's entries
|
|
252
|
+
in the window across **all accessible projects**, plus precomputed totals (by_day,
|
|
253
|
+
by_cell, today, week) and the week's ``submission`` marker (ADR-0224). The membership
|
|
254
|
+
re-check (defence-in-depth, mirrors ``/me/work``) drops entries on projects the user was
|
|
255
|
+
removed from. ``select_related`` on task→project keeps the read N+1-bounded (asserted by
|
|
256
|
+
``assertNumQueries``); the totals are folded from the already-fetched rows, and the
|
|
257
|
+
submission is a single indexed lookup, so the read stays a small constant of queries.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
permission_classes = [IsAuthenticated]
|
|
261
|
+
|
|
262
|
+
@extend_schema(
|
|
263
|
+
parameters=[
|
|
264
|
+
OpenApiParameter("from", OpenApiTypes.DATE, OpenApiParameter.QUERY, required=False),
|
|
265
|
+
OpenApiParameter("to", OpenApiTypes.DATE, OpenApiParameter.QUERY, required=False),
|
|
266
|
+
],
|
|
267
|
+
responses={
|
|
268
|
+
200: inline_serializer(
|
|
269
|
+
name="MeTimeEntriesWeeklyResponse",
|
|
270
|
+
fields={
|
|
271
|
+
"results": TimeEntryWeeklySerializer(many=True),
|
|
272
|
+
"totals": inline_serializer(
|
|
273
|
+
name="MeTimeEntriesWeeklyTotals",
|
|
274
|
+
fields={
|
|
275
|
+
# Keyed by ISO date (by_day) or "<task_id>|<ISO date>"
|
|
276
|
+
# (by_cell) — the key set is data-shaped, not a fixed
|
|
277
|
+
# schema, so the value type is all drf-spectacular can
|
|
278
|
+
# usefully declare here.
|
|
279
|
+
"by_day": serializers.DictField(child=serializers.IntegerField()),
|
|
280
|
+
"by_cell": serializers.DictField(child=serializers.IntegerField()),
|
|
281
|
+
"today_minutes": serializers.IntegerField(),
|
|
282
|
+
"week_minutes": serializers.IntegerField(),
|
|
283
|
+
},
|
|
284
|
+
),
|
|
285
|
+
"submission": inline_serializer(
|
|
286
|
+
name="MeTimeEntriesWeeklySubmission",
|
|
287
|
+
fields={
|
|
288
|
+
"week_start": serializers.CharField(),
|
|
289
|
+
"submitted": serializers.BooleanField(),
|
|
290
|
+
"submitted_at": serializers.CharField(allow_null=True),
|
|
291
|
+
},
|
|
292
|
+
),
|
|
293
|
+
},
|
|
294
|
+
),
|
|
295
|
+
},
|
|
296
|
+
)
|
|
297
|
+
def get(self, request: Request) -> Response:
|
|
298
|
+
user = cast("_User", request.user)
|
|
299
|
+
from_raw = request.query_params.get("from")
|
|
300
|
+
to_raw = request.query_params.get("to")
|
|
301
|
+
if (from_raw is None) != (to_raw is None):
|
|
302
|
+
raise ValidationError("Provide both 'from' and 'to', or neither.")
|
|
303
|
+
if from_raw is None or to_raw is None:
|
|
304
|
+
# Default to the current ISO week (Monday..Sunday).
|
|
305
|
+
today = timezone.localdate()
|
|
306
|
+
start = today - timedelta(days=today.weekday())
|
|
307
|
+
end = start + timedelta(days=6)
|
|
308
|
+
else:
|
|
309
|
+
start = _parse_iso_date(from_raw, "from")
|
|
310
|
+
end = _parse_iso_date(to_raw, "to")
|
|
311
|
+
if start > end:
|
|
312
|
+
raise ValidationError({"from": "'from' must be on or before 'to'."})
|
|
313
|
+
|
|
314
|
+
entries = list(
|
|
315
|
+
TimeEntry.objects.filter(
|
|
316
|
+
user=user,
|
|
317
|
+
is_deleted=False,
|
|
318
|
+
entry_date__range=(start, end),
|
|
319
|
+
task__project__memberships__user=user,
|
|
320
|
+
task__project__memberships__is_deleted=False,
|
|
321
|
+
task__project__is_deleted=False, # #4577: trashed project confers no access
|
|
322
|
+
)
|
|
323
|
+
.select_related("task", "task__project")
|
|
324
|
+
.order_by("entry_date", "created_at")
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
today = timezone.localdate()
|
|
328
|
+
by_day: dict[str, int] = {}
|
|
329
|
+
by_cell: dict[str, int] = {}
|
|
330
|
+
today_minutes = 0
|
|
331
|
+
week_minutes = 0
|
|
332
|
+
for e in entries:
|
|
333
|
+
iso = e.entry_date.isoformat()
|
|
334
|
+
by_day[iso] = by_day.get(iso, 0) + e.minutes
|
|
335
|
+
cell = f"{e.task_id}|{iso}"
|
|
336
|
+
by_cell[cell] = by_cell.get(cell, 0) + e.minutes
|
|
337
|
+
week_minutes += e.minutes
|
|
338
|
+
if e.entry_date == today:
|
|
339
|
+
today_minutes += e.minutes
|
|
340
|
+
|
|
341
|
+
# Fold the week's submission marker (ADR-0224) so the grid needs no second
|
|
342
|
+
# round-trip. The grid always requests a full Mon-Sun window, so the Monday of
|
|
343
|
+
# 'start' is the well-defined submission key.
|
|
344
|
+
week_start = _monday_of(start)
|
|
345
|
+
submission = (
|
|
346
|
+
TimesheetSubmission.objects.filter(user=user, week_start=week_start)
|
|
347
|
+
.values_list("submitted_at", flat=True)
|
|
348
|
+
.first()
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return Response(
|
|
352
|
+
{
|
|
353
|
+
"results": TimeEntryWeeklySerializer(entries, many=True).data,
|
|
354
|
+
"totals": {
|
|
355
|
+
"by_day": by_day,
|
|
356
|
+
"by_cell": by_cell,
|
|
357
|
+
"today_minutes": today_minutes,
|
|
358
|
+
"week_minutes": week_minutes,
|
|
359
|
+
},
|
|
360
|
+
"submission": {
|
|
361
|
+
"week_start": week_start.isoformat(),
|
|
362
|
+
"submitted": submission is not None,
|
|
363
|
+
"submitted_at": submission.isoformat() if submission is not None else None,
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class MeTimesheetSubmitView(APIView):
|
|
370
|
+
"""``/api/v1/me/timesheets/{week_start}/submit`` — mark a week done / undo (ADR-0224).
|
|
371
|
+
|
|
372
|
+
A per-user-per-week submission marker with no approver: entries stay editable after
|
|
373
|
+
submit, and the 0.5 approval epic (#100) reads this row rather than re-deciding it. The
|
|
374
|
+
marker is hard-scoped to ``request.user`` (IDOR-safe by construction — the URL carries no
|
|
375
|
+
other user's identity), so ``IsAuthenticated`` is the whole gate; there is no task/project
|
|
376
|
+
to run ``CanLogTime`` against, and a marker with no entries is inert.
|
|
377
|
+
|
|
378
|
+
``week_start`` is normalized to its ISO Monday (:func:`_monday_of`) before write, so the
|
|
379
|
+
``(user, week_start)`` uniqueness cannot fragment. POST is an idempotent upsert
|
|
380
|
+
(``update_or_create`` refreshes ``submitted_at``); DELETE is an idempotent un-submit (204
|
|
381
|
+
even when no marker exists).
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
permission_classes = [IsAuthenticated]
|
|
385
|
+
|
|
386
|
+
@extend_schema(
|
|
387
|
+
request=None,
|
|
388
|
+
responses={
|
|
389
|
+
200: inline_serializer(
|
|
390
|
+
name="TimesheetSubmitResponse",
|
|
391
|
+
fields={
|
|
392
|
+
"week_start": serializers.CharField(),
|
|
393
|
+
"submitted_at": serializers.CharField(),
|
|
394
|
+
},
|
|
395
|
+
),
|
|
396
|
+
400: state_refusal_400(
|
|
397
|
+
"``week_start`` in the path is not an ISO date (YYYY-MM-DD). The "
|
|
398
|
+
"segment is free text, so a malformed value reaches the handler and "
|
|
399
|
+
"``_parse_iso_date`` refuses it with a body keyed by ``week_start`` "
|
|
400
|
+
"rather than a flat ``detail`` (#3319).",
|
|
401
|
+
shape="fields",
|
|
402
|
+
),
|
|
403
|
+
},
|
|
404
|
+
)
|
|
405
|
+
def post(self, request: Request, week_start: str) -> Response:
|
|
406
|
+
user = cast("_User", request.user)
|
|
407
|
+
monday = _monday_of(_parse_iso_date(week_start, "week_start"))
|
|
408
|
+
submission, _ = TimesheetSubmission.objects.update_or_create(
|
|
409
|
+
user=user,
|
|
410
|
+
week_start=monday,
|
|
411
|
+
defaults={"submitted_at": timezone.now()},
|
|
412
|
+
)
|
|
413
|
+
return Response(
|
|
414
|
+
{
|
|
415
|
+
"week_start": monday.isoformat(),
|
|
416
|
+
"submitted_at": submission.submitted_at.isoformat(),
|
|
417
|
+
}
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
@extend_schema(
|
|
421
|
+
responses={
|
|
422
|
+
204: OpenApiResponse(description="Week un-submitted; empty body."),
|
|
423
|
+
400: state_refusal_400(
|
|
424
|
+
"``week_start`` in the path is not an ISO date (YYYY-MM-DD). The "
|
|
425
|
+
"segment is free text, so a malformed value reaches the handler and "
|
|
426
|
+
"``_parse_iso_date`` refuses it with a body keyed by ``week_start`` "
|
|
427
|
+
"rather than a flat ``detail`` (#3319).",
|
|
428
|
+
shape="fields",
|
|
429
|
+
),
|
|
430
|
+
}
|
|
431
|
+
)
|
|
432
|
+
def delete(self, request: Request, week_start: str) -> Response:
|
|
433
|
+
user = cast("_User", request.user)
|
|
434
|
+
monday = _monday_of(_parse_iso_date(week_start, "week_start"))
|
|
435
|
+
TimesheetSubmission.objects.filter(user=user, week_start=monday).delete()
|
|
436
|
+
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class MeTimerView(APIView):
|
|
440
|
+
"""``GET /api/v1/me/timer/`` — the caller's running timer, reconcile-on-load (ADR-0185 §4).
|
|
441
|
+
|
|
442
|
+
Returns ``{active: false}`` or the live timer with server-computed
|
|
443
|
+
``elapsed_seconds`` / ``stale``. Multi-device continuity is handled here (refetch on
|
|
444
|
+
focus, ``started_at`` as the authoritative clock) — not by a broadcast (ADR-0185 §5).
|
|
445
|
+
"""
|
|
446
|
+
|
|
447
|
+
permission_classes = [IsAuthenticated]
|
|
448
|
+
|
|
449
|
+
@extend_schema(
|
|
450
|
+
responses={
|
|
451
|
+
200: inline_serializer(
|
|
452
|
+
name="MeTimerResponse",
|
|
453
|
+
fields={
|
|
454
|
+
"active": serializers.BooleanField(),
|
|
455
|
+
# Present only when active is True — flattened from
|
|
456
|
+
# ActiveTimerSerializer at the top level (not nested), matching
|
|
457
|
+
# what the view actually returns.
|
|
458
|
+
"id": serializers.UUIDField(required=False),
|
|
459
|
+
"task": serializers.UUIDField(required=False),
|
|
460
|
+
"task_short_id": serializers.CharField(required=False),
|
|
461
|
+
"task_name": serializers.CharField(required=False),
|
|
462
|
+
"project": serializers.UUIDField(required=False),
|
|
463
|
+
"started_at": serializers.DateTimeField(required=False),
|
|
464
|
+
"elapsed_seconds": serializers.IntegerField(required=False),
|
|
465
|
+
"note": serializers.CharField(required=False, allow_blank=True),
|
|
466
|
+
"stale": serializers.BooleanField(required=False),
|
|
467
|
+
},
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
)
|
|
471
|
+
def get(self, request: Request) -> Response:
|
|
472
|
+
user = cast("_User", request.user)
|
|
473
|
+
timer = (
|
|
474
|
+
ActiveTimer.objects.filter(user=user).select_related("task", "task__project").first()
|
|
475
|
+
)
|
|
476
|
+
if timer is None:
|
|
477
|
+
return Response({"active": False})
|
|
478
|
+
data = {"active": True, **ActiveTimerSerializer(timer).data}
|
|
479
|
+
return Response(data)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
class MeTimerStartView(IdempotencyMixin, APIView):
|
|
483
|
+
"""``POST /api/v1/me/timer/start`` — start (or restart) the caller's timer (ADR-0185 §4).
|
|
484
|
+
|
|
485
|
+
Second-start: if a timer is already running it is atomically stopped and logged, and
|
|
486
|
+
that finalized entry rides back in ``finalized_entry`` for the undo toast. The task
|
|
487
|
+
is resolved membership-scoped (404 for cross-project) and gated by ``can_log_time``
|
|
488
|
+
(403 for a Viewer).
|
|
489
|
+
|
|
490
|
+
Carries ``IsProjectNotArchived`` alongside ``CanLogTime`` (#4318) — starting a timer
|
|
491
|
+
is a write like any other, so the **new** task's project being archived answers 403
|
|
492
|
+
exactly as it does on the other write paths. This says nothing about the *prior*
|
|
493
|
+
running timer a second-start may finalize: that timer's own project is re-checked
|
|
494
|
+
inside ``services.start_timer`` (discard rather than finalize if it is no longer
|
|
495
|
+
writable), because it belongs to a different project than the one this permission
|
|
496
|
+
class is resolving against here.
|
|
497
|
+
"""
|
|
498
|
+
|
|
499
|
+
permission_classes = [IsAuthenticated, CanLogTime, IsProjectNotArchived]
|
|
500
|
+
|
|
501
|
+
@extend_schema(
|
|
502
|
+
request=TimerStartSerializer,
|
|
503
|
+
responses={
|
|
504
|
+
201: inline_serializer(
|
|
505
|
+
name="MeTimerStartResponse",
|
|
506
|
+
fields={
|
|
507
|
+
"active_timer": ActiveTimerSerializer(),
|
|
508
|
+
# Non-null only on a second-start, where the prior timer is
|
|
509
|
+
# atomically stopped and logged (see class docstring).
|
|
510
|
+
"finalized_entry": TimeEntrySerializer(allow_null=True, required=False),
|
|
511
|
+
},
|
|
512
|
+
),
|
|
513
|
+
403: OpenApiTypes.OBJECT,
|
|
514
|
+
},
|
|
515
|
+
)
|
|
516
|
+
def post(self, request: Request) -> Response:
|
|
517
|
+
body = TimerStartSerializer(data=request.data)
|
|
518
|
+
body.is_valid(raise_exception=True)
|
|
519
|
+
task = _member_task_or_404(request, body.validated_data["task"])
|
|
520
|
+
self.check_object_permissions(request, task)
|
|
521
|
+
# A phase carries no logged time (ADR-0293); reject starting a timer on one
|
|
522
|
+
# up front so the timer-stop path can never create a phase entry that would
|
|
523
|
+
# bypass TimeEntrySerializer. Same stable code as the manual-entry lock.
|
|
524
|
+
from trueppm_api.apps.projects.models import task_is_phase
|
|
525
|
+
|
|
526
|
+
if task_is_phase(task):
|
|
527
|
+
raise ValidationError(
|
|
528
|
+
ErrorDetail(
|
|
529
|
+
"Time cannot be logged against a phase — it rolls up the logged "
|
|
530
|
+
"time of its child tasks. Log against a leaf task instead.",
|
|
531
|
+
code="time_log_on_phase",
|
|
532
|
+
)
|
|
533
|
+
)
|
|
534
|
+
timer, finalized = services.start_timer(
|
|
535
|
+
user=cast("_User", request.user),
|
|
536
|
+
task=task,
|
|
537
|
+
note=body.validated_data.get("note", ""),
|
|
538
|
+
)
|
|
539
|
+
return Response(
|
|
540
|
+
{
|
|
541
|
+
"active_timer": ActiveTimerSerializer(timer).data,
|
|
542
|
+
"finalized_entry": (
|
|
543
|
+
TimeEntrySerializer(finalized, context={"request": request}).data
|
|
544
|
+
if finalized is not None
|
|
545
|
+
else None
|
|
546
|
+
),
|
|
547
|
+
},
|
|
548
|
+
status=status.HTTP_201_CREATED,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class MeTimerStopView(IdempotencyMixin, APIView):
|
|
553
|
+
"""``POST /api/v1/me/timer/stop`` — finalize the caller's running timer (ADR-0185 §4).
|
|
554
|
+
|
|
555
|
+
Returns the created ``TimeEntry`` (``source="timer"``). Naturally idempotent without a
|
|
556
|
+
key — a second stop finds no timer and returns ``409`` rather than a 500 or a
|
|
557
|
+
double-log — and carries :class:`IdempotencyMixin` (matching ``MeTimerStartView``) so a
|
|
558
|
+
keyed retry replays the original ``201`` instead of racing into that 409.
|
|
559
|
+
|
|
560
|
+
**No object to resolve a project from at this layer (#4318).** Unlike
|
|
561
|
+
``MeTimeEntryDetailView``, the request carries no id — the running timer, and
|
|
562
|
+
therefore its task and project, is only known once ``services.stop_timer``
|
|
563
|
+
looks it up. The archived-project and live-Member+ re-checks accordingly live
|
|
564
|
+
*inside* ``stop_timer`` itself (``timetracking/services.py``), not on
|
|
565
|
+
``permission_classes`` here.
|
|
566
|
+
|
|
567
|
+
**Never a 403 from either re-check (#4318 round 2).** An earlier version of
|
|
568
|
+
``stop_timer`` raised ``PermissionDenied`` when a timer's project was archived
|
|
569
|
+
or access had lapsed — which left the ``ActiveTimer`` row in place and made
|
|
570
|
+
that timer permanently un-stoppable (every retry re-hit the same 403, and the
|
|
571
|
+
web client restores the timer into its cache on any non-409 error). Both
|
|
572
|
+
checks now **discard** the timer and return ``None``, so this view answers the
|
|
573
|
+
same ``409`` ("no timer to stop") it already answers for a timer that was
|
|
574
|
+
stopped from another device — a state the client already clears silently with
|
|
575
|
+
no error toast. In practice a revoked or demoted member's ``ActiveTimer`` row
|
|
576
|
+
is usually already gone by the time this runs regardless — the
|
|
577
|
+
``ProjectMembership`` revocation hook (``access/signals.py``) discards it
|
|
578
|
+
immediately — so an archived project (which does not evict membership, and so
|
|
579
|
+
never fires that hook) is the common way to actually reach this discard.
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
permission_classes = [IsAuthenticated]
|
|
583
|
+
|
|
584
|
+
@extend_schema(
|
|
585
|
+
responses={
|
|
586
|
+
201: TimeEntrySerializer,
|
|
587
|
+
409: OpenApiTypes.OBJECT,
|
|
588
|
+
}
|
|
589
|
+
)
|
|
590
|
+
def post(self, request: Request) -> Response:
|
|
591
|
+
entry = services.stop_timer(user=cast("_User", request.user))
|
|
592
|
+
if entry is None:
|
|
593
|
+
return Response({"detail": "No active timer to stop."}, status=status.HTTP_409_CONFLICT)
|
|
594
|
+
return Response(
|
|
595
|
+
TimeEntrySerializer(entry, context={"request": request}).data,
|
|
596
|
+
status=status.HTTP_201_CREATED,
|
|
597
|
+
)
|
|
File without changes
|