From 4279ea2827d1483a7b73d657e4347531e7fcbe7c Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 22 Jul 2026 03:01:26 +0200 Subject: [PATCH] feat(scheduling): persist bounded cancellation notices --- src/govoplan_scheduling/backend/db/models.py | 1 + ...b3_v0111_scheduling_cancellation_notice.py | 43 +++++++++++++++++++ tests/test_migrations.py | 3 +- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/govoplan_scheduling/backend/migrations/versions/c9d4e7f1a2b3_v0111_scheduling_cancellation_notice.py diff --git a/src/govoplan_scheduling/backend/db/models.py b/src/govoplan_scheduling/backend/db/models.py index ecc2b80..4d5f14e 100644 --- a/src/govoplan_scheduling/backend/db/models.py +++ b/src/govoplan_scheduling/backend/db/models.py @@ -53,6 +53,7 @@ class SchedulingRequest(Base, TimestampMixin): calendar_event_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True) handed_off_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) cancelled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) + cancellation_notice_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True) metadata_: Mapped[dict[str, Any] | None] = mapped_column("metadata", JSON, nullable=True) diff --git a/src/govoplan_scheduling/backend/migrations/versions/c9d4e7f1a2b3_v0111_scheduling_cancellation_notice.py b/src/govoplan_scheduling/backend/migrations/versions/c9d4e7f1a2b3_v0111_scheduling_cancellation_notice.py new file mode 100644 index 0000000..24256c4 --- /dev/null +++ b/src/govoplan_scheduling/backend/migrations/versions/c9d4e7f1a2b3_v0111_scheduling_cancellation_notice.py @@ -0,0 +1,43 @@ +"""v0.1.11 bounded scheduling cancellation notice + +Revision ID: c9d4e7f1a2b3 +Revises: be8f4d2c1a70 +Create Date: 2026-07-22 00:00:00.000000 +""" +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa + + +revision = "c9d4e7f1a2b3" +down_revision = "be8f4d2c1a70" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + inspector = sa.inspect(op.get_bind()) + columns = { + item["name"]: item + for item in inspector.get_columns("scheduling_requests") + } + existing = columns.get("cancellation_notice_until") + if existing is not None: + if ( + not existing.get("nullable") + or not isinstance(existing["type"], sa.DateTime) + ): + raise RuntimeError( + "Cannot adopt scheduling_requests.cancellation_notice_until " + "because its schema is unexpected" + ) + return + op.add_column( + "scheduling_requests", + sa.Column("cancellation_notice_until", sa.DateTime(timezone=True), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column("scheduling_requests", "cancellation_notice_until") diff --git a/tests/test_migrations.py b/tests/test_migrations.py index 19c199f..443eb4c 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -20,7 +20,7 @@ from govoplan_scheduling.backend.db.models import ( from govoplan_scheduling.backend.manifest import get_manifest as get_scheduling_manifest -_SCHEDULING_HEAD = "be8f4d2c1a70" +_SCHEDULING_HEAD = "c9d4e7f1a2b3" _SCHEDULING_RESPONSE_SETTINGS_REVISION = "ad7e3c9b2f10" _ENABLED_MODULES = ("poll", "scheduling") _MANIFEST_FACTORIES = (get_poll_manifest, get_scheduling_manifest) @@ -146,6 +146,7 @@ class SchedulingMigrationTests(unittest.TestCase): self.assertIn(_SCHEDULING_HEAD, heads) self.assertIn("participant_visibility", columns) + self.assertIn("cancellation_notice_until", columns) self.assertIn("max_participants_per_option", columns) self.assertIn("response_comment", participant_columns) self.assertIn("participation_gateway", participant_columns)