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,178 @@
|
|
|
1
|
+
"""Time-tracking domain models — logged effort and the running timer (ADR-0185 §2).
|
|
2
|
+
|
|
3
|
+
``TimeEntry`` is the durable, sync-eligible fact; ``ActiveTimer`` is transient live
|
|
4
|
+
state. The split is deliberate (ADR-0185 §2): a ticking timer is derived state, not a
|
|
5
|
+
logged fact, so it is *not* a ``VersionedModel`` and a stopped timer is hard-deleted,
|
|
6
|
+
not soft-deleted. Nothing here broadcasts — a time entry mutates no shared board state
|
|
7
|
+
(ADR-0185 §5).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import uuid
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from django.conf import settings
|
|
16
|
+
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
17
|
+
from django.db import models
|
|
18
|
+
from django.utils import timezone
|
|
19
|
+
|
|
20
|
+
from trueppm_api.apps.projects.models import VersionedModel
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TimeEntrySource(models.TextChoices):
|
|
24
|
+
"""Provenance of a logged entry — drives the UI/undo affordance."""
|
|
25
|
+
|
|
26
|
+
MANUAL = "manual", "Manual"
|
|
27
|
+
TIMER = "timer", "Timer"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TimeEntry(VersionedModel):
|
|
31
|
+
"""A contributor's logged effort against a task (ADR-0185 §2).
|
|
32
|
+
|
|
33
|
+
The durable, sync-eligible record of *where a user's hours went*. The row is owned
|
|
34
|
+
by the logger: ``user`` is server-set to ``request.user`` and is never client-
|
|
35
|
+
supplied, so the entry is IDOR-safe by construction. Duration is canonical integer
|
|
36
|
+
``minutes`` (1..1440) to avoid the decimal-hours rounding drift the constraint
|
|
37
|
+
exists to prevent — the client formats ``h:mm``.
|
|
38
|
+
|
|
39
|
+
There is **no** uniqueness constraint on ``(user, task, entry_date)``: a contributor
|
|
40
|
+
may legitimately log multiple entries against the same task on the same day (two
|
|
41
|
+
sessions). The weekly grid aggregates them.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
task = models.ForeignKey(
|
|
45
|
+
"projects.Task",
|
|
46
|
+
on_delete=models.CASCADE,
|
|
47
|
+
related_name="time_entries",
|
|
48
|
+
)
|
|
49
|
+
user = models.ForeignKey(
|
|
50
|
+
settings.AUTH_USER_MODEL,
|
|
51
|
+
on_delete=models.CASCADE,
|
|
52
|
+
related_name="time_entries",
|
|
53
|
+
)
|
|
54
|
+
minutes = models.PositiveIntegerField(
|
|
55
|
+
validators=[MinValueValidator(1), MaxValueValidator(1440)],
|
|
56
|
+
)
|
|
57
|
+
entry_date = models.DateField(default=timezone.localdate)
|
|
58
|
+
note = models.CharField(max_length=500, blank=True)
|
|
59
|
+
source = models.CharField(
|
|
60
|
+
max_length=10,
|
|
61
|
+
choices=TimeEntrySource.choices,
|
|
62
|
+
default=TimeEntrySource.MANUAL,
|
|
63
|
+
)
|
|
64
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
65
|
+
|
|
66
|
+
# Soft-delete accountability (issue #1888). The base ``VersionedModel.soft_delete()``
|
|
67
|
+
# only flips ``is_deleted``, so a removed entry left no trace of *when* or *by whom*
|
|
68
|
+
# — and the task activity stream, which filtered ``is_deleted=False``, silently
|
|
69
|
+
# dropped its ``time_logged`` event too. That is an EVM/billing integrity gap: logged
|
|
70
|
+
# hours could be revised or removed with no record. These fields let the stream
|
|
71
|
+
# synthesize a ``time_deleted`` event, mirroring the ``TaskComment``/``TaskAttachment``
|
|
72
|
+
# soft-delete precedent. ``deleted_by`` is the acting user (always the owner — entries
|
|
73
|
+
# are self-scoped) and SET_NULLs on account deletion, so the event actor goes null.
|
|
74
|
+
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
75
|
+
deleted_by = models.ForeignKey(
|
|
76
|
+
settings.AUTH_USER_MODEL,
|
|
77
|
+
on_delete=models.SET_NULL,
|
|
78
|
+
null=True,
|
|
79
|
+
blank=True,
|
|
80
|
+
related_name="deleted_time_entries",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
class Meta:
|
|
84
|
+
db_table = "timetracking_time_entry"
|
|
85
|
+
indexes = [
|
|
86
|
+
# Header today/week rollup + weekly grid read.
|
|
87
|
+
models.Index(fields=["user", "entry_date"]),
|
|
88
|
+
# Grid cell (task, date) upsert/aggregate.
|
|
89
|
+
models.Index(fields=["user", "task", "entry_date"]),
|
|
90
|
+
# Per-project sync delta pull (task → server_version).
|
|
91
|
+
models.Index(fields=["task", "server_version"]),
|
|
92
|
+
models.Index(fields=["task", "sync_seq"]),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
return f"TimeEntry({self.user_id}, {self.minutes}m, {self.entry_date})"
|
|
97
|
+
|
|
98
|
+
def soft_delete(self, *, actor: Any | None = None) -> None:
|
|
99
|
+
"""Stamp ``deleted_at``/``deleted_by`` before delegating to the base soft-delete.
|
|
100
|
+
|
|
101
|
+
Mirrors ``Dependency.soft_delete()``'s stamping (issue #1888): the base
|
|
102
|
+
``VersionedModel.soft_delete()`` persists the whole row, so setting these two
|
|
103
|
+
fields first writes them in the same UPDATE that flips ``is_deleted``. Without
|
|
104
|
+
the timestamp the activity stream could not order or surface the ``time_deleted``
|
|
105
|
+
event, so a removed entry vanished from the ``time_logged`` feed with no trace.
|
|
106
|
+
"""
|
|
107
|
+
self.deleted_at = timezone.now()
|
|
108
|
+
self.deleted_by = actor
|
|
109
|
+
super().soft_delete()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ActiveTimer(models.Model):
|
|
113
|
+
"""The running stopwatch — transient live state, deliberately NOT versioned (ADR-0185 §2).
|
|
114
|
+
|
|
115
|
+
Elapsed time is derived (``now - started_at``); the row carries no logged fact, so
|
|
116
|
+
syncing it as versioned WatermelonDB rows would churn for a value the client
|
|
117
|
+
recomputes locally. The ``OneToOneField(user)`` is the singleton guarantee — a user
|
|
118
|
+
has at most one running timer at the DB level (cleaner than a partial unique on a
|
|
119
|
+
status flag). ``stop`` finalizes it into a ``TimeEntry`` and *deletes* this row
|
|
120
|
+
(no tombstone to sync); it is recovered after a reload via ``GET /me/timer/``.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
124
|
+
user = models.OneToOneField(
|
|
125
|
+
settings.AUTH_USER_MODEL,
|
|
126
|
+
on_delete=models.CASCADE,
|
|
127
|
+
related_name="active_timer",
|
|
128
|
+
)
|
|
129
|
+
task = models.ForeignKey(
|
|
130
|
+
"projects.Task",
|
|
131
|
+
on_delete=models.CASCADE,
|
|
132
|
+
related_name="+",
|
|
133
|
+
)
|
|
134
|
+
started_at = models.DateTimeField()
|
|
135
|
+
note = models.CharField(max_length=500, blank=True)
|
|
136
|
+
|
|
137
|
+
class Meta:
|
|
138
|
+
db_table = "timetracking_active_timer"
|
|
139
|
+
|
|
140
|
+
def __str__(self) -> str:
|
|
141
|
+
return f"ActiveTimer({self.user_id} -> {self.task_id})"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TimesheetSubmission(models.Model):
|
|
145
|
+
"""A contributor's "I marked this week done" signal — a per-user-per-week marker (ADR-0224).
|
|
146
|
+
|
|
147
|
+
Deliberately **not** a :class:`VersionedModel` (mirrors :class:`ActiveTimer`): submitting a
|
|
148
|
+
week is an inherently *online* action on a web-first surface, and the row carries no field a
|
|
149
|
+
client edits offline, so WatermelonDB version/tombstone machinery would churn sync for a
|
|
150
|
+
boolean-shaped signal. It is the whole submission state machine 0.4 ships — no approver, no
|
|
151
|
+
lock, no return; the 0.5 approval epic (#100) reads or extends this row's existence +
|
|
152
|
+
``submitted_at`` as its "Submitted" signal without migrating :class:`TimeEntry`.
|
|
153
|
+
|
|
154
|
+
``week_start`` is canonicalized to the ISO Monday by the view before write, so the
|
|
155
|
+
``(user, week_start)`` uniqueness cannot fragment into off-by-a-day rows. Un-submit hard-
|
|
156
|
+
deletes the row (no tombstone — not sync-eligible).
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
160
|
+
user = models.ForeignKey(
|
|
161
|
+
settings.AUTH_USER_MODEL,
|
|
162
|
+
on_delete=models.CASCADE,
|
|
163
|
+
related_name="timesheet_submissions",
|
|
164
|
+
)
|
|
165
|
+
week_start = models.DateField()
|
|
166
|
+
submitted_at = models.DateTimeField()
|
|
167
|
+
|
|
168
|
+
class Meta:
|
|
169
|
+
db_table = "timetracking_timesheet_submission"
|
|
170
|
+
constraints = [
|
|
171
|
+
models.UniqueConstraint(
|
|
172
|
+
fields=["user", "week_start"],
|
|
173
|
+
name="uniq_timesheet_submission_user_week",
|
|
174
|
+
),
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
def __str__(self) -> str:
|
|
178
|
+
return f"TimesheetSubmission({self.user_id}, week of {self.week_start})"
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Serializers for the time-tracking REST surface (ADR-0185 §4)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import date, timedelta
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
from django.utils import timezone
|
|
10
|
+
from rest_framework import serializers
|
|
11
|
+
|
|
12
|
+
from trueppm_api.apps.timetracking.models import ActiveTimer, TimeEntry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _backdate_window_days() -> int:
|
|
16
|
+
"""Manual-entry backdate window (settings ``TIMETRACKING_BACKDATE_DAYS``, default 60)."""
|
|
17
|
+
return int(getattr(settings, "TIMETRACKING_BACKDATE_DAYS", 60))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _timer_max_minutes() -> int:
|
|
21
|
+
return int(getattr(settings, "TIMETRACKING_TIMER_MAX_MINUTES", 600))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TimeEntrySerializer(serializers.ModelSerializer[TimeEntry]):
|
|
25
|
+
"""Read/write serializer for a single :class:`TimeEntry` (create + author-only PATCH).
|
|
26
|
+
|
|
27
|
+
``task``, ``user``, ``source``, ``server_version`` and ``created_at`` are read-only:
|
|
28
|
+
the task comes from the nested route (not the body), the owner is server-set
|
|
29
|
+
(IDOR-safe), and provenance is server-decided. Only ``minutes``/``entry_date``/
|
|
30
|
+
``note`` are writable. ``validate_entry_date`` enforces the no-future / backdate-
|
|
31
|
+
window rule (ADR-0185 §Consequences) — it applies only to manual writes, since timer
|
|
32
|
+
entries set ``entry_date`` in the service and never pass through this serializer.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
class Meta:
|
|
36
|
+
model = TimeEntry
|
|
37
|
+
fields = [
|
|
38
|
+
"id",
|
|
39
|
+
"task",
|
|
40
|
+
"user",
|
|
41
|
+
"minutes",
|
|
42
|
+
"entry_date",
|
|
43
|
+
"note",
|
|
44
|
+
"source",
|
|
45
|
+
"server_version",
|
|
46
|
+
"created_at",
|
|
47
|
+
]
|
|
48
|
+
read_only_fields = [
|
|
49
|
+
"id",
|
|
50
|
+
"task",
|
|
51
|
+
"user",
|
|
52
|
+
"source",
|
|
53
|
+
"server_version",
|
|
54
|
+
"created_at",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
|
58
|
+
"""Reject logging time against a *phase* (ADR-0293) — code ``time_log_on_phase``.
|
|
59
|
+
|
|
60
|
+
A phase is a rollup: its effort is the sum of its children's logged time, so a
|
|
61
|
+
direct entry against the phase itself would double-count. The target task is
|
|
62
|
+
the nested-route task on create (passed in context by the view) or the entry's
|
|
63
|
+
own task on PATCH. Enforced here so an MCP/agent caller is blocked identically
|
|
64
|
+
to the UI (ADR-0112). Non-phase leaves — including a leaf-with-subtasks — accept
|
|
65
|
+
time as before.
|
|
66
|
+
"""
|
|
67
|
+
from trueppm_api.apps.projects.models import task_is_phase
|
|
68
|
+
|
|
69
|
+
task = self.context.get("task") or getattr(self.instance, "task", None)
|
|
70
|
+
if task is not None and task_is_phase(task):
|
|
71
|
+
raise serializers.ValidationError(
|
|
72
|
+
serializers.ErrorDetail(
|
|
73
|
+
"Time cannot be logged against a phase — it rolls up the logged "
|
|
74
|
+
"time of its child tasks. Log against a leaf task instead.",
|
|
75
|
+
code="time_log_on_phase",
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
return attrs
|
|
79
|
+
|
|
80
|
+
def validate_entry_date(self, value: date) -> date:
|
|
81
|
+
today = timezone.localdate()
|
|
82
|
+
# Allow one day past the server's local date. "Today" is the *contributor's* calendar
|
|
83
|
+
# day, and their timezone can be up to ~a day ahead of the server's (e.g. any zone vs a
|
|
84
|
+
# UTC-configured server, the default). Without this grace an ahead-of-server user
|
|
85
|
+
# logging "today" would hit a spurious 400 (#1926). Genuine future-dated logging is
|
|
86
|
+
# still blocked (client offers no future day) and >1 day ahead exceeds any real skew.
|
|
87
|
+
if value > today + timedelta(days=1):
|
|
88
|
+
raise serializers.ValidationError("Entry date cannot be in the future.")
|
|
89
|
+
window = _backdate_window_days()
|
|
90
|
+
if value < today - timedelta(days=window):
|
|
91
|
+
raise serializers.ValidationError(
|
|
92
|
+
f"Entry date cannot be more than {window} days in the past."
|
|
93
|
+
)
|
|
94
|
+
return value
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TimeEntryWeeklySerializer(serializers.ModelSerializer[TimeEntry]):
|
|
98
|
+
"""Enriched read serializer for the weekly cross-project grid (ADR-0185 §4).
|
|
99
|
+
|
|
100
|
+
Carries the task and project labels the grid renders so the client needs no second
|
|
101
|
+
round-trip. The viewset ``select_related("task", "task__project")``, so these nested
|
|
102
|
+
reads add no per-row query (N+1-safe — asserted by ``assertNumQueries``).
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
task_short_id = serializers.CharField(source="task.short_id", read_only=True)
|
|
106
|
+
task_name = serializers.CharField(source="task.name", read_only=True)
|
|
107
|
+
project = serializers.UUIDField(source="task.project_id", read_only=True)
|
|
108
|
+
project_code = serializers.CharField(source="task.project.code", read_only=True)
|
|
109
|
+
project_name = serializers.CharField(source="task.project.name", read_only=True)
|
|
110
|
+
|
|
111
|
+
class Meta:
|
|
112
|
+
model = TimeEntry
|
|
113
|
+
fields = [
|
|
114
|
+
"id",
|
|
115
|
+
"task",
|
|
116
|
+
"task_short_id",
|
|
117
|
+
"task_name",
|
|
118
|
+
"project",
|
|
119
|
+
"project_code",
|
|
120
|
+
"project_name",
|
|
121
|
+
"minutes",
|
|
122
|
+
"entry_date",
|
|
123
|
+
"note",
|
|
124
|
+
"source",
|
|
125
|
+
"server_version",
|
|
126
|
+
"created_at",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class ActiveTimerSerializer(serializers.ModelSerializer[ActiveTimer]):
|
|
131
|
+
"""Read serializer for the running timer (``GET /me/timer/`` active branch).
|
|
132
|
+
|
|
133
|
+
``elapsed_seconds`` and ``stale`` are server-computed from the authoritative
|
|
134
|
+
``started_at`` clock so every device agrees regardless of when it last polled.
|
|
135
|
+
``stale`` flips once elapsed exceeds the ceiling, prompting the UI rather than
|
|
136
|
+
silently logging a weekend (ADR-0185 §2).
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
task_short_id = serializers.CharField(source="task.short_id", read_only=True)
|
|
140
|
+
task_name = serializers.CharField(source="task.name", read_only=True)
|
|
141
|
+
project = serializers.UUIDField(source="task.project_id", read_only=True)
|
|
142
|
+
elapsed_seconds = serializers.SerializerMethodField()
|
|
143
|
+
stale = serializers.SerializerMethodField()
|
|
144
|
+
|
|
145
|
+
class Meta:
|
|
146
|
+
model = ActiveTimer
|
|
147
|
+
fields = [
|
|
148
|
+
"id",
|
|
149
|
+
"task",
|
|
150
|
+
"task_short_id",
|
|
151
|
+
"task_name",
|
|
152
|
+
"project",
|
|
153
|
+
"started_at",
|
|
154
|
+
"elapsed_seconds",
|
|
155
|
+
"note",
|
|
156
|
+
"stale",
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
def get_elapsed_seconds(self, obj: ActiveTimer) -> int:
|
|
160
|
+
return int((timezone.now() - obj.started_at).total_seconds())
|
|
161
|
+
|
|
162
|
+
def get_stale(self, obj: ActiveTimer) -> bool:
|
|
163
|
+
return self.get_elapsed_seconds(obj) > _timer_max_minutes() * 60
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class TimerStartSerializer(serializers.Serializer): # type: ignore[type-arg]
|
|
167
|
+
"""Validate the ``POST /me/timer/start`` body (ADR-0185 §4).
|
|
168
|
+
|
|
169
|
+
Only ``task`` (uuid) and an optional ``note`` are accepted. Task resolution,
|
|
170
|
+
membership scoping (404), and the ``can_log_time`` role gate (403) are enforced in
|
|
171
|
+
the view against a membership-scoped queryset so existence is never leaked.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
task = serializers.UUIDField()
|
|
175
|
+
note = serializers.CharField(required=False, allow_blank=True, max_length=500, default="")
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Service layer for time tracking (ADR-0185 Durable Execution §4).
|
|
2
|
+
|
|
3
|
+
Every operation here is a **synchronous** DB transaction with no async side effect:
|
|
4
|
+
no Celery ``.delay()``, no ``broadcast_board_event()``, no CPM recompute. A time entry
|
|
5
|
+
never touches ``Task`` dates, so it cannot trigger a schedule recalculation. There is
|
|
6
|
+
nothing to dead-letter and nothing to broadcast (ADR-0185 §5) — the negative is
|
|
7
|
+
deliberate.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from datetime import date
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
from django.conf import settings
|
|
16
|
+
from django.db import transaction
|
|
17
|
+
from django.utils import timezone
|
|
18
|
+
|
|
19
|
+
from trueppm_api.apps.timetracking.models import ActiveTimer, TimeEntry, TimeEntrySource
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from django.contrib.auth.models import User as _User
|
|
23
|
+
|
|
24
|
+
from trueppm_api.apps.projects.models import Task
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _timer_max_minutes() -> int:
|
|
28
|
+
"""The stale-timer ceiling (settings ``TIMETRACKING_TIMER_MAX_MINUTES``, default 600)."""
|
|
29
|
+
return int(getattr(settings, "TIMETRACKING_TIMER_MAX_MINUTES", 600))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _member_can_still_log_time(user: _User, project_id: Any) -> bool:
|
|
33
|
+
"""Role >= Member on ``project_id`` right now, with no ``request`` in hand (#4318).
|
|
34
|
+
|
|
35
|
+
Mirrors ``can_user_log_time`` (``access/permissions.py``), which is the
|
|
36
|
+
authoritative predicate everywhere a request is available — but this service
|
|
37
|
+
layer only ever has a ``user``, so it re-derives the role directly from a live
|
|
38
|
+
(non-soft-deleted) ``ProjectMembership`` row rather than through the
|
|
39
|
+
request-cached helper. A boolean, not a raise: both ``start_timer``'s
|
|
40
|
+
second-start and ``stop_timer`` *decide* what to do with a timer whose access
|
|
41
|
+
has lapsed (discard it) rather than refuse the request outright — see each
|
|
42
|
+
function's docstring for why discard, not a 403, is the answer both places.
|
|
43
|
+
"""
|
|
44
|
+
from trueppm_api.apps.access.models import ProjectMembership, Role
|
|
45
|
+
|
|
46
|
+
role = (
|
|
47
|
+
# project__is_deleted (#4577): ``_membership_role`` floors it, so a trashed
|
|
48
|
+
# project confers no role there — and this mirror must agree, or a timer
|
|
49
|
+
# left running on a trashed project would still finalize into it.
|
|
50
|
+
ProjectMembership.objects.filter(
|
|
51
|
+
project_id=project_id, user=user, is_deleted=False, project__is_deleted=False
|
|
52
|
+
)
|
|
53
|
+
.values_list("role", flat=True)
|
|
54
|
+
.first()
|
|
55
|
+
)
|
|
56
|
+
return role is not None and role >= Role.MEMBER
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _project_is_archived_now(project_id: Any) -> bool:
|
|
60
|
+
"""Direct, request-less archived check (#4318).
|
|
61
|
+
|
|
62
|
+
Mirrors ``_is_project_archived`` (``access/permissions.py``), which is
|
|
63
|
+
request-cached and unavailable here for the same reason
|
|
64
|
+
:func:`_member_can_still_log_time` re-derives role directly instead of calling
|
|
65
|
+
the request-cached ``_membership_role``.
|
|
66
|
+
"""
|
|
67
|
+
from trueppm_api.apps.projects.models import Project
|
|
68
|
+
|
|
69
|
+
return Project.objects.filter(pk=project_id, is_archived=True).exists()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def log_time(
|
|
73
|
+
*,
|
|
74
|
+
user: _User,
|
|
75
|
+
task: Task,
|
|
76
|
+
minutes: int,
|
|
77
|
+
entry_date: date | None = None,
|
|
78
|
+
note: str = "",
|
|
79
|
+
source: str = TimeEntrySource.MANUAL,
|
|
80
|
+
) -> TimeEntry:
|
|
81
|
+
"""Create a logged :class:`TimeEntry`.
|
|
82
|
+
|
|
83
|
+
``user`` is the server-set owner (never client-supplied); the caller resolves it
|
|
84
|
+
from ``request.user``. ``entry_date`` defaults to the caller's local "today"
|
|
85
|
+
(``timezone.localdate()``), matching ``/me/work``'s today bucket.
|
|
86
|
+
"""
|
|
87
|
+
return TimeEntry.objects.create(
|
|
88
|
+
user=user,
|
|
89
|
+
task=task,
|
|
90
|
+
minutes=minutes,
|
|
91
|
+
entry_date=entry_date or timezone.localdate(),
|
|
92
|
+
note=note,
|
|
93
|
+
source=source,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@transaction.atomic
|
|
98
|
+
def start_timer(*, user: _User, task: Task, note: str = "") -> tuple[ActiveTimer, TimeEntry | None]:
|
|
99
|
+
"""Start a running timer for ``user``.
|
|
100
|
+
|
|
101
|
+
Second-start (#1415): if a timer is already running it is atomically stopped and
|
|
102
|
+
logged first, and the finalized :class:`TimeEntry` is returned alongside the new
|
|
103
|
+
timer (the UI surfaces it in the undo toast). The ``OneToOneField(user)`` guarantees
|
|
104
|
+
a single live timer, so this can never leave two rows. ``select_for_update`` locks
|
|
105
|
+
the existing timer row so a concurrent double-start serializes rather than racing.
|
|
106
|
+
|
|
107
|
+
**The existing timer is re-checked before it is finalized (#4318).** A member who
|
|
108
|
+
starts a timer on project A, loses write access there (archived, removed, or
|
|
109
|
+
demoted below Member), and then legitimately starts a *new* timer on project B
|
|
110
|
+
must not have project A's stale timer silently logged as a ``TimeEntry`` — that
|
|
111
|
+
would create a write on a project they can no longer write to, through a path
|
|
112
|
+
(starting work elsewhere) that has nothing to do with project A. So this applies
|
|
113
|
+
the same **discard** semantics the ``ProjectMembership`` revocation hook uses
|
|
114
|
+
(``access/signals.py``): if project A is archived, or the caller can no longer
|
|
115
|
+
log time there, the existing ``ActiveTimer`` row is simply deleted, with no
|
|
116
|
+
``TimeEntry`` created. The common case (membership still live) is unaffected —
|
|
117
|
+
the prior timer is finalized exactly as before. This deliberately never *raises*:
|
|
118
|
+
a stale timer on an unrelated, inaccessible project must not block a legitimate
|
|
119
|
+
new start on a project the caller can still write to.
|
|
120
|
+
"""
|
|
121
|
+
finalized: TimeEntry | None = None
|
|
122
|
+
existing = (
|
|
123
|
+
ActiveTimer.objects.select_for_update().select_related("task").filter(user=user).first()
|
|
124
|
+
)
|
|
125
|
+
if existing is not None:
|
|
126
|
+
existing_project_id = existing.task.project_id
|
|
127
|
+
if _project_is_archived_now(existing_project_id) or not _member_can_still_log_time(
|
|
128
|
+
user, existing_project_id
|
|
129
|
+
):
|
|
130
|
+
existing.delete()
|
|
131
|
+
else:
|
|
132
|
+
finalized = _finalize(existing)
|
|
133
|
+
timer = ActiveTimer.objects.create(
|
|
134
|
+
user=user,
|
|
135
|
+
task=task,
|
|
136
|
+
started_at=timezone.now(),
|
|
137
|
+
note=note,
|
|
138
|
+
)
|
|
139
|
+
return timer, finalized
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@transaction.atomic
|
|
143
|
+
def stop_timer(*, user: _User) -> TimeEntry | None:
|
|
144
|
+
"""Stop ``user``'s running timer, finalize it into a :class:`TimeEntry`, delete the row.
|
|
145
|
+
|
|
146
|
+
Returns ``None`` when no timer is running (or when the timer is discarded —
|
|
147
|
+
see below) so the caller can respond ``409`` — a duplicate stop is a no-op,
|
|
148
|
+
never a double-log or a 500. ``select_for_update`` makes concurrent stops
|
|
149
|
+
serialize: the loser finds no row and gets ``None``.
|
|
150
|
+
|
|
151
|
+
**Re-checks the archived-project gate and live Member+ role before finalizing
|
|
152
|
+
(#4318).** This is defence-in-depth, not the primary control: a ``ProjectMembership``
|
|
153
|
+
revocation (removal, or demotion below Member) deletes the user's ``ActiveTimer`` rows
|
|
154
|
+
outright via the ``pre_save`` eviction hook in ``access/signals.py`` — the discard
|
|
155
|
+
decision made for #4318 — so in the common revoked-member case there is no row left
|
|
156
|
+
here to stop, and the caller already gets the existing ``409``. What this still catches:
|
|
157
|
+
an **archived** project, which does not touch membership and so never fires that hook,
|
|
158
|
+
and any ``ActiveTimer`` that predates it.
|
|
159
|
+
|
|
160
|
+
**Discard, never raise, on either check — round 2 of #4318.** The first pass raised
|
|
161
|
+
``PermissionDenied`` (403) here, which was itself a bug: raising left the
|
|
162
|
+
``ActiveTimer`` row in place, so a timer on a now-archived project could **never**
|
|
163
|
+
be stopped — every retry re-hit the same 403, and the web client
|
|
164
|
+
(``useActiveTimer.ts``) restores the optimistically-cleared timer into its cache on
|
|
165
|
+
any non-409 error, so the chip reappeared and the user was stuck retrying forever.
|
|
166
|
+
Deleting the row and returning ``None`` instead routes through the exact same,
|
|
167
|
+
already-handled path as "no timer is running": the view answers the ordinary
|
|
168
|
+
``409``, which the client already clears silently, no restore and no error toast.
|
|
169
|
+
This is the same resolution ``start_timer``'s second-start discard and the
|
|
170
|
+
``ProjectMembership`` revocation hook both already use — one semantics (discard a
|
|
171
|
+
timer whose access has lapsed) applied everywhere it can arise, rather than two
|
|
172
|
+
different outcomes for the same underlying situation. Deliberately does **not**
|
|
173
|
+
raise after the delete, even just to signal the refusal for observability: this
|
|
174
|
+
function runs inside ``@transaction.atomic`` under ``ATOMIC_REQUESTS``, and DRF's
|
|
175
|
+
exception handler calls ``set_rollback()`` for any raised ``APIException`` — which
|
|
176
|
+
would roll back the very delete meant to free the caller from this state.
|
|
177
|
+
"""
|
|
178
|
+
timer = ActiveTimer.objects.select_for_update().select_related("task").filter(user=user).first()
|
|
179
|
+
if timer is None:
|
|
180
|
+
return None
|
|
181
|
+
project_id = timer.task.project_id
|
|
182
|
+
if _project_is_archived_now(project_id) or not _member_can_still_log_time(user, project_id):
|
|
183
|
+
timer.delete()
|
|
184
|
+
return None
|
|
185
|
+
return _finalize(timer)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _finalize(timer: ActiveTimer) -> TimeEntry:
|
|
189
|
+
"""Convert a running timer into a logged ``TimeEntry`` and delete the timer row.
|
|
190
|
+
|
|
191
|
+
Elapsed seconds are rounded to the nearest minute (floored at 1 so a sub-minute
|
|
192
|
+
timer still logs something) and **capped** at the stale ceiling so a timer left
|
|
193
|
+
running over a weekend logs the ceiling, not thousands of minutes. The cap is also
|
|
194
|
+
clamped to the model's 1440-minute maximum to preserve the row invariant even if the
|
|
195
|
+
ceiling is misconfigured above 24 h. The entry dates to ``localdate(started_at)`` so
|
|
196
|
+
a timer crossing midnight is attributed to the day the work started. Must be called
|
|
197
|
+
inside a transaction (``start_timer`` / ``stop_timer`` provide it).
|
|
198
|
+
"""
|
|
199
|
+
elapsed_seconds = (timezone.now() - timer.started_at).total_seconds()
|
|
200
|
+
minutes = max(1, round(elapsed_seconds / 60))
|
|
201
|
+
minutes = min(minutes, _timer_max_minutes(), 1440)
|
|
202
|
+
entry = TimeEntry.objects.create(
|
|
203
|
+
user_id=timer.user_id,
|
|
204
|
+
task_id=timer.task_id,
|
|
205
|
+
minutes=minutes,
|
|
206
|
+
entry_date=timezone.localdate(timer.started_at),
|
|
207
|
+
note=timer.note,
|
|
208
|
+
source=TimeEntrySource.TIMER,
|
|
209
|
+
)
|
|
210
|
+
timer.delete()
|
|
211
|
+
return entry
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""URL patterns for the time-tracking app (ADR-0185 §4)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from django.urls import path
|
|
6
|
+
|
|
7
|
+
from trueppm_api.apps.timetracking.views import (
|
|
8
|
+
MeTimeEntryDetailView,
|
|
9
|
+
MeTimeEntryWeeklyView,
|
|
10
|
+
MeTimerStartView,
|
|
11
|
+
MeTimerStopView,
|
|
12
|
+
MeTimerView,
|
|
13
|
+
MeTimesheetSubmitView,
|
|
14
|
+
TaskTimeEntryView,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
urlpatterns = [
|
|
18
|
+
# Per-task, caller-scoped create + list.
|
|
19
|
+
path(
|
|
20
|
+
"tasks/<uuid:task_pk>/time-entries/",
|
|
21
|
+
TaskTimeEntryView.as_view(),
|
|
22
|
+
name="task-time-entries",
|
|
23
|
+
),
|
|
24
|
+
# Weekly cross-project rollup (grid + header). Registered before the detail
|
|
25
|
+
# route; the trailing-uuid detail route cannot shadow the bare list path.
|
|
26
|
+
path(
|
|
27
|
+
"me/time-entries/",
|
|
28
|
+
MeTimeEntryWeeklyView.as_view(),
|
|
29
|
+
name="me-time-entries",
|
|
30
|
+
),
|
|
31
|
+
# Author-only edit/delete.
|
|
32
|
+
path(
|
|
33
|
+
"me/time-entries/<uuid:pk>/",
|
|
34
|
+
MeTimeEntryDetailView.as_view(),
|
|
35
|
+
name="me-time-entry-detail",
|
|
36
|
+
),
|
|
37
|
+
# Weekly submission marker — submit / un-submit (ADR-0224).
|
|
38
|
+
path(
|
|
39
|
+
"me/timesheets/<str:week_start>/submit",
|
|
40
|
+
MeTimesheetSubmitView.as_view(),
|
|
41
|
+
name="me-timesheet-submit",
|
|
42
|
+
),
|
|
43
|
+
# Running timer (user singleton).
|
|
44
|
+
path("me/timer/", MeTimerView.as_view(), name="me-timer"),
|
|
45
|
+
path("me/timer/start", MeTimerStartView.as_view(), name="me-timer-start"),
|
|
46
|
+
path("me/timer/stop", MeTimerStopView.as_view(), name="me-timer-stop"),
|
|
47
|
+
]
|