diff --git a/.tbls.yml b/.tbls.yml index a3a220b..9f218e2 100644 --- a/.tbls.yml +++ b/.tbls.yml @@ -19,7 +19,6 @@ viewpoints: - hame.document - hame.source_data - hame.organisation - - hame.lifecycle_date - codes.administrative_region - codes.lifecycle_status - codes.plan_theme @@ -56,7 +55,6 @@ viewpoints: - hame.document - hame.source_data - hame.organisation - - hame.lifecycle_date - name: Code tables desc: These tables contain all national (koodistot.suomi.fi) and local codes used for classifying plan data. diff --git a/database/codes.py b/database/codes.py index c30c6a5..39fd89c 100644 --- a/database/codes.py +++ b/database/codes.py @@ -13,9 +13,7 @@ if TYPE_CHECKING: from database.models import ( Document, - EventDate, LandUseArea, - LifeCycleDate, Line, Organisation, OtherArea, @@ -124,9 +122,6 @@ class LifeCycleStatus(CodeBase): __tablename__ = "lifecycle_status" code_list_uri = "http://uri.suomi.fi/codelist/rytj/kaavaelinkaari" - lifecycle_dates: Mapped[list[LifeCycleDate]] = relationship( - back_populates="lifecycle_status" - ) allowed_interaction_events: Mapped[list[TypeOfInteractionEvent]] = relationship( secondary="codes.allowed_events", back_populates="allowed_statuses" ) @@ -446,10 +441,6 @@ class TypeOfInteractionEvent(CodeBase): overlaps="allowed_decisions,allowed_processing_events", ) - event_dates: Mapped[list[EventDate]] = relationship( - back_populates="interaction_event" - ) - class NameOfPlanCaseDecision(CodeBase): """Kaava-asian päätöksen nimi""" @@ -463,8 +454,6 @@ class NameOfPlanCaseDecision(CodeBase): overlaps="allowed_interaction_events,allowed_processing_events,allowed_statuses", ) - event_dates: Mapped[list[EventDate]] = relationship(back_populates="decision") - class TypeOfProcessingEvent(CodeBase): """Käsittelytapahtuman laji""" @@ -478,10 +467,6 @@ class TypeOfProcessingEvent(CodeBase): overlaps="allowed_interaction_events,allowed_decisions,allowed_statuses", ) - event_dates: Mapped[list[EventDate]] = relationship( - back_populates="processing_event" - ) - class TypeOfDecisionMaker(CodeBase): """Päätöksentekijän laji""" diff --git a/database/models.py b/database/models.py index 3b864dd..26409e4 100644 --- a/database/models.py +++ b/database/models.py @@ -25,17 +25,14 @@ LegalEffectsOfMasterPlan, LifeCycleStatus, Municipality, - NameOfPlanCaseDecision, PersonalDataContent, PlanTheme, PlanType, RetentionTime, TypeOfAdditionalInformation, TypeOfDocument, - TypeOfInteractionEvent, TypeOfPlanRegulation, TypeOfPlanRegulationGroup, - TypeOfProcessingEvent, TypeOfSourceData, TypeOfUnderground, TypeOfVerbalPlanRegulation, @@ -44,8 +41,8 @@ regulation_group_association = Table( "regulation_group_association", Base.metadata, - Column("id", Uuid, primary_key=True, server_default=func.gen_random_uuid()), - Column( + Column[UUID]("id", Uuid, primary_key=True, server_default=func.gen_random_uuid()), + Column[UUID]( "plan_regulation_group_id", ForeignKey( "hame.plan_regulation_group.id", @@ -56,30 +53,30 @@ ), # General groups cannot actually be n2n but use this approach anyway # to make the approach uniform with the plan objects - Column( + Column[UUID]( "plan_id", ForeignKey("hame.plan.id", name="plan_id_fkey", ondelete="CASCADE"), index=True, comment="A plan in which the regulation group is a general regulation group", ), - Column( + Column[UUID]( "land_use_area_id", ForeignKey( "hame.land_use_area.id", name="land_use_area_id_fkey", ondelete="CASCADE" ), index=True, ), - Column( + Column[UUID]( "other_area_id", ForeignKey("hame.other_area.id", name="other_area_id_fkey", ondelete="CASCADE"), index=True, ), - Column( + Column[UUID]( "line_id", ForeignKey("hame.line.id", name="line_id_fkey", ondelete="CASCADE"), index=True, ), - Column( + Column[UUID]( "point_id", ForeignKey("hame.point.id", name="point_id_fkey", ondelete="CASCADE"), index=True, @@ -91,13 +88,13 @@ legal_effects_association = Table( "legal_effects_association", Base.metadata, - Column( + Column[UUID]( "plan_id", ForeignKey("hame.plan.id", name="plan_id_fkey", ondelete="CASCADE"), primary_key=True, # indexed by the primary key ), - Column( + Column[UUID]( "legal_effects_of_master_plan_id", ForeignKey( "codes.legal_effects_of_master_plan.id", @@ -115,8 +112,8 @@ plan_theme_association = Table( "plan_theme_association", Base.metadata, - Column("id", Uuid, primary_key=True, server_default=func.gen_random_uuid()), - Column( + Column[UUID]("id", Uuid, primary_key=True, server_default=func.gen_random_uuid()), + Column[UUID]( "plan_regulation_id", ForeignKey( "hame.plan_regulation.id", @@ -125,7 +122,7 @@ ), index=True, ), - Column( + Column[UUID]( "plan_proposition_id", ForeignKey( "hame.plan_proposition.id", @@ -134,7 +131,7 @@ ), index=True, ), - Column( + Column[UUID]( "plan_theme_id", ForeignKey( "codes.plan_theme.id", name="plan_theme_id_fkey", ondelete="CASCADE" @@ -209,19 +206,6 @@ def lifecycle_status(cls) -> Mapped[LifeCycleStatus]: "LifeCycleStatus", back_populates=f"{cls.__tablename__}s", lazy="joined" ) - # Let's add backreference to allow lazy loading from this side. - @declared_attr - @classmethod - def lifecycle_dates(cls) -> Mapped[list[LifeCycleDate]]: - return relationship( - "LifeCycleDate", - back_populates=f"{cls.__tablename__}", - lazy="joined", - cascade="all, delete-orphan", - passive_deletes=True, - order_by="LifeCycleDate.starting_at", - ) - class PeriodOfValidityMixin: """Mixin for period of validity fields.""" @@ -536,7 +520,7 @@ class AdditionalInformation(VersionedBase, AttributeValueMixin): type_of_verbal_regulation_association = Table( "type_of_verbal_regulation_association", Base.metadata, - Column( + Column[UUID]( "plan_regulation_id", ForeignKey( "hame.plan_regulation.id", @@ -545,7 +529,7 @@ class AdditionalInformation(VersionedBase, AttributeValueMixin): ), primary_key=True, ), - Column( + Column[UUID]( "type_of_verbal_plan_regulation_id", ForeignKey( "codes.type_of_verbal_plan_regulation.id", @@ -767,131 +751,3 @@ class Document(VersionedBase): decision_date: Mapped[datetime | None] document_date: Mapped[datetime] url: Mapped[str | None] - - -class LifeCycleDate(VersionedBase): - """Elinkaaritilan päivämäärät""" - - __tablename__ = "lifecycle_date" - - lifecycle_status_id: Mapped[UUID] = mapped_column( - ForeignKey("codes.lifecycle_status.id", name="plan_lifecycle_status_id_fkey"), - index=True, - ) - plan_id: Mapped[UUID | None] = mapped_column( - ForeignKey("hame.plan.id", name="plan_id_fkey", ondelete="CASCADE"), index=True - ) - land_use_area_id: Mapped[UUID | None] = mapped_column( - ForeignKey( - "hame.land_use_area.id", name="land_use_area_id_fkey", ondelete="CASCADE" - ), - index=True, - ) - other_area_id: Mapped[UUID | None] = mapped_column( - ForeignKey("hame.other_area.id", name="other_area_id_fkey", ondelete="CASCADE"), - index=True, - ) - line_id: Mapped[UUID | None] = mapped_column( - ForeignKey("hame.line.id", name="line_id_fkey", ondelete="CASCADE"), index=True - ) - point_id: Mapped[UUID | None] = mapped_column( - ForeignKey("hame.point.id", name="point_id_fkey", ondelete="CASCADE"), - index=True, - ) - plan_regulation_id: Mapped[UUID | None] = mapped_column( - ForeignKey( - "hame.plan_regulation.id", - name="plan_regulation_id_fkey", - ondelete="CASCADE", - ), - index=True, - ) - plan_proposition_id: Mapped[UUID | None] = mapped_column( - ForeignKey( - "hame.plan_proposition.id", - name="plan_proposition_id_fkey", - ondelete="CASCADE", - ), - index=True, - ) - - plan: Mapped[Plan | None] = relationship(back_populates="lifecycle_dates") - land_use_area: Mapped[LandUseArea | None] = relationship( - back_populates="lifecycle_dates" - ) - other_area: Mapped[OtherArea | None] = relationship( - back_populates="lifecycle_dates" - ) - line: Mapped[Line | None] = relationship(back_populates="lifecycle_dates") - point: Mapped[Point | None] = relationship(back_populates="lifecycle_dates") - plan_regulation: Mapped[PlanRegulation | None] = relationship( - back_populates="lifecycle_dates" - ) - plan_proposition: Mapped[PlanProposition | None] = relationship( - back_populates="lifecycle_dates" - ) - # Let's load all the codes for objects joined. - lifecycle_status: Mapped[LifeCycleStatus] = relationship( - back_populates="lifecycle_dates", lazy="joined" - ) - # Let's add backreference to allow lazy loading from this side. - event_dates: Mapped[list[EventDate]] = relationship( - back_populates="lifecycle_date", - lazy="joined", - cascade="all, delete-orphan", - passive_deletes=True, - ) - - starting_at: Mapped[datetime] - ending_at: Mapped[datetime | None] - - -class EventDate(VersionedBase): - """Tapahtuman päivämäärät - - Jokaisessa elinkaaritilassa voi olla tiettyjä tapahtumia. Liitetään tapahtuma - sille sallittuun elinkaaritilaan. Tapahtuman päivämäärien tulee olla aina - elinkaaritilan päivämäärien välissä. - """ - - __tablename__ = "event_date" - - lifecycle_date_id: Mapped[UUID] = mapped_column( - ForeignKey( - "hame.lifecycle_date.id", name="lifecycle_date_id_fkey", ondelete="CASCADE" - ), - index=True, - ) - decision_id: Mapped[UUID | None] = mapped_column( - ForeignKey( - "codes.name_of_plan_case_decision.id", - name="name_of_plan_case_decision_id_fkey", - ) - ) - processing_event_id: Mapped[UUID | None] = mapped_column( - ForeignKey( - "codes.type_of_processing_event.id", name="type_of_processing_event_fkey" - ) - ) - interaction_event_id: Mapped[UUID | None] = mapped_column( - ForeignKey( - "codes.type_of_interaction_event.id", name="type_of_interaction_event_fkey" - ) - ) - - # Let's load all the codes for objects joined. - lifecycle_date: Mapped[LifeCycleDate] = relationship( - back_populates="event_dates", lazy="joined" - ) - decision: Mapped[NameOfPlanCaseDecision] = relationship( - back_populates="event_dates", lazy="joined" - ) - processing_event: Mapped[TypeOfProcessingEvent] = relationship( - back_populates="event_dates", lazy="joined" - ) - interaction_event: Mapped[TypeOfInteractionEvent] = relationship( - back_populates="event_dates", lazy="joined" - ) - - starting_at: Mapped[datetime] - ending_at: Mapped[datetime | None] diff --git a/database/triggers.py b/database/triggers.py index 96d0779..76846cc 100644 --- a/database/triggers.py +++ b/database/triggers.py @@ -25,14 +25,6 @@ def all_subclasses(cls: type) -> set[type]: # and cls.__table__.name != "" ] -# All tables that inherit PlanBase -tables_with_lifecycle_date = [ - klass.__tablename__ - for _, klass in inspect.getmembers(models, inspect.isclass) - if inspect.getmodule(klass) == models - and issubclass(klass, models.PlanBase) - and hasattr(klass, "__tablename__") # Ignore classes without __tablename__ -] # Regulations and propositions link to plan via plan regulation group plan_regulation_tables = ["plan_regulation", "plan_proposition"] @@ -158,103 +150,6 @@ def generate_modified_at_triggers() -> tuple[list[PGTrigger], list[PGFunction]]: return trgs, [trgfunc] -def generate_new_object_add_lifecycle_date_triggers() -> tuple[ - list[PGTrigger], list[PGFunction] -]: - trgs = [] - trgfunc_signature = "trgfunc_new_object_add_lifecycle_date()" - trgfunc_definition = """ - RETURNS TRIGGER AS $$ - BEGIN - EXECUTE format( - $query$ - INSERT INTO hame.lifecycle_date - (lifecycle_status_id, %I, starting_at) - VALUES - ($1, $2, CURRENT_TIMESTAMP) - $query$, - TG_TABLE_NAME || '_id' - ) USING NEW.lifecycle_status_id, NEW.id; - RETURN NEW; - END; - $$ language 'plpgsql' - """ - trgfunc = PGFunction( - schema="hame", signature=trgfunc_signature, definition=trgfunc_definition - ) - - for table in tables_with_lifecycle_date: - trg_signature = f"trg_new_{table}_add_lifecycle_date" - trg_definition = f""" - AFTER INSERT ON {table} - FOR EACH ROW - EXECUTE FUNCTION hame.{trgfunc_signature} - """ - trg = PGTrigger( - schema="hame", - signature=trg_signature, - on_entity=f"hame.{table}", - is_constraint=False, - definition=trg_definition, - ) - trgs.append(trg) - - return trgs, [trgfunc] - - -def generate_new_lifecycle_date_triggers() -> tuple[list[PGTrigger], list[PGFunction]]: - trgs = [] - trgfunc_signature = "trgfunc_new_lifecycle_date()" - trgfunc_definition = """ - RETURNS TRIGGER AS $$ - BEGIN - EXECUTE format( - $query$ - INSERT INTO hame.lifecycle_date - (lifecycle_status_id, %I, starting_at) - VALUES - ($1, $2, CURRENT_TIMESTAMP) - $query$, - TG_TABLE_NAME || '_id' - ) USING NEW.lifecycle_status_id, NEW.id; - EXECUTE format( - $query$ - UPDATE hame.lifecycle_date - SET ending_at=CURRENT_TIMESTAMP - WHERE %I = $1 - AND ending_at IS NULL - AND lifecycle_status_id = $2 - $query$, - TG_TABLE_NAME || '_id' - ) USING NEW.id, OLD.lifecycle_status_id; - RETURN NEW; - END; - $$ language 'plpgsql' - """ - trgfunc = PGFunction( - schema="hame", signature=trgfunc_signature, definition=trgfunc_definition - ) - - for table in tables_with_lifecycle_date: - trg_signature = f"trg_{table}_new_lifecycle_date" - trg_definition = f""" - BEFORE UPDATE ON {table} - FOR EACH ROW - WHEN (NEW.lifecycle_status_id <> OLD.lifecycle_status_id) - EXECUTE FUNCTION hame.{trgfunc_signature} - """ - trg = PGTrigger( - schema="hame", - signature=trg_signature, - on_entity=f"hame.{table}", - is_constraint=False, - definition=trg_definition, - ) - trgs.append(trg) - - return trgs, [trgfunc] - - def generate_update_lifecycle_status_triggers() -> tuple[ list[PGTrigger], list[PGFunction] ]: diff --git a/database/validation.py b/database/validation.py deleted file mode 100644 index 24cbbac..0000000 --- a/database/validation.py +++ /dev/null @@ -1,163 +0,0 @@ -from alembic_utils.pg_function import PGFunction -from alembic_utils.pg_trigger import PGTrigger - -trgfunc_validate_lifecycle_date = PGFunction( - schema="hame", - signature="trgfunc_lifecycle_date_validate_dates()", - definition=""" - RETURNS TRIGGER AS $$ - BEGIN - IF ( - NEW.starting_at IS NOT NULL AND - NEW.ending_at IS NOT NULL AND - NEW.starting_at > NEW.ending_at - ) IS TRUE - THEN - RAISE EXCEPTION 'Status starting date % after ending date %', - NEW.starting_at, NEW.ending_at - USING HINT = 'Status ending date must be after starting date.'; - END IF; - RETURN NEW; - END; - $$ language 'plpgsql' - """, -) - -trg_validate_lifecycle_date = PGTrigger( - schema="hame", - signature="trg_lifecycle_date_validate_dates", - on_entity="hame.lifecycle_date", - definition=""" - BEFORE INSERT OR UPDATE ON lifecycle_date - FOR EACH ROW - EXECUTE FUNCTION hame.trgfunc_lifecycle_date_validate_dates() - """, -) - -trgfunc_validate_event_date = PGFunction( - schema="hame", - signature="trgfunc_event_date_validate_dates()", - definition=""" - RETURNS TRIGGER AS $$ - BEGIN - IF ( - NEW.starting_at IS NOT NULL AND - NEW.ending_at IS NOT NULL AND - NEW.starting_at > NEW.ending_at - ) IS TRUE - THEN - RAISE EXCEPTION 'Event starting date % after ending date %', - NEW.starting_at, NEW.ending_at - USING HINT = 'Event ending date must be after starting date.'; - END IF; - RETURN NEW; - END; - $$ language 'plpgsql' - """, -) - -trg_validate_event_date = PGTrigger( - schema="hame", - signature="trg_event_date_validate_dates", - on_entity="hame.event_date", - definition=""" - BEFORE INSERT OR UPDATE ON event_date - FOR EACH ROW - EXECUTE FUNCTION hame.trgfunc_event_date_validate_dates() - """, -) - -trgfunc_validate_event_date_inside_status_date = PGFunction( - schema="hame", - signature="trgfunc_event_date_validate_inside_status_date()", - definition=""" - RETURNS TRIGGER AS $$ - DECLARE - status_starting_at TIMESTAMP WITH TIME ZONE; - status_ending_at TIMESTAMP WITH TIME ZONE; - BEGIN - SELECT starting_at, ending_at INTO status_starting_at, status_ending_at - FROM hame.lifecycle_date - WHERE NEW.lifecycle_date_id = hame.lifecycle_date.id; - IF ( - -- Does the event start before status starts? - ( - NEW.starting_at < status_starting_at - ) OR - -- Missing event ending date means event is instantaneous. Only - -- events with ending date have a duration. Both must have ending - -- date specified to check if event ends after status ends. - ( - NEW.ending_at IS NOT NULL AND status_ending_at IS NOT NULL AND - NEW.ending_at > status_ending_at - ) - ) IS TRUE - THEN - RAISE EXCEPTION 'Event dates % - % outside status dates % - %', - NEW.starting_at, NEW.ending_at, status_starting_at, status_ending_at - USING HINT = 'Event cannot be outside lifecycle status dates.'; - END IF; - RETURN NEW; - END; - $$ language 'plpgsql' - """, -) - -trg_validate_event_date_inside_status_date = PGTrigger( - schema="hame", - signature="trg_event_date_validate_inside_status_date", - on_entity="hame.event_date", - definition=""" - BEFORE INSERT OR UPDATE ON event_date - FOR EACH ROW - EXECUTE FUNCTION hame.trgfunc_event_date_validate_inside_status_date() - """, -) - -trgfunc_validate_event_type = PGFunction( - schema="hame", - signature="trgfunc_event_date_validate_type()", - definition=""" - RETURNS TRIGGER AS $$ - DECLARE - status_id UUID; - association_id UUID; - BEGIN - SELECT lifecycle_status_id INTO status_id - FROM - hame.lifecycle_date - WHERE - NEW.lifecycle_date_id = lifecycle_date.id; - SELECT id INTO association_id - FROM - codes.allowed_events - WHERE - lifecycle_status_id = status_id AND ( - (NEW.decision_id IS NOT NULL AND - name_of_plan_case_decision_id = NEW.decision_id) OR - (NEW.processing_event_id IS NOT NULL AND - type_of_processing_event_id = NEW.processing_event_id) OR - (NEW.interaction_event_id IS NOT NULL AND - type_of_interaction_event_id = NEW.interaction_event_id) - ); - IF association_id IS NULL - THEN - RAISE EXCEPTION 'Wrong event type for status' - USING HINT = 'This event type cannot be added to this lifecycle status.'; - END IF; - RETURN NEW; - END; - $$ language 'plpgsql' - """, -) - -trg_validate_event_type = PGTrigger( - schema="hame", - signature="trg_event_date_validate_type", - on_entity="hame.event_date", - definition=""" - BEFORE INSERT OR UPDATE ON event_date - FOR EACH ROW - EXECUTE FUNCTION hame.trgfunc_event_date_validate_type() - """, -) diff --git a/lambdas/ryhti_client/lambda_function.py b/lambdas/ryhti_client/lambda_function.py index 7654981..b47b59c 100644 --- a/lambdas/ryhti_client/lambda_function.py +++ b/lambdas/ryhti_client/lambda_function.py @@ -389,15 +389,13 @@ def handler( elif event_type is Action.GET_PLAN_MATTERS: # just return the JSON to the user - LOGGER.info("Formatting plan matter data...") - plan_matters = database_client.get_plan_matters() - response_title = "Returning serialized plan matters from database." + response_title = "Serializing plan matters not implemented yet." LOGGER.info(response_title) lambda_response = Response( - statusCode=200, + statusCode=405, body=ResponseBody( title=response_title, - details=cast("dict", plan_matters), + details={"error": "Not implemented yet."}, ryhti_responses={}, ), ) @@ -441,57 +439,26 @@ def handler( ) elif event_type is Action.VALIDATE_PLAN_MATTERS: - LOGGER.info("Authenticating to X-road Ryhti API...") - client.xroad_ryhti_authenticate() - # Documents are exported separately from plan matter. Also, they need to be - # present in Ryhti *before* plan matter is validated or created. - # - # Therefore, let's export all the documents right away, and update them to - # the latest version when needed. Otherwise, the plan matter would never be - # valid. Only upload documents for those plans that have permanent plan - # identifiers. - # 1) If changed documents exist, upload documents - LOGGER.info("Checking and updating plan documents for plans...") - plan_documents = client.upload_plan_documents() - LOGGER.info("Marking documents exported...") - database_client.set_plan_documents(plan_documents) - # 2) Validate plan matters with identifiers with X-Road API - LOGGER.info("Validating plan matters for plans...") - responses = client.validate_plan_matters() - # 3) Save and return plan matter validation data - LOGGER.info("Saving plan matter validation data for plans...") - save_details = database_client.save_plan_matter_validation_responses( - responses - ) + # Plan matter support removed from Arho. See git history for previous + # implementation. lambda_response = Response( - statusCode=200, + statusCode=405, body=ResponseBody( - title="Plan matter validations run.", - details=save_details, # type: ignore[typeddict-item] - ryhti_responses=responses, + title="Plan matter validation not implemented.", + details={}, # type: ignore[typeddict-item] + ryhti_responses={}, ), ) elif event_type is Action.POST_PLAN_MATTERS: - LOGGER.info("Authenticating to X-road Ryhti API...") - client.xroad_ryhti_authenticate() - # 1) If changed documents exist, upload documents - LOGGER.info("Checking and updating plan documents for plans...") - plan_documents = client.upload_plan_documents() - LOGGER.info("Marking documents exported...") - database_client.set_plan_documents(plan_documents) - # 2) Create or update Ryhti plan matters - LOGGER.info("POSTing plan matters...") - responses = client.post_plan_matters() - # 3) Save and return plan matter update responses - LOGGER.info("Saving plan matter POST data for posted plans...") - save_details = database_client.save_plan_matter_post_responses(responses) + # Plan matter support removed from Arho. See git history for previous + # implementation. lambda_response = Response( - statusCode=200, + statusCode=405, body=ResponseBody( - title="Plan matters POSTed.", - details=save_details, # type: ignore[typeddict-item] - ryhti_responses=responses, + title="Plan matter posting not implemented.", + details={}, # type: ignore[typeddict-item] + ryhti_responses={}, ), ) elif event_type is Action.COPY_PLAN: diff --git a/lambdas/ryhti_client/ryhti_client/database_client.py b/lambdas/ryhti_client/ryhti_client/database_client.py index d5d82ef..5e7537e 100644 --- a/lambdas/ryhti_client/ryhti_client/database_client.py +++ b/lambdas/ryhti_client/ryhti_client/database_client.py @@ -13,17 +13,6 @@ from sqlalchemy.orm import sessionmaker from database import base, codes, models -from database.codes import ( - NameOfPlanCaseDecision, - TypeOfDecisionMaker, - TypeOfInteractionEvent, - TypeOfProcessingEvent, - decisionmaker_by_status, - decisions_by_status, - get_code_uri, - interaction_events_by_status, - processing_events_by_status, -) from database.enums import AttributeValueDataType from ryhti_client.deserializer import ( Deserializer, @@ -35,12 +24,8 @@ Period, RyhtiAdditionalInformation, RyhtiAttributeValue, - RyhtiHandlingEvent, - RyhtiInteractionEvent, RyhtiPlan, - RyhtiPlanDecision, RyhtiPlanMatter, - RyhtiPlanMatterPhase, ) if TYPE_CHECKING: @@ -182,97 +167,6 @@ def get_date(self, datetime_value: datetime.datetime) -> str: """Returns isoformatted date for the given datetime in local timezone.""" return datetime_value.astimezone(LOCAL_TZ).date().isoformat() - def get_periods( - self, - dates_objects: list[models.LifeCycleDate] | list[models.EventDate], - datetimes: bool = True, - ) -> list[Period]: - """Returns the time periods of given date objects. Optionally, only dates instead - of datetimes may be returned. - """ - return [ - { - "begin": ( - self.get_isoformat_value_with_z(dates_object.starting_at) - if datetimes - else self.get_date(dates_object.starting_at) - ), - "end": ( - ( - self.get_isoformat_value_with_z(dates_object.ending_at) - if datetimes - else self.get_date(dates_object.ending_at) - ) - if dates_object.ending_at - else None - ), - } - for dates_object in dates_objects - ] - - def get_lifecycle_dates_for_status( - self, plan_base: models.PlanBase, status_value: str - ) -> list[models.LifeCycleDate]: - """Returns the plan lifecycle date objects for the desired status.""" - return [ - lifecycle_date - for lifecycle_date in plan_base.lifecycle_dates - if lifecycle_date.lifecycle_status.value == status_value - ] - - def get_lifecycle_periods( - self, plan_base: models.PlanBase, status_value: str, datetimes: bool = True - ) -> list[Period]: - """Returns the start and end datetimes of lifecycle status for object. Optionally, - only dates instead of datetimes may be returned. - - Note that for some lifecycle statuses, we may return multiple periods. - """ - lifecycle_dates = self.get_lifecycle_dates_for_status(plan_base, status_value) - return self.get_periods(lifecycle_dates, datetimes) - - def get_event_periods( - self, - lifecycle_date: models.LifeCycleDate, - event_class: type[codes.CodeBase], - event_value: str, - datetimes: bool = True, - ) -> list[Period]: - """Returns the start and end datetimes of events with desired class and value - linked to a lifecycle date object. Optionally, only dates instead of datetimes - may be returned. - - Note that if the event occurs multiple times, we may return multiple periods. - """ - - def class_and_value_match(event_date: models.EventDate) -> bool: - return bool( - ( - event_class is NameOfPlanCaseDecision - and event_date.decision - and event_date.decision.value == event_value - ) - or ( - event_class is TypeOfProcessingEvent - and event_date.processing_event - and event_date.processing_event.value == event_value - ) - or ( - event_class is TypeOfInteractionEvent - and event_date.interaction_event - and event_date.interaction_event.value == event_value - ) - ) - - event_dates = [ - date for date in lifecycle_date.event_dates if class_and_value_match(date) - ] - return self.get_periods(event_dates, datetimes) - - def get_last_period(self, periods: list[Period]) -> Period | None: - """Returns the last period in the list, or None if the list is empty.""" - return periods[-1] if periods else None - def serialize_date_period( self, date_start: datetime.date, date_end: datetime.date | None ) -> Period: @@ -786,223 +680,6 @@ def add_document_to_plan_dict( ) return plan_dictionary - def get_plan_decisions(self, plan: models.Plan) -> list[RyhtiPlanDecision]: - """Construct a list of Ryhti compatible plan decisions from plan in the local - database. - """ - decisions: list[RyhtiPlanDecision] = [] - # Decision name must correspond to the phase the plan is in. This requires - # mapping from lifecycle statuses to decision names. - print(decisions_by_status.get(plan.lifecycle_status.value, [])) - for decision_value in decisions_by_status.get(plan.lifecycle_status.value, []): - entry = RyhtiPlanDecision() - # TODO: Let's just have random uuid for now, on the assumption that each - # phase is only POSTed to ryhti once. If planners need to post and repost - # the same phase, script needs logic to check if the phase exists in Ryhti - # already before reposting. - entry["planDecisionKey"] = str(uuid4()) - entry["name"] = get_code_uri(NameOfPlanCaseDecision, decision_value) - entry["typeOfDecisionMaker"] = get_code_uri( - TypeOfDecisionMaker, - decisionmaker_by_status[plan.lifecycle_status.value], - ) - # Plan must be embedded in decision when POSTing! - entry["plans"] = [self.plan_dictionaries[plan.id]] - - try: - lifecycle_date = self.get_lifecycle_dates_for_status( - plan, plan.lifecycle_status.value - )[-1] - except IndexError: - # If we have an old plan with no phase data, we cannot add decisions. - LOGGER.warning( - "Error in plan! Current lifecycle status is missing start date." - ) - continue - # Decision date will be - # 1) decision date if found in database or, if not found, - # 2) start of the current status period is used as backup. - # TODO: Remove 2) once QGIS makes sure all necessary dates are filled in - # manually (or automatically). - period_of_decision = self.get_last_period( - self.get_event_periods( - lifecycle_date, - NameOfPlanCaseDecision, - decision_value, - datetimes=False, - ) - ) - period_of_current_status = self.get_periods( - [lifecycle_date], datetimes=False - )[-1] - # Decision has no duration: - entry["decisionDate"] = ( - period_of_decision["begin"] - if period_of_decision - else period_of_current_status["begin"] - ) - entry["dateOfDecision"] = entry["decisionDate"] - - decisions.append(entry) - return decisions - - def get_plan_handling_events(self, plan: models.Plan) -> list[RyhtiHandlingEvent]: - """Construct a list of Ryhti compatible plan handling events from plan in the - local database. - """ - events: list[RyhtiHandlingEvent] = [] - # Decision name must correspond to the phase the plan is in. This requires - # mapping from lifecycle statuses to decision names. - for event_value in processing_events_by_status.get( - plan.lifecycle_status.value, [] - ): - entry = RyhtiHandlingEvent() - # TODO: Let's just have random uuid for now, on the assumption that each - # phase is only POSTed to ryhti once. If planners need to post and repost - # the same phase, script needs logic to check if the phase exists in Ryhti - # already before reposting. - entry["handlingEventKey"] = str(uuid4()) - entry["handlingEventType"] = get_code_uri( - TypeOfProcessingEvent, event_value - ) - - try: - lifecycle_date = self.get_lifecycle_dates_for_status( - plan, plan.lifecycle_status.value - )[-1] - except IndexError: - # If we have an old plan with no phase data, we cannot add any events. - LOGGER.warning( - "Error in plan! Current lifecycle status is missing start date." - ) - continue - # Handling event date will be - # 1) handling event date if found in database or, if not found, - # 2) start of the current status period is used as backup. - # TODO: Remove 2) once QGIS makes sure all necessary dates are filled in - # manually (or automatically). - period_of_handling_event = self.get_last_period( - self.get_event_periods( - lifecycle_date, TypeOfProcessingEvent, event_value, datetimes=False - ) - ) - period_of_current_status = self.get_periods( - [lifecycle_date], datetimes=False - )[-1] - # Handling event has no duration: - entry["eventTime"] = ( - period_of_handling_event["begin"] - if period_of_handling_event - else period_of_current_status["begin"] - ) - entry["cancelled"] = False - - events.append(entry) - return events - - def get_interaction_events(self, plan: models.Plan) -> list[RyhtiInteractionEvent]: - """Construct a list of Ryhti compatible interaction events from plan in the local - database. - """ - events: list[RyhtiInteractionEvent] = [] - # Decision name must correspond to the phase the plan is in. This requires - # mapping from lifecycle statuses to decision names. - for event_value in interaction_events_by_status.get( - plan.lifecycle_status.value, [] - ): - entry = RyhtiInteractionEvent() - # TODO: Let's just have random uuid for now, on the assumption that each - # phase is only POSTed to ryhti once. If planners need to post and repost - # the same phase, script needs logic to check if the phase exists in Ryhti - # already before reposting. - entry["interactionEventKey"] = str(uuid4()) - entry["interactionEventType"] = get_code_uri( - TypeOfInteractionEvent, event_value - ) - - try: - lifecycle_date = self.get_lifecycle_dates_for_status( - plan, plan.lifecycle_status.value - )[-1] - except IndexError: - # If we have an old plan with no phase data, we cannot add any events. - LOGGER.warning( - "Error in plan! Current lifecycle status is missing start date." - ) - continue - # Interaction event period will be - # 1) interaction event period if found in database or, if not found, - # 2) 30 days at start of the current status period (nähtävilläoloaika) are - # used as backup. - # TODO: Remove 2) once QGIS makes sure all necessary dates are filled in - # manually (or automatically). - period_of_interaction_event = self.get_last_period( - self.get_event_periods( - lifecycle_date, TypeOfInteractionEvent, event_value - ) - ) - period_of_current_status = self.get_periods([lifecycle_date])[-1] - entry["eventTime"] = ( - period_of_interaction_event - if period_of_interaction_event - else { - "begin": (period_of_current_status["begin"]), - "end": ( - datetime.datetime.fromisoformat( - period_of_current_status["begin"] - ) - + datetime.timedelta(days=30) - ) - .isoformat() - .replace("+00:00", "Z"), - } - ) - - events.append(entry) - return events - - def get_plan_matter_phases(self, plan: models.Plan) -> list[RyhtiPlanMatterPhase]: - """Construct a list of Ryhti compatible plan matter phases from plan in the local - database. - - Currently, we only return the *current* phase, because our database does *not* - save plan history between phases. However, we could return multiple phases or - multiple decisions in the future, if there is a need to POST all the dates - saved in the lifecycle_dates table. - - TODO: perhaps we will have to return multiple phases, if there may be multiple - decision or multiple processing events in this phase. However, if we are only - returning one phase per phase, then let's just return one phase. Simple, isn't - it? - """ - phase = RyhtiPlanMatterPhase() - # TODO: Let's just have random uuid for now, on the assumption that each phase - # is only POSTed to ryhti once. If planners need to post and repost the same - # phase, script needs logic to check if the phase exists in Ryhti already before - # reposting. - # - # However, such logic may not be forthcoming, if multiple phases with - # the same lifecycle status are allowed?? - phase["planMatterPhaseKey"] = str(uuid4()) - # Always post phase and plan with the same status. - phase["lifeCycleStatus"] = self.plan_dictionaries[plan.id]["lifeCycleStatus"] - phase["geographicalArea"] = self.plan_dictionaries[plan.id]["geographicalArea"] - - # TODO: currently, the API spec only allows for one plan decision per phase, - # for reasons unknown. Therefore, let's pick the first possible decision in - # each phase. - plan_decisions = self.get_plan_decisions(plan) - phase["planDecision"] = plan_decisions[0] if plan_decisions else None - # TODO: currently, the API spec only allows for one plan handling event per - # phase, for reasons unknown. Therefore, let's pick the first possible event in - # each phase. - handling_events = self.get_plan_handling_events(plan) - phase["handlingEvent"] = handling_events[0] if handling_events else None - interaction_events = self.get_interaction_events(plan) - phase["interactionEvents"] = interaction_events if interaction_events else None - - return [phase] - def get_source_datas(self, plan: models.Plan) -> list[dict]: """Construct a list of Ryhti compatible source datas from plan in the local database. @@ -1011,74 +688,10 @@ def get_source_datas(self, plan: models.Plan) -> list[dict]: return [] def get_plan_matter(self, plan: models.Plan) -> RyhtiPlanMatter: - """Construct a dict of single Ryhti compatible plan matter from plan in the local - database. - """ - ryhti_plan_matter = RyhtiPlanMatter() - - plan_matter = plan.plan_matter - - # TODO: permanentPlanIdentifier should be mandatory in this stage - ryhti_plan_matter["permanentPlanIdentifier"] = ( - plan_matter.permanent_plan_identifier - ) - - # Plan type has to be proper URI (not just value) here, *unlike* when only - # validating plan. Go figure. - ryhti_plan_matter["planType"] = plan_matter.plan_type.uri - # For reasons unknown, name is needed for plan matter but not for plan. Plan - # only contains description, and only in one language. - # TODO: name should be mandatory in this stage - ryhti_plan_matter["name"] = self.format_language_string_value(plan_matter.name) - - # we should only have one pending period. If there are several, pick last - dates_of_initiation = self.get_last_period( - self.get_lifecycle_periods(plan, self.pending_status_value, datetimes=False) - ) - ryhti_plan_matter["timeOfInitiation"] = ( - dates_of_initiation["begin"] if dates_of_initiation else None - ) - # Hooray, unlike plan, the plan *matter* description allows multilanguage data! - ryhti_plan_matter["description"] = self.format_language_string_value( - plan_matter.description - ) - ryhti_plan_matter["producerPlanIdentifier"] = ( - plan_matter.producers_plan_identifier - ) - ryhti_plan_matter["caseIdentifiers"] = ( - [plan_matter.case_identifier] if plan_matter.case_identifier else [] - ) - ryhti_plan_matter["recordNumbers"] = ( - [plan_matter.record_number] if plan_matter.record_number else [] - ) - # Apparently Ryhti plans may cover multiple administrative areas, so the region - # identifier has to be embedded in a list. - ryhti_plan_matter["administrativeAreaIdentifiers"] = [ - ( - plan_matter.organisation.municipality.value - if plan_matter.organisation.municipality - else plan_matter.organisation.administrative_region.value - ) - ] - # We have no need of importing the digital origin code list as long as we are - # not digitizing old plans: - ryhti_plan_matter["digitalOrigin"] = ( - "http://uri.suomi.fi/codelist/rytj/RY_DigitaalinenAlkupera/code/01" - ) - # TODO: kaava-asian liitteet - # are these different from plan annexes? why? how?? - # plan_matter["matterAnnexes"] = self.get_plan_matter_annexes(plan) - # TODO: lähdeaineistot - # plan_matter["sourceDatas"] = self.get_source_datas(plan) - ryhti_plan_matter["planMatterPhases"] = self.get_plan_matter_phases(plan) - return ryhti_plan_matter + raise NotImplementedError def get_plan_matters(self) -> dict[DbId, RyhtiPlanMatter]: - """Construct a dict of Ryhti compatible plan matters from plans with - permanent identifiers in the local database. In case plan has no - permanent identifier, it is not included in the dict. - """ - return {plan.id: self.get_plan_matter(plan) for plan in self.plans.values()} + raise NotImplementedError def save_plan_validation_responses( self, responses: dict[DbId, RyhtiResponse] diff --git a/migrations/env.py b/migrations/env.py index 5316739..bd698b0 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -17,37 +17,15 @@ generate_add_plan_id_fkey_triggers, generate_instead_of_triggers_for_visualization_views, generate_modified_at_triggers, - generate_new_lifecycle_date_triggers, generate_new_lifecycle_status_triggers, - generate_new_object_add_lifecycle_date_triggers, generate_update_lifecycle_status_triggers, generate_created_at_triggers, generate_no_created_at_update_triggers ) -from database.validation import ( - trg_validate_event_date, - trg_validate_event_date_inside_status_date, - trg_validate_event_type, - trg_validate_lifecycle_date, - trgfunc_validate_event_date, - trgfunc_validate_event_date_inside_status_date, - trgfunc_validate_event_type, - trgfunc_validate_lifecycle_date, -) from database.views import views modified_at_trgs, modified_at_trgfuncs = generate_modified_at_triggers() -( - new_object_add_lifecycle_date_trgs, - new_object_add_lifecycle_date_trgfuncs, -) = generate_new_object_add_lifecycle_date_triggers() - -( - new_lifecycle_date_trgs, - new_lifecycle_date_trgfuncs, -) = generate_new_lifecycle_date_triggers() - ( update_lifecycle_status_trgs, update_lifecycle_status_trgfuncs, @@ -79,10 +57,6 @@ imported_triggers = ( modified_at_trgfuncs + modified_at_trgs - + new_object_add_lifecycle_date_trgfuncs - + new_object_add_lifecycle_date_trgs - + new_lifecycle_date_trgfuncs - + new_lifecycle_date_trgs + update_lifecycle_status_trgfuncs + update_lifecycle_status_trgs + new_lifecycle_status_trgfuncs @@ -95,14 +69,6 @@ + created_at_trgfuncs + no_created_at_update_trgs + no_created_at_update_trgfuncs - + [trgfunc_validate_lifecycle_date] - + [trg_validate_lifecycle_date] - + [trgfunc_validate_event_date] - + [trg_validate_event_date] - + [trgfunc_validate_event_date_inside_status_date] - + [trg_validate_event_date_inside_status_date] - + [trgfunc_validate_event_type] - + [trg_validate_event_type] ) register_entities(imported_triggers) diff --git a/migrations/versions/2026_02_04_1234-d83654421156_remove_lifecycle_history_tables.py b/migrations/versions/2026_02_04_1234-d83654421156_remove_lifecycle_history_tables.py new file mode 100644 index 0000000..abf9dce --- /dev/null +++ b/migrations/versions/2026_02_04_1234-d83654421156_remove_lifecycle_history_tables.py @@ -0,0 +1,819 @@ +"""Remove lifecycle history tables + +Revision ID: d83654421156 +Revises: 489b23cdab0a +Create Date: 2026-02-04 12:34:09.555102 + +""" + +from collections.abc import Sequence + +import geoalchemy2 +import sqlalchemy as sa +from alembic import op +from alembic_utils.pg_function import PGFunction +from alembic_utils.pg_trigger import PGTrigger +from sqlalchemy import text as sql_text +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "d83654421156" +down_revision: str | None = "489b23cdab0a" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + + + hame_event_date_trg_event_date_modified_at = PGTrigger( + schema="hame", + signature="trg_event_date_modified_at", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_modified_at()", + ) + op.drop_entity(hame_event_date_trg_event_date_modified_at) + + hame_lifecycle_date_trg_lifecycle_date_validate_dates = PGTrigger( + schema="hame", + signature="trg_lifecycle_date_validate_dates", + on_entity="hame.lifecycle_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.lifecycle_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_lifecycle_date_validate_dates()", + ) + op.drop_entity(hame_lifecycle_date_trg_lifecycle_date_validate_dates) + + hame_event_date_trg_event_date_validate_dates = PGTrigger( + schema="hame", + signature="trg_event_date_validate_dates", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_event_date_validate_dates()", + ) + op.drop_entity(hame_event_date_trg_event_date_validate_dates) + + hame_event_date_trg_event_date_validate_inside_status_date = PGTrigger( + schema="hame", + signature="trg_event_date_validate_inside_status_date", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_event_date_validate_inside_status_date()", + ) + op.drop_entity(hame_event_date_trg_event_date_validate_inside_status_date) + + hame_event_date_trg_event_date_validate_type = PGTrigger( + schema="hame", + signature="trg_event_date_validate_type", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_event_date_validate_type()", + ) + op.drop_entity(hame_event_date_trg_event_date_validate_type) + + hame_land_use_area_trg_new_land_use_area_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_land_use_area_add_lifecycle_date", + on_entity="hame.land_use_area", + is_constraint=False, + definition="AFTER INSERT ON hame.land_use_area FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.drop_entity(hame_land_use_area_trg_new_land_use_area_add_lifecycle_date) + + hame_line_trg_new_line_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_line_add_lifecycle_date", + on_entity="hame.line", + is_constraint=False, + definition="AFTER INSERT ON hame.line FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.drop_entity(hame_line_trg_new_line_add_lifecycle_date) + + hame_other_area_trg_new_other_area_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_other_area_add_lifecycle_date", + on_entity="hame.other_area", + is_constraint=False, + definition="AFTER INSERT ON hame.other_area FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.drop_entity(hame_other_area_trg_new_other_area_add_lifecycle_date) + + hame_plan_trg_new_plan_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_plan_add_lifecycle_date", + on_entity="hame.plan", + is_constraint=False, + definition="AFTER INSERT ON hame.plan FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.drop_entity(hame_plan_trg_new_plan_add_lifecycle_date) + + hame_plan_proposition_trg_new_plan_proposition_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_plan_proposition_add_lifecycle_date", + on_entity="hame.plan_proposition", + is_constraint=False, + definition="AFTER INSERT ON hame.plan_proposition FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.drop_entity(hame_plan_proposition_trg_new_plan_proposition_add_lifecycle_date) + + hame_plan_regulation_trg_new_plan_regulation_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_plan_regulation_add_lifecycle_date", + on_entity="hame.plan_regulation", + is_constraint=False, + definition="AFTER INSERT ON hame.plan_regulation FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.drop_entity(hame_plan_regulation_trg_new_plan_regulation_add_lifecycle_date) + + hame_land_use_area_trg_land_use_area_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_land_use_area_new_lifecycle_date", + on_entity="hame.land_use_area", + is_constraint=False, + definition="BEFORE UPDATE ON hame.land_use_area FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.drop_entity(hame_land_use_area_trg_land_use_area_new_lifecycle_date) + + hame_line_trg_line_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_line_new_lifecycle_date", + on_entity="hame.line", + is_constraint=False, + definition="BEFORE UPDATE ON hame.line FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.drop_entity(hame_line_trg_line_new_lifecycle_date) + + hame_other_area_trg_other_area_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_other_area_new_lifecycle_date", + on_entity="hame.other_area", + is_constraint=False, + definition="BEFORE UPDATE ON hame.other_area FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.drop_entity(hame_other_area_trg_other_area_new_lifecycle_date) + + hame_plan_trg_plan_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_plan_new_lifecycle_date", + on_entity="hame.plan", + is_constraint=False, + definition="BEFORE UPDATE ON hame.plan FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.drop_entity(hame_plan_trg_plan_new_lifecycle_date) + + hame_plan_proposition_trg_plan_proposition_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_plan_proposition_new_lifecycle_date", + on_entity="hame.plan_proposition", + is_constraint=False, + definition="BEFORE UPDATE ON hame.plan_proposition FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.drop_entity(hame_plan_proposition_trg_plan_proposition_new_lifecycle_date) + + hame_plan_regulation_trg_plan_regulation_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_plan_regulation_new_lifecycle_date", + on_entity="hame.plan_regulation", + is_constraint=False, + definition="BEFORE UPDATE ON hame.plan_regulation FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.drop_entity(hame_plan_regulation_trg_plan_regulation_new_lifecycle_date) + + hame_point_trg_new_point_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_point_add_lifecycle_date", + on_entity="hame.point", + is_constraint=False, + definition="AFTER INSERT ON hame.point FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.drop_entity(hame_point_trg_new_point_add_lifecycle_date) + + hame_point_trg_point_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_point_new_lifecycle_date", + on_entity="hame.point", + is_constraint=False, + definition="BEFORE UPDATE ON hame.point FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.drop_entity(hame_point_trg_point_new_lifecycle_date) + + hame_lifecycle_date_trg_lifecycle_date_created_at = PGTrigger( + schema="hame", + signature="trg_lifecycle_date_created_at", + on_entity="hame.lifecycle_date", + is_constraint=False, + definition="BEFORE INSERT ON hame.lifecycle_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_created_at()", + ) + op.drop_entity(hame_lifecycle_date_trg_lifecycle_date_created_at) + + hame_event_date_trg_event_date_created_at = PGTrigger( + schema="hame", + signature="trg_event_date_created_at", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_created_at()", + ) + op.drop_entity(hame_event_date_trg_event_date_created_at) + + hame_lifecycle_date_trg_lifecycle_date_001_no_created_at_update = PGTrigger( + schema="hame", + signature="trg_lifecycle_date_001_no_created_at_update", + on_entity="hame.lifecycle_date", + is_constraint=False, + definition="BEFORE UPDATE ON hame.lifecycle_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_no_created_at_update()", + ) + op.drop_entity(hame_lifecycle_date_trg_lifecycle_date_001_no_created_at_update) + + hame_event_date_trg_event_date_001_no_created_at_update = PGTrigger( + schema="hame", + signature="trg_event_date_001_no_created_at_update", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_no_created_at_update()", + ) + op.drop_entity(hame_event_date_trg_event_date_001_no_created_at_update) + + hame_trgfunc_lifecycle_date_validate_dates = PGFunction( + schema="hame", + signature="trgfunc_lifecycle_date_validate_dates()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n BEGIN\n IF (\n NEW.starting_at IS NOT NULL AND\n NEW.ending_at IS NOT NULL AND\n NEW.starting_at > NEW.ending_at\n ) IS TRUE\n THEN\n RAISE EXCEPTION 'Status starting date % after ending date %',\n NEW.starting_at, NEW.ending_at\n USING HINT = 'Status ending date must be after starting date.';\n END IF;\n RETURN NEW;\n END;\n $function$", + ) + op.drop_entity(hame_trgfunc_lifecycle_date_validate_dates) + + hame_trgfunc_event_date_validate_dates = PGFunction( + schema="hame", + signature="trgfunc_event_date_validate_dates()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n BEGIN\n IF (\n NEW.starting_at IS NOT NULL AND\n NEW.ending_at IS NOT NULL AND\n NEW.starting_at > NEW.ending_at\n ) IS TRUE\n THEN\n RAISE EXCEPTION 'Event starting date % after ending date %',\n NEW.starting_at, NEW.ending_at\n USING HINT = 'Event ending date must be after starting date.';\n END IF;\n RETURN NEW;\n END;\n $function$", + ) + op.drop_entity(hame_trgfunc_event_date_validate_dates) + + hame_trgfunc_event_date_validate_inside_status_date = PGFunction( + schema="hame", + signature="trgfunc_event_date_validate_inside_status_date()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n DECLARE\n status_starting_at TIMESTAMP WITH TIME ZONE;\n status_ending_at TIMESTAMP WITH TIME ZONE;\n BEGIN\n SELECT starting_at, ending_at INTO status_starting_at, status_ending_at\n FROM hame.lifecycle_date\n WHERE NEW.lifecycle_date_id = hame.lifecycle_date.id;\n IF (\n -- Does the event start before status starts?\n (\n NEW.starting_at < status_starting_at\n ) OR\n -- Missing event ending date means event is instantaneous. Only\n -- events with ending date have a duration. Both must have ending\n -- date specified to check if event ends after status ends.\n (\n NEW.ending_at IS NOT NULL AND status_ending_at IS NOT NULL AND\n NEW.ending_at > status_ending_at\n )\n ) IS TRUE\n THEN\n RAISE EXCEPTION 'Event dates % - % outside status dates % - %',\n NEW.starting_at, NEW.ending_at, status_starting_at, status_ending_at\n USING HINT = 'Event cannot be outside lifecycle status dates.';\n END IF;\n RETURN NEW;\n END;\n $function$", + ) + op.drop_entity(hame_trgfunc_event_date_validate_inside_status_date) + + hame_trgfunc_event_date_validate_type = PGFunction( + schema="hame", + signature="trgfunc_event_date_validate_type()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n DECLARE\n status_id UUID;\n association_id UUID;\n BEGIN\n SELECT lifecycle_status_id INTO status_id\n FROM\n hame.lifecycle_date\n WHERE\n NEW.lifecycle_date_id = lifecycle_date.id;\n SELECT id INTO association_id\n FROM\n codes.allowed_events\n WHERE\n lifecycle_status_id = status_id AND (\n (NEW.decision_id IS NOT NULL AND\n name_of_plan_case_decision_id = NEW.decision_id) OR\n (NEW.processing_event_id IS NOT NULL AND\n type_of_processing_event_id = NEW.processing_event_id) OR\n (NEW.interaction_event_id IS NOT NULL AND\n type_of_interaction_event_id = NEW.interaction_event_id)\n );\n IF association_id IS NULL\n THEN\n RAISE EXCEPTION 'Wrong event type for status'\n USING HINT = 'This event type cannot be added to this lifecycle status.';\n END IF;\n RETURN NEW;\n END;\n $function$", + ) + op.drop_entity(hame_trgfunc_event_date_validate_type) + + hame_trgfunc_new_object_add_lifecycle_date = PGFunction( + schema="hame", + signature="trgfunc_new_object_add_lifecycle_date()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n BEGIN\n EXECUTE format(\n $query$\n INSERT INTO hame.lifecycle_date\n (lifecycle_status_id, %I, starting_at)\n VALUES\n ($1, $2, CURRENT_TIMESTAMP)\n $query$,\n TG_TABLE_NAME || '_id'\n ) USING NEW.lifecycle_status_id, NEW.id;\n RETURN NEW;\n END;\n $function$", + ) + op.drop_entity(hame_trgfunc_new_object_add_lifecycle_date) + + hame_trgfunc_new_lifecycle_date = PGFunction( + schema="hame", + signature="trgfunc_new_lifecycle_date()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n BEGIN\n EXECUTE format(\n $query$\n INSERT INTO hame.lifecycle_date\n (lifecycle_status_id, %I, starting_at)\n VALUES\n ($1, $2, CURRENT_TIMESTAMP)\n $query$,\n TG_TABLE_NAME || '_id'\n ) USING NEW.lifecycle_status_id, NEW.id;\n EXECUTE format(\n $query$\n UPDATE hame.lifecycle_date\n SET ending_at=CURRENT_TIMESTAMP\n WHERE %I = $1\n AND ending_at IS NULL\n AND lifecycle_status_id = $2\n $query$,\n TG_TABLE_NAME || '_id'\n ) USING NEW.id, OLD.lifecycle_status_id;\n RETURN NEW;\n END;\n $function$", + ) + op.drop_entity(hame_trgfunc_new_lifecycle_date) + + hame_lifecycle_date_trg_lifecycle_date_modified_at = PGTrigger( + schema="hame", + signature="trg_lifecycle_date_modified_at", + on_entity="hame.lifecycle_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.lifecycle_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_modified_at()", + ) + op.drop_entity(hame_lifecycle_date_trg_lifecycle_date_modified_at) + + op.drop_index( + op.f("ix_hame_event_date_lifecycle_date_id"), + table_name="event_date", + schema="hame", + ) + op.drop_table("event_date", schema="hame") + op.drop_index( + op.f("ix_hame_lifecycle_date_land_use_area_id"), + table_name="lifecycle_date", + schema="hame", + ) + op.drop_index( + op.f("ix_hame_lifecycle_date_lifecycle_status_id"), + table_name="lifecycle_date", + schema="hame", + ) + op.drop_index( + op.f("ix_hame_lifecycle_date_line_id"), + table_name="lifecycle_date", + schema="hame", + ) + op.drop_index( + op.f("ix_hame_lifecycle_date_other_area_id"), + table_name="lifecycle_date", + schema="hame", + ) + op.drop_index( + op.f("ix_hame_lifecycle_date_plan_id"), + table_name="lifecycle_date", + schema="hame", + ) + op.drop_index( + op.f("ix_hame_lifecycle_date_plan_proposition_id"), + table_name="lifecycle_date", + schema="hame", + ) + op.drop_index( + op.f("ix_hame_lifecycle_date_plan_regulation_id"), + table_name="lifecycle_date", + schema="hame", + ) + op.drop_index( + op.f("ix_hame_lifecycle_date_point_id"), + table_name="lifecycle_date", + schema="hame", + ) + op.drop_table("lifecycle_date", schema="hame") + + + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + + + op.create_table( + "lifecycle_date", + sa.Column( + "lifecycle_status_id", sa.UUID(), autoincrement=False, nullable=False + ), + sa.Column("plan_id", sa.UUID(), autoincrement=False, nullable=True), + sa.Column("plan_regulation_id", sa.UUID(), autoincrement=False, nullable=True), + sa.Column("plan_proposition_id", sa.UUID(), autoincrement=False, nullable=True), + sa.Column( + "starting_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=False, + ), + sa.Column( + "ending_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=True, + ), + sa.Column( + "id", + sa.UUID(), + server_default=sa.text("gen_random_uuid()"), + autoincrement=False, + nullable=False, + ), + sa.Column( + "created_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=True, + ), + sa.Column( + "modified_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=True, + ), + sa.Column("land_use_area_id", sa.UUID(), autoincrement=False, nullable=True), + sa.Column("other_area_id", sa.UUID(), autoincrement=False, nullable=True), + sa.Column("line_id", sa.UUID(), autoincrement=False, nullable=True), + sa.Column("point_id", sa.UUID(), autoincrement=False, nullable=True), + sa.Column("creator", sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column("modifier", sa.VARCHAR(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint( + ["land_use_area_id"], + ["hame.land_use_area.id"], + name="land_use_area_id_fkey", + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["lifecycle_status_id"], + ["codes.lifecycle_status.id"], + name="plan_lifecycle_status_id_fkey", + ), + sa.ForeignKeyConstraint( + ["line_id"], ["hame.line.id"], name="line_id_fkey", ondelete="CASCADE" + ), + sa.ForeignKeyConstraint( + ["other_area_id"], + ["hame.other_area.id"], + name="other_area_id_fkey", + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["plan_id"], ["hame.plan.id"], name="plan_id_fkey", ondelete="CASCADE" + ), + sa.ForeignKeyConstraint( + ["plan_proposition_id"], + ["hame.plan_proposition.id"], + name="plan_proposition_id_fkey", + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["plan_regulation_id"], + ["hame.plan_regulation.id"], + name="plan_regulation_id_fkey", + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["point_id"], ["hame.point.id"], name="point_id_fkey", ondelete="CASCADE" + ), + sa.PrimaryKeyConstraint("id", name="lifecycle_date_pkey"), + schema="hame", + postgresql_ignore_search_path=False, + ) + op.create_index( + op.f("ix_hame_lifecycle_date_point_id"), + "lifecycle_date", + ["point_id"], + unique=False, + schema="hame", + ) + op.create_index( + op.f("ix_hame_lifecycle_date_plan_regulation_id"), + "lifecycle_date", + ["plan_regulation_id"], + unique=False, + schema="hame", + ) + op.create_index( + op.f("ix_hame_lifecycle_date_plan_proposition_id"), + "lifecycle_date", + ["plan_proposition_id"], + unique=False, + schema="hame", + ) + op.create_index( + op.f("ix_hame_lifecycle_date_plan_id"), + "lifecycle_date", + ["plan_id"], + unique=False, + schema="hame", + ) + op.create_index( + op.f("ix_hame_lifecycle_date_other_area_id"), + "lifecycle_date", + ["other_area_id"], + unique=False, + schema="hame", + ) + op.create_index( + op.f("ix_hame_lifecycle_date_line_id"), + "lifecycle_date", + ["line_id"], + unique=False, + schema="hame", + ) + op.create_index( + op.f("ix_hame_lifecycle_date_lifecycle_status_id"), + "lifecycle_date", + ["lifecycle_status_id"], + unique=False, + schema="hame", + ) + op.create_index( + op.f("ix_hame_lifecycle_date_land_use_area_id"), + "lifecycle_date", + ["land_use_area_id"], + unique=False, + schema="hame", + ) + op.create_table( + "event_date", + sa.Column("lifecycle_date_id", sa.UUID(), autoincrement=False, nullable=False), + sa.Column("decision_id", sa.UUID(), autoincrement=False, nullable=True), + sa.Column("processing_event_id", sa.UUID(), autoincrement=False, nullable=True), + sa.Column( + "interaction_event_id", sa.UUID(), autoincrement=False, nullable=True + ), + sa.Column( + "starting_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=False, + ), + sa.Column( + "ending_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=True, + ), + sa.Column( + "id", + sa.UUID(), + server_default=sa.text("gen_random_uuid()"), + autoincrement=False, + nullable=False, + ), + sa.Column( + "created_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=True, + ), + sa.Column( + "modified_at", + postgresql.TIMESTAMP(timezone=True), + autoincrement=False, + nullable=True, + ), + sa.Column("creator", sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column("modifier", sa.VARCHAR(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint( + ["decision_id"], + ["codes.name_of_plan_case_decision.id"], + name=op.f("name_of_plan_case_decision_id_fkey"), + ), + sa.ForeignKeyConstraint( + ["interaction_event_id"], + ["codes.type_of_interaction_event.id"], + name=op.f("type_of_interaction_event_fkey"), + ), + sa.ForeignKeyConstraint( + ["lifecycle_date_id"], + ["hame.lifecycle_date.id"], + name=op.f("lifecycle_date_id_fkey"), + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["processing_event_id"], + ["codes.type_of_processing_event.id"], + name=op.f("type_of_processing_event_fkey"), + ), + sa.PrimaryKeyConstraint("id", name=op.f("event_date_pkey")), + schema="hame", + ) + op.create_index( + op.f("ix_hame_event_date_lifecycle_date_id"), + "event_date", + ["lifecycle_date_id"], + unique=False, + schema="hame", + ) + + hame_trgfunc_new_lifecycle_date = PGFunction( + schema="hame", + signature="trgfunc_new_lifecycle_date()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n BEGIN\n EXECUTE format(\n $query$\n INSERT INTO hame.lifecycle_date\n (lifecycle_status_id, %I, starting_at)\n VALUES\n ($1, $2, CURRENT_TIMESTAMP)\n $query$,\n TG_TABLE_NAME || '_id'\n ) USING NEW.lifecycle_status_id, NEW.id;\n EXECUTE format(\n $query$\n UPDATE hame.lifecycle_date\n SET ending_at=CURRENT_TIMESTAMP\n WHERE %I = $1\n AND ending_at IS NULL\n AND lifecycle_status_id = $2\n $query$,\n TG_TABLE_NAME || '_id'\n ) USING NEW.id, OLD.lifecycle_status_id;\n RETURN NEW;\n END;\n $function$", + ) + op.create_entity(hame_trgfunc_new_lifecycle_date) + + hame_trgfunc_new_object_add_lifecycle_date = PGFunction( + schema="hame", + signature="trgfunc_new_object_add_lifecycle_date()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n BEGIN\n EXECUTE format(\n $query$\n INSERT INTO hame.lifecycle_date\n (lifecycle_status_id, %I, starting_at)\n VALUES\n ($1, $2, CURRENT_TIMESTAMP)\n $query$,\n TG_TABLE_NAME || '_id'\n ) USING NEW.lifecycle_status_id, NEW.id;\n RETURN NEW;\n END;\n $function$", + ) + op.create_entity(hame_trgfunc_new_object_add_lifecycle_date) + + hame_trgfunc_event_date_validate_type = PGFunction( + schema="hame", + signature="trgfunc_event_date_validate_type()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n DECLARE\n status_id UUID;\n association_id UUID;\n BEGIN\n SELECT lifecycle_status_id INTO status_id\n FROM\n hame.lifecycle_date\n WHERE\n NEW.lifecycle_date_id = lifecycle_date.id;\n SELECT id INTO association_id\n FROM\n codes.allowed_events\n WHERE\n lifecycle_status_id = status_id AND (\n (NEW.decision_id IS NOT NULL AND\n name_of_plan_case_decision_id = NEW.decision_id) OR\n (NEW.processing_event_id IS NOT NULL AND\n type_of_processing_event_id = NEW.processing_event_id) OR\n (NEW.interaction_event_id IS NOT NULL AND\n type_of_interaction_event_id = NEW.interaction_event_id)\n );\n IF association_id IS NULL\n THEN\n RAISE EXCEPTION 'Wrong event type for status'\n USING HINT = 'This event type cannot be added to this lifecycle status.';\n END IF;\n RETURN NEW;\n END;\n $function$", + ) + op.create_entity(hame_trgfunc_event_date_validate_type) + + hame_trgfunc_event_date_validate_inside_status_date = PGFunction( + schema="hame", + signature="trgfunc_event_date_validate_inside_status_date()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n DECLARE\n status_starting_at TIMESTAMP WITH TIME ZONE;\n status_ending_at TIMESTAMP WITH TIME ZONE;\n BEGIN\n SELECT starting_at, ending_at INTO status_starting_at, status_ending_at\n FROM hame.lifecycle_date\n WHERE NEW.lifecycle_date_id = hame.lifecycle_date.id;\n IF (\n -- Does the event start before status starts?\n (\n NEW.starting_at < status_starting_at\n ) OR\n -- Missing event ending date means event is instantaneous. Only\n -- events with ending date have a duration. Both must have ending\n -- date specified to check if event ends after status ends.\n (\n NEW.ending_at IS NOT NULL AND status_ending_at IS NOT NULL AND\n NEW.ending_at > status_ending_at\n )\n ) IS TRUE\n THEN\n RAISE EXCEPTION 'Event dates % - % outside status dates % - %',\n NEW.starting_at, NEW.ending_at, status_starting_at, status_ending_at\n USING HINT = 'Event cannot be outside lifecycle status dates.';\n END IF;\n RETURN NEW;\n END;\n $function$", + ) + op.create_entity(hame_trgfunc_event_date_validate_inside_status_date) + + hame_trgfunc_event_date_validate_dates = PGFunction( + schema="hame", + signature="trgfunc_event_date_validate_dates()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n BEGIN\n IF (\n NEW.starting_at IS NOT NULL AND\n NEW.ending_at IS NOT NULL AND\n NEW.starting_at > NEW.ending_at\n ) IS TRUE\n THEN\n RAISE EXCEPTION 'Event starting date % after ending date %',\n NEW.starting_at, NEW.ending_at\n USING HINT = 'Event ending date must be after starting date.';\n END IF;\n RETURN NEW;\n END;\n $function$", + ) + op.create_entity(hame_trgfunc_event_date_validate_dates) + + hame_trgfunc_lifecycle_date_validate_dates = PGFunction( + schema="hame", + signature="trgfunc_lifecycle_date_validate_dates()", + definition="returns trigger\n LANGUAGE plpgsql\nAS $function$\n BEGIN\n IF (\n NEW.starting_at IS NOT NULL AND\n NEW.ending_at IS NOT NULL AND\n NEW.starting_at > NEW.ending_at\n ) IS TRUE\n THEN\n RAISE EXCEPTION 'Status starting date % after ending date %',\n NEW.starting_at, NEW.ending_at\n USING HINT = 'Status ending date must be after starting date.';\n END IF;\n RETURN NEW;\n END;\n $function$", + ) + op.create_entity(hame_trgfunc_lifecycle_date_validate_dates) + + hame_event_date_trg_event_date_001_no_created_at_update = PGTrigger( + schema="hame", + signature="trg_event_date_001_no_created_at_update", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_no_created_at_update()", + ) + op.create_entity(hame_event_date_trg_event_date_001_no_created_at_update) + + hame_lifecycle_date_trg_lifecycle_date_001_no_created_at_update = PGTrigger( + schema="hame", + signature="trg_lifecycle_date_001_no_created_at_update", + on_entity="hame.lifecycle_date", + is_constraint=False, + definition="BEFORE UPDATE ON hame.lifecycle_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_no_created_at_update()", + ) + op.create_entity(hame_lifecycle_date_trg_lifecycle_date_001_no_created_at_update) + + hame_event_date_trg_event_date_created_at = PGTrigger( + schema="hame", + signature="trg_event_date_created_at", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_created_at()", + ) + op.create_entity(hame_event_date_trg_event_date_created_at) + + hame_lifecycle_date_trg_lifecycle_date_created_at = PGTrigger( + schema="hame", + signature="trg_lifecycle_date_created_at", + on_entity="hame.lifecycle_date", + is_constraint=False, + definition="BEFORE INSERT ON hame.lifecycle_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_created_at()", + ) + op.create_entity(hame_lifecycle_date_trg_lifecycle_date_created_at) + + hame_point_trg_point_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_point_new_lifecycle_date", + on_entity="hame.point", + is_constraint=False, + definition="BEFORE UPDATE ON hame.point FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.create_entity(hame_point_trg_point_new_lifecycle_date) + + hame_point_trg_new_point_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_point_add_lifecycle_date", + on_entity="hame.point", + is_constraint=False, + definition="AFTER INSERT ON hame.point FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.create_entity(hame_point_trg_new_point_add_lifecycle_date) + + hame_plan_regulation_trg_plan_regulation_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_plan_regulation_new_lifecycle_date", + on_entity="hame.plan_regulation", + is_constraint=False, + definition="BEFORE UPDATE ON hame.plan_regulation FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.create_entity(hame_plan_regulation_trg_plan_regulation_new_lifecycle_date) + + hame_plan_proposition_trg_plan_proposition_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_plan_proposition_new_lifecycle_date", + on_entity="hame.plan_proposition", + is_constraint=False, + definition="BEFORE UPDATE ON hame.plan_proposition FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.create_entity(hame_plan_proposition_trg_plan_proposition_new_lifecycle_date) + + hame_plan_trg_plan_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_plan_new_lifecycle_date", + on_entity="hame.plan", + is_constraint=False, + definition="BEFORE UPDATE ON hame.plan FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.create_entity(hame_plan_trg_plan_new_lifecycle_date) + + hame_other_area_trg_other_area_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_other_area_new_lifecycle_date", + on_entity="hame.other_area", + is_constraint=False, + definition="BEFORE UPDATE ON hame.other_area FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.create_entity(hame_other_area_trg_other_area_new_lifecycle_date) + + hame_line_trg_line_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_line_new_lifecycle_date", + on_entity="hame.line", + is_constraint=False, + definition="BEFORE UPDATE ON hame.line FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.create_entity(hame_line_trg_line_new_lifecycle_date) + + hame_land_use_area_trg_land_use_area_new_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_land_use_area_new_lifecycle_date", + on_entity="hame.land_use_area", + is_constraint=False, + definition="BEFORE UPDATE ON hame.land_use_area FOR EACH ROW WHEN ((new.lifecycle_status_id <> old.lifecycle_status_id)) EXECUTE FUNCTION hame.trgfunc_new_lifecycle_date()", + ) + op.create_entity(hame_land_use_area_trg_land_use_area_new_lifecycle_date) + + hame_plan_regulation_trg_new_plan_regulation_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_plan_regulation_add_lifecycle_date", + on_entity="hame.plan_regulation", + is_constraint=False, + definition="AFTER INSERT ON hame.plan_regulation FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.create_entity(hame_plan_regulation_trg_new_plan_regulation_add_lifecycle_date) + + hame_plan_proposition_trg_new_plan_proposition_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_plan_proposition_add_lifecycle_date", + on_entity="hame.plan_proposition", + is_constraint=False, + definition="AFTER INSERT ON hame.plan_proposition FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.create_entity(hame_plan_proposition_trg_new_plan_proposition_add_lifecycle_date) + + hame_plan_trg_new_plan_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_plan_add_lifecycle_date", + on_entity="hame.plan", + is_constraint=False, + definition="AFTER INSERT ON hame.plan FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.create_entity(hame_plan_trg_new_plan_add_lifecycle_date) + + hame_other_area_trg_new_other_area_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_other_area_add_lifecycle_date", + on_entity="hame.other_area", + is_constraint=False, + definition="AFTER INSERT ON hame.other_area FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.create_entity(hame_other_area_trg_new_other_area_add_lifecycle_date) + + hame_line_trg_new_line_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_line_add_lifecycle_date", + on_entity="hame.line", + is_constraint=False, + definition="AFTER INSERT ON hame.line FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.create_entity(hame_line_trg_new_line_add_lifecycle_date) + + hame_land_use_area_trg_new_land_use_area_add_lifecycle_date = PGTrigger( + schema="hame", + signature="trg_new_land_use_area_add_lifecycle_date", + on_entity="hame.land_use_area", + is_constraint=False, + definition="AFTER INSERT ON hame.land_use_area FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_new_object_add_lifecycle_date()", + ) + op.create_entity(hame_land_use_area_trg_new_land_use_area_add_lifecycle_date) + + hame_event_date_trg_event_date_validate_type = PGTrigger( + schema="hame", + signature="trg_event_date_validate_type", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_event_date_validate_type()", + ) + op.create_entity(hame_event_date_trg_event_date_validate_type) + + hame_event_date_trg_event_date_validate_inside_status_date = PGTrigger( + schema="hame", + signature="trg_event_date_validate_inside_status_date", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_event_date_validate_inside_status_date()", + ) + op.create_entity(hame_event_date_trg_event_date_validate_inside_status_date) + + hame_event_date_trg_event_date_validate_dates = PGTrigger( + schema="hame", + signature="trg_event_date_validate_dates", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_event_date_validate_dates()", + ) + op.create_entity(hame_event_date_trg_event_date_validate_dates) + + hame_lifecycle_date_trg_lifecycle_date_validate_dates = PGTrigger( + schema="hame", + signature="trg_lifecycle_date_validate_dates", + on_entity="hame.lifecycle_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.lifecycle_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_lifecycle_date_validate_dates()", + ) + op.create_entity(hame_lifecycle_date_trg_lifecycle_date_validate_dates) + + hame_event_date_trg_event_date_modified_at = PGTrigger( + schema="hame", + signature="trg_event_date_modified_at", + on_entity="hame.event_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.event_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_modified_at()", + ) + op.create_entity(hame_event_date_trg_event_date_modified_at) + + hame_lifecycle_date_trg_lifecycle_date_modified_at = PGTrigger( + schema="hame", + signature="trg_lifecycle_date_modified_at", + on_entity="hame.lifecycle_date", + is_constraint=False, + definition="BEFORE INSERT OR UPDATE ON hame.lifecycle_date FOR EACH ROW EXECUTE FUNCTION hame.trgfunc_modified_at()", + ) + op.create_entity(hame_lifecycle_date_trg_lifecycle_date_modified_at) + + # ### end Alembic commands ### diff --git a/test/conftest.py b/test/conftest.py index e955628..3b20b01 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1484,139 +1484,6 @@ def plan_map_instance( return temp_session_feature(instance) -# Date fixtures - - -@pytest.fixture -def lifecycle_date_instance( - temp_session_feature: ReturnSame[models.LifeCycleDate], - code_instance: codes.LifeCycleStatus, -) -> models.LifeCycleDate: - instance = models.LifeCycleDate( - lifecycle_status=code_instance, - starting_at=datetime(2024, 1, 1, tzinfo=LOCAL_TZ), - ending_at=datetime(2025, 1, 1, tzinfo=LOCAL_TZ), - ) - return temp_session_feature(instance) - - -@pytest.fixture -def pending_date_instance( - temp_session_feature: ReturnSame[models.LifeCycleDate], - plan_instance: codes.Plan, - pending_status_instance: codes.LifeCycleStatus, -) -> models.LifeCycleDate: - instance = models.LifeCycleDate( - plan=plan_instance, - lifecycle_status=pending_status_instance, - starting_at=datetime(2024, 1, 1, tzinfo=LOCAL_TZ), - ending_at=datetime(2024, 2, 1, tzinfo=LOCAL_TZ), - ) - return temp_session_feature(instance) - - -@pytest.fixture -def preparation_date_instance( - temp_session_feature: ReturnSame[models.LifeCycleDate], - plan_instance: codes.Plan, - preparation_status_instance: codes.LifeCycleStatus, -) -> models.LifeCycleDate: - instance = models.LifeCycleDate( - plan=plan_instance, - lifecycle_status=preparation_status_instance, - starting_at=datetime(2024, 2, 1, tzinfo=LOCAL_TZ), - ending_at=datetime(2024, 3, 1, tzinfo=LOCAL_TZ), - ) - return temp_session_feature(instance) - - -@pytest.fixture -def plan_proposal_date_instance( - temp_session_feature: ReturnSame[models.LifeCycleDate], - plan_instance: codes.Plan, - plan_proposal_status_instance: codes.LifeCycleStatus, -) -> models.LifeCycleDate: - instance = models.LifeCycleDate( - plan=plan_instance, - lifecycle_status=plan_proposal_status_instance, - starting_at=datetime(2024, 4, 1, tzinfo=LOCAL_TZ), - ending_at=datetime(2024, 5, 1, tzinfo=LOCAL_TZ), - ) - return temp_session_feature(instance) - - -@pytest.fixture -def approved_date_instance( - temp_session_feature: ReturnSame[models.LifeCycleDate], - plan_instance: codes.Plan, - approved_status_instance: codes.LifeCycleStatus, -) -> models.LifeCycleDate: - instance = models.LifeCycleDate( - plan=plan_instance, - lifecycle_status=approved_status_instance, - starting_at=datetime(2024, 4, 1, tzinfo=LOCAL_TZ), - ending_at=datetime(2024, 5, 1, tzinfo=LOCAL_TZ), - ) - return temp_session_feature(instance) - - -@pytest.fixture -def valid_date_instance( - temp_session_feature: ReturnSame[models.LifeCycleDate], - plan_instance: codes.Plan, - valid_status_instance: codes.LifeCycleStatus, -) -> models.LifeCycleDate: - instance = models.LifeCycleDate( - plan=plan_instance, - lifecycle_status=valid_status_instance, - starting_at=datetime(2024, 5, 1, tzinfo=LOCAL_TZ), - ) - return temp_session_feature(instance) - - -@pytest.fixture -def decision_date_instance( - temp_session_feature: ReturnSame[models.EventDate], - preparation_date_instance: models.LifeCycleDate, - participation_plan_presenting_for_public_decision: codes.NameOfPlanCaseDecision, -) -> models.EventDate: - instance = models.EventDate( - lifecycle_date=preparation_date_instance, - decision=participation_plan_presenting_for_public_decision, - starting_at=datetime(2024, 2, 5, tzinfo=LOCAL_TZ), - ) - return temp_session_feature(instance) - - -@pytest.fixture -def processing_event_date_instance( - temp_session_feature: ReturnSame[models.EventDate], - preparation_date_instance: models.LifeCycleDate, - participation_plan_presenting_for_public_event: codes.TypeOfProcessingEvent, -) -> models.EventDate: - instance = models.EventDate( - lifecycle_date=preparation_date_instance, - processing_event=participation_plan_presenting_for_public_event, - starting_at=datetime(2024, 2, 15, tzinfo=LOCAL_TZ), - ) - return temp_session_feature(instance) - - -@pytest.fixture -def interaction_event_date_instance( - temp_session_feature: ReturnSame[models.EventDate], - preparation_date_instance: models.LifeCycleDate, - presentation_to_the_public_interaction: codes.TypeOfInteractionEvent, -) -> models.EventDate: - instance = models.EventDate( - lifecycle_date=preparation_date_instance, - interaction_event=presentation_to_the_public_interaction, - starting_at=datetime(2024, 2, 15, tzinfo=LOCAL_TZ), - ending_at=datetime(2024, 2, 28, tzinfo=LOCAL_TZ), - ) - return temp_session_feature(instance) - - # Additional information fixtures @@ -1717,11 +1584,6 @@ def complete_test_plan( plan_proposal_requesting_for_opinions_event: codes.TypeOfProcessingEvent, presentation_to_the_public_interaction: codes.TypeOfInteractionEvent, decisionmaker_type: codes.TypeOfDecisionMaker, - pending_date_instance: models.LifeCycleDate, - preparation_date_instance: models.LifeCycleDate, - decision_date_instance: models.EventDate, - processing_event_date_instance: models.EventDate, - interaction_event_date_instance: models.EventDate, ) -> models.Plan: """Plan data that might be more or less complete, to be tested and validated with the Ryhti API. @@ -1730,11 +1592,6 @@ def complete_test_plan( linked to the plan in the database) to be committed to the database, and some dates for the plan lifecycle statuses to be set. """ - # In tests, we need known dates for the phases. Plan has a trigger-generated additional - # date for the preparation phase that we must delete before testing. - session.delete(plan_instance.lifecycle_dates[2]) - session.commit() - # Add the optional (nullable) relationships. We don't want them to be present in # all fixtures. plan_instance.legal_effects_of_master_plan.append( diff --git a/test/ryhti_client/test_copy_plan.py b/test/ryhti_client/test_copy_plan.py index 8843a03..159afd0 100644 --- a/test/ryhti_client/test_copy_plan.py +++ b/test/ryhti_client/test_copy_plan.py @@ -129,9 +129,7 @@ def test_copy_plan( assert copied_land_use_area_1 is not None assert copied_land_use_area_1.lifecycle_status.value == valid_status_instance.value assert copied_land_use_area_1.period_of_validity_start == period_of_validity_start - assert len(land_use_area_1.lifecycle_dates) == len( - copied_land_use_area_1.lifecycle_dates - ) + assert ( land_use_area_1.type_of_underground == copied_land_use_area_1.type_of_underground @@ -150,7 +148,7 @@ def test_copy_plan( ) assert copied_other_area_1 is not None assert copied_other_area_1.lifecycle_status.value == valid_status_instance.value - assert len(other_area_1.lifecycle_dates) == len(copied_other_area_1.lifecycle_dates) + assert other_area_1.type_of_underground == copied_other_area_1.type_of_underground # complete_test_plan fixture has no lines @@ -164,7 +162,7 @@ def test_copy_plan( ) assert copied_point_1 is not None assert copied_point_1.lifecycle_status.value == valid_status_instance.value - assert len(point_1.lifecycle_dates) == len(copied_point_1.lifecycle_dates) + assert point_1.type_of_underground == copied_point_1.type_of_underground # General regulation groups diff --git a/test/test_models.py b/test/test_models.py index a38cc10..985dafb 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -334,123 +334,6 @@ def test_document( session.flush() -def test_lifecycle_date( - session: Session, - lifecycle_date_instance: models.LifeCycleDate, - code_instance: codes.LifeCycleStatus, - plan_instance: models.Plan, - text_plan_regulation_instance: models.PlanRegulation, - plan_proposition_instance: models.PlanProposition, -) -> None: - # non-nullable lifecycle date relations - assert lifecycle_date_instance.lifecycle_status is code_instance - assert code_instance.lifecycle_dates == [lifecycle_date_instance] - # nullable lifecycle date relations - assert lifecycle_date_instance.plan is None - assert lifecycle_date_instance not in plan_instance.lifecycle_dates - lifecycle_date_instance.plan = plan_instance - assert lifecycle_date_instance.plan_regulation is None - assert lifecycle_date_instance not in text_plan_regulation_instance.lifecycle_dates - lifecycle_date_instance.plan_regulation = text_plan_regulation_instance - assert lifecycle_date_instance.plan_proposition is None - assert lifecycle_date_instance not in plan_proposition_instance.lifecycle_dates - lifecycle_date_instance.plan_proposition = plan_proposition_instance - session.flush() - - assert lifecycle_date_instance.plan is plan_instance - assert lifecycle_date_instance.plan_regulation is text_plan_regulation_instance - assert lifecycle_date_instance.plan_proposition is plan_proposition_instance - assert lifecycle_date_instance in plan_instance.lifecycle_dates - assert lifecycle_date_instance in text_plan_regulation_instance.lifecycle_dates - assert lifecycle_date_instance in plan_proposition_instance.lifecycle_dates - - -def test_decision_date( - session: Session, - preparation_date_instance: models.LifeCycleDate, - decision_date_instance: models.EventDate, - participation_plan_presenting_for_public_decision: codes.NameOfPlanCaseDecision, -) -> None: - # non-nullable decision date relations - assert decision_date_instance.lifecycle_date is preparation_date_instance - assert preparation_date_instance.event_dates == [decision_date_instance] - - # nullable decision date relations - assert ( - decision_date_instance.decision - == participation_plan_presenting_for_public_decision - ) - assert participation_plan_presenting_for_public_decision.event_dates == [ - decision_date_instance - ] - - -def test_processing_event_date( - session: Session, - preparation_date_instance: models.LifeCycleDate, - processing_event_date_instance: models.EventDate, - participation_plan_presenting_for_public_event: codes.TypeOfProcessingEvent, -) -> None: - # non-nullable decision date relations - assert processing_event_date_instance.lifecycle_date is preparation_date_instance - assert preparation_date_instance.event_dates == [processing_event_date_instance] - - # nullable decision date relations - assert ( - processing_event_date_instance.processing_event - == participation_plan_presenting_for_public_event - ) - assert participation_plan_presenting_for_public_event.event_dates == [ - processing_event_date_instance - ] - - -def test_interaction_event_date( - session: Session, - preparation_date_instance: models.LifeCycleDate, - interaction_event_date_instance: models.EventDate, - presentation_to_the_public_interaction: codes.TypeOfProcessingEvent, -) -> None: - # non-nullable decision date relations - assert interaction_event_date_instance.lifecycle_date is preparation_date_instance - assert preparation_date_instance.event_dates == [interaction_event_date_instance] - - # nullable decision date relations - assert ( - interaction_event_date_instance.interaction_event - == presentation_to_the_public_interaction - ) - assert presentation_to_the_public_interaction.event_dates == [ - interaction_event_date_instance - ] - - -@pytest.mark.parametrize( - "fixture_name", - [ - pytest.param("land_use_area_instance", id="land_use_area"), - pytest.param("line_instance", id="line"), - pytest.param("point_instance", id="point"), - pytest.param("other_area_instance", id="other_area"), - pytest.param("plan_proposition_instance", id="plan_proposition"), - pytest.param("empty_value_plan_regulation_instance", id="plan_regulation"), - ], -) -def test_cascade_delete_of_lifecycle_dates_using_orm( - session: Session, request: pytest.FixtureRequest, fixture_name: str -) -> None: - """Test that deleting a plan object cascades to the lifecycle date when using ORM. - This makes sure that the cascade options are configured correctly in the SqlAlchemy models. - """ - plan_base_object = request.getfixturevalue(fixture_name) - assert len(plan_base_object.lifecycle_dates) == 1 - lifecycle_date = plan_base_object.lifecycle_dates[0] - session.delete(plan_base_object) - session.commit() - - assert inspect(lifecycle_date).was_deleted - - @pytest.mark.parametrize( ("parent_fixture_name", "child_fixture_name", "child_collection_name"), [ @@ -495,59 +378,6 @@ def test_cascade_delete_using_orm( assert inspect(child_object).was_deleted -@pytest.mark.parametrize( - "fixture_name", - [ - pytest.param("land_use_area_instance", id="land_use_area"), - pytest.param("line_instance", id="line"), - pytest.param("point_instance", id="point"), - pytest.param("other_area_instance", id="other_area"), - pytest.param("plan_proposition_instance", id="plan_proposition"), - pytest.param("empty_value_plan_regulation_instance", id="plan_regulation"), - ], -) -def test_cascade_delete_of_lifecycle_dates_using_db( - session: Session, request: pytest.FixtureRequest, fixture_name: str -) -> None: - """Test that deleting a plan object cascades to the lifecycle date when using raw SQL. - This makes sure that the ON DELETE cascade options are configured correctly for the foreign keys in the database. - """ - plan_base_object = request.getfixturevalue(fixture_name) - lifecycle_object = plan_base_object.lifecycle_dates[0] - - connection = session.connection().connection - cur = connection.cursor() - - def lifecycle_date_exists() -> bool: - cur.execute( - sql.SQL("SELECT EXISTS (SELECT id FROM {table} WHERE id=%s)").format( - table=sql.Identifier( - lifecycle_object.__table__.schema, lifecycle_object.__table__.name - ) - ), - (lifecycle_object.id,), - ) - row = cur.fetchone() - return bool(row[0]) if row else False - - def delete_plan_base_object() -> None: - cur.execute( - sql.SQL("DELETE FROM {table} WHERE id=%s").format( - table=sql.Identifier( - plan_base_object.__table__.schema, plan_base_object.__table__.name - ) - ), - (plan_base_object.id,), - ) - - assert lifecycle_date_exists() - delete_plan_base_object() - assert not lifecycle_date_exists() - - cur.close() - connection.rollback() - - @pytest.mark.parametrize( ("parent_fixture_name", "child_fixture_name"), [ diff --git a/test/test_ryhti_client.py b/test/test_ryhti_client.py index 221ed47..c57f398 100644 --- a/test/test_ryhti_client.py +++ b/test/test_ryhti_client.py @@ -470,7 +470,6 @@ def client_with_plan_data_in_proposal_phase( dba_connection_string: str, complete_test_plan: models.Plan, plan_proposal_status_instance: codes.LifeCycleStatus, - plan_proposal_date_instance: models.LifeCycleDate, ) -> RyhtiClient: """Return RyhtiClient that has plan data in proposal phase read in. @@ -483,11 +482,6 @@ def client_with_plan_data_in_proposal_phase( session.add(plan_proposal_status_instance) complete_test_plan.lifecycle_status = plan_proposal_status_instance session.commit() - # Delete the new additional date for proposal phase that just appeared. Our fixture already - # has a proposal date. - session.refresh(complete_test_plan) - session.delete(complete_test_plan.lifecycle_dates[2]) - session.commit() # Let's mock production x-road with gispo organization client here. database_client = DatabaseClient(dba_connection_string) @@ -837,178 +831,3 @@ def test_upload_unchanged_plan_documents( assert plan_instance.documents[0].exported_at == old_exported_at assert plan_instance.documents[0].exported_file_key == old_file_key assert plan_instance.documents[0].exported_file_etag == old_file_etag - - -def test_get_plan_matters( - client_with_plan_with_permanent_identifier_and_documents: RyhtiClient, - plan_instance: models.Plan, - desired_plan_matter_dict: dict, -) -> None: - """Check that correct JSON structure is generated for plan matter. This requires that - the client has already fetched a permanent identifer for the plan. - """ - plan_matter_dictionaries = client_with_plan_with_permanent_identifier_and_documents.database_client.get_plan_matters() - plan_matter = plan_matter_dictionaries[plan_instance.id] - deepcompare( - plan_matter, - desired_plan_matter_dict, - ignore_keys=[ - "planMatterPhaseKey", - "handlingEventKey", - "interactionEventKey", - "planDecisionKey", - "planMapKey", - "attachmentDocumentKey", - "planReportKey", - "otherPlanMaterialKey", - "fileKey", - ], - ignore_order_for_keys=[ - "planRegulationGroupRelations", - "additionalInformations", - ], - ) - - -def test_validate_plan_matters( - client_with_plan_with_permanent_identifier_and_documents: RyhtiClient, - plan_instance: models.Plan, - mock_xroad_ryhti_validate_invalid: Callable, -) -> None: - """Check that JSON is posted and response received""" - responses = ( - client_with_plan_with_permanent_identifier_and_documents.validate_plan_matters() - ) - for plan_id, response in responses.items(): - assert plan_id == plan_instance.id - assert response["errors"] == [ - { - "ruleId": mock_matter_rule, - "message": mock_matter_error_string, - "instance": mock_matter_instance, - } - ] - - -def test_save_plan_matter_validation_responses( - session: Session, - client_with_plan_with_permanent_identifier_and_documents: RyhtiClient, - plan_instance: models.Plan, - mock_xroad_ryhti_validate_invalid: Callable, -) -> None: - """Check that Ryhti X-Road validation error is saved to database.""" - responses = ( - client_with_plan_with_permanent_identifier_and_documents.validate_plan_matters() - ) - message = client_with_plan_with_permanent_identifier_and_documents.database_client.save_plan_matter_validation_responses( - responses - ) - session.refresh(plan_instance) - assert plan_instance.validated_at - assert plan_instance.validation_errors == next(iter(responses.values()))["errors"] - - -def test_post_new_plan_matters( - client_with_plan_with_permanent_identifier_and_documents: RyhtiClient, - plan_instance: models.Plan, - mock_xroad_ryhti_post_new_plan_matter: Callable, -) -> None: - """Check that JSON is posted and response received when the plan matter does not - exist in Ryhti yet. - """ - responses = ( - client_with_plan_with_permanent_identifier_and_documents.post_plan_matters() - ) - for plan_id, response in responses.items(): - assert plan_id == plan_instance.id - assert response["warnings"] - assert not response["errors"] - - -def test_save_new_plan_matter_post_responses( - session: Session, - client_with_plan_with_permanent_identifier_and_documents: RyhtiClient, - plan_instance: models.Plan, - mock_xroad_ryhti_post_new_plan_matter: Callable, -) -> None: - """Check that export time is saved to database.""" - responses = ( - client_with_plan_with_permanent_identifier_and_documents.post_plan_matters() - ) - message = client_with_plan_with_permanent_identifier_and_documents.database_client.save_plan_matter_post_responses( - responses - ) - session.refresh(plan_instance) - assert plan_instance.exported_at - assert plan_instance.validation_errors == "Uusi kaava-asian vaihe on viety Ryhtiin." - - -def test_update_existing_plan_matters( - session: Session, - client_with_plan_with_permanent_identifier_and_documents_in_proposal_phase: RyhtiClient, - plan_instance: models.Plan, - mock_xroad_ryhti_update_existing_plan_matter: Callable, -) -> None: - """Check that JSON is posted and response received when the plan matter exists in Ryhti - and a new plan matter phase must be posted. - """ - responses = client_with_plan_with_permanent_identifier_and_documents_in_proposal_phase.post_plan_matters() - for plan_id, response in responses.items(): - assert plan_id == plan_instance.id - assert response["warnings"] - assert not response["errors"] - - -def test_save_update_existing_matter_post_responses( - session: Session, - client_with_plan_with_permanent_identifier_and_documents_in_proposal_phase: RyhtiClient, - plan_instance: models.Plan, - mock_xroad_ryhti_update_existing_plan_matter: Callable, -) -> None: - """Check that export time is saved to database.""" - responses = client_with_plan_with_permanent_identifier_and_documents_in_proposal_phase.post_plan_matters() - message = client_with_plan_with_permanent_identifier_and_documents_in_proposal_phase.database_client.save_plan_matter_post_responses( - responses - ) - session.refresh(plan_instance) - assert plan_instance.exported_at - assert plan_instance.validation_errors == "Uusi kaava-asian vaihe on viety Ryhtiin." - - -def test_update_existing_plan_matter_phase( - session: Session, - client_with_plan_with_permanent_identifier_and_documents: RyhtiClient, - plan_instance: models.Plan, - mock_xroad_ryhti_update_existing_plan_matter: Callable, -) -> None: - """Check that JSON is posted and response received when the plan matter and the plan matter - phase exist in Ryhti and the plan matter phase must be updated. - """ - responses = ( - client_with_plan_with_permanent_identifier_and_documents.post_plan_matters() - ) - for plan_id, response in responses.items(): - assert plan_id == plan_instance.id - assert response["warnings"] - assert not response["errors"] - - -def test_save_update_existing_matter_phase_post_responses( - session: Session, - client_with_plan_with_permanent_identifier_and_documents: RyhtiClient, - plan_instance: models.Plan, - mock_xroad_ryhti_update_existing_plan_matter: Callable, -) -> None: - """Check that export time is saved to database.""" - responses = ( - client_with_plan_with_permanent_identifier_and_documents.post_plan_matters() - ) - message = client_with_plan_with_permanent_identifier_and_documents.database_client.save_plan_matter_post_responses( - responses - ) - session.refresh(plan_instance) - assert plan_instance.exported_at - # TODO: switch to using correct message once Ryhti responds correctly. Currently, Ryhti - # claims a new phase is created every time the existing phase is updated. - # assert plan_instance.validation_errors == "Kaava-asian vaihe on päivitetty Ryhtiin." - assert plan_instance.validation_errors == "Uusi kaava-asian vaihe on viety Ryhtiin." diff --git a/test/test_services.py b/test/test_services.py index ccf8f65..4cf1c6b 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -503,393 +503,6 @@ def test_get_permanent_plan_identifier( conn.close() -@pytest.fixture -def get_single_plan_matter( - ryhti_client_url: str, - complete_test_plan: Plan, - another_test_plan: Plan, - desired_plan_matter_dict: dict, -) -> None: - """Get single plan matter JSON from lambda by id. Another plan in the database should - not be serialized. - - Getting plan matter should make lambda return http 200 OK (to indicate that - serialization has been run successfully), with the ryhti_responses dict empty, and - details dict containing the serialized plan matter. - - Formatting plan matter requires that a plan has a permanent identifier. - """ - payload = { - "action": "get_permanent_plan_identifiers", - "plan_uuid": complete_test_plan.id, - "save_json": True, - } - r = requests.post(ryhti_client_url, data=json.dumps(payload)) - # now the plan has permanent identifier, we can proceed: - payload = { - "action": "get_plan_matters", - "plan_uuid": complete_test_plan.id, - "save_json": True, - } - r = requests.post(ryhti_client_url, data=json.dumps(payload)) - data = r.json() - print(data) - assert data["statusCode"] == 200 - body = data["body"] - assert body["title"] == "Returning serialized plan matters from database." - # Check that other plan matter is NOT returned - assert len(body["details"]) == 1 - deepcompare( - body["details"][complete_test_plan.id], - desired_plan_matter_dict, - ignore_keys=[ - "planMatterPhaseKey", - "handlingEventKey", - "interactionEventKey", - "planDecisionKey", - "planMaps", # currently, we do not upload documents when getting plan matter json - "planAnnexes", # currently, we do not upload documents when getting plan matter json - "planReport", # currently, we do not upload documents when getting plan matter json - "otherPlanMaterials", # currently, we do not upload documents when getting plan matter json - "fileKey", - ], - ignore_order_for_keys=[ - "planRegulationGroupRelations", - "additionalInformations", - ], - ) - assert not body["ryhti_responses"] - - -def test_get_single_plan_matter( - get_single_plan_matter: None, main_db_params: ConnectionParameters -) -> None: - """Test the whole lambda endpoint with single_plan_matter""" - # getting plan JSON from lambda should not run validations - conn = psycopg.connect(**main_db_params) - try: - with conn.cursor() as cur: - # Check that plans are NOT validated - cur.execute("SELECT validated_at, validation_errors FROM hame.plan") - row = cur.fetchone() - assert row is not None - validation_date, errors = row - assert not validation_date - assert not errors - - row = cur.fetchone() - assert row is not None - validation_date, errors = row - assert not validation_date - assert not errors - finally: - conn.close() - - -@pytest.fixture -def validate_valid_plan_matter_in_preparation( - ryhti_client_url: str, complete_test_plan: Plan -) -> None: - """Validate a valid Ryhti plan and plan matter against the Ryhti API. This guarantees - that the Ryhti plan is formed according to spec and passes open Ryhti API validation. - - If the plan has a permanent plan identifier, the client proceeds to also validate - the plan matter. - - Since local tests or CI/CD cannot connect to X-Road servers, we validate the plan - *matter* against a Mock X-Road API that responds with 200 OK. Therefore, for the - X-Road APIs, this only guarantees that the lambda runs correctly, not that the plan - *matter* is formed according to spec. - - A valid plan should make lambda return http 200 OK (to indicate that the validation - has been run successfully), with the validation errors list empty and validation - warnings returned. - """ - payload = { - "action": "get_permanent_plan_identifiers", - "plan_uuid": complete_test_plan.id, - "save_json": True, - } - r = requests.post(ryhti_client_url, data=json.dumps(payload)) - # now the plan has permanent identifier, we can proceed: - payload = { - "action": "validate_plan_matters", - "plan_uuid": complete_test_plan.id, - "save_json": True, - } - r = requests.post(ryhti_client_url, data=json.dumps(payload)) - data = r.json() - print(data) - assert data["statusCode"] == 200 - body = data["body"] - assert body["title"] == "Plan matter validations run." - assert ( - body["details"][complete_test_plan.id] - == f"Plan matter validation successful for {complete_test_plan.id}!" - ) - assert body["ryhti_responses"][complete_test_plan.id]["status"] == 200 - assert body["ryhti_responses"][complete_test_plan.id]["warnings"] - assert not body["ryhti_responses"][complete_test_plan.id]["errors"] - - -def test_validate_valid_plan_matter_in_preparation( - validate_valid_plan_matter_in_preparation: None, - main_db_params: ConnectionParameters, -) -> None: - """Test the whole lambda endpoint with a valid plan and plan matter in preparation - stage. Plan is validated with public Ryhti API. Validate plan matter with mock - X-Road API. - - The mock X-Road should return a permanent identifier and report the plan matter - as valid. Also, validating plan matter should make sure that plan documents - are included in the plan matter. - """ - conn = psycopg.connect(**main_db_params) - try: - with conn.cursor() as cur: - cur.execute( - "SELECT p.id, validated_at, validation_errors, pm.permanent_plan_identifier " - "FROM hame.plan p JOIN hame.plan_matter pm " - "ON p.plan_matter_id = pm.id" - ) - row = cur.fetchone() - assert row is not None - exported_plan_id, validation_date, errors, permanent_plan_identifier = row - assert validation_date - assert errors == "Kaava-asia on validi ja sen voi viedä Ryhtiin." - assert permanent_plan_identifier == "MK-123456" - - # Document should be exported - cur.execute( - "SELECT plan_id, exported_at, exported_file_key FROM hame.document" - ) - row = cur.fetchone() - assert row is not None - plan_id, exported_at, exported_file_key = row - assert plan_id == exported_plan_id - assert exported_at - assert exported_file_key - finally: - conn.close() - - -@pytest.fixture -def post_plan_matters_in_preparation( - ryhti_client_url: str, complete_test_plan: Plan, another_test_plan: Plan -) -> None: - """POST all plans to the mock X-Road API. Plans need not be validated, but they need - to have permanent identifiers set. - - POSTing plans should make lambda return http 200 OK (to indicate that POSTs - have been run successfully), with the validation errors list empty and validation - warnings returned (if plan was valid) or validation errors (if plan was invalid). - """ - payload = { - "action": "get_permanent_plan_identifiers", - "plan_uuid": complete_test_plan.id, - "save_json": True, - } - r = requests.post(ryhti_client_url, data=json.dumps(payload)) - # now one plan has a permanent identifier, the other does not: - payload = {"action": "post_plan_matters", "save_json": True} - r = requests.post(ryhti_client_url, data=json.dumps(payload)) - data = r.json() - print(data) - assert data["statusCode"] == 200 - body = data["body"] - assert body["title"] == "Plan matters POSTed." - assert ( - body["details"][complete_test_plan.id] - == f"Plan matter or plan matter phase POST successful for {complete_test_plan.id}." - ) - assert ( - body["details"][another_test_plan.id] - == f"Plan {another_test_plan.id} had no permanent identifier. Could not create plan matter!" - ) - # Valid plan was posted - assert body["ryhti_responses"][complete_test_plan.id]["status"] == 201 - assert body["ryhti_responses"][complete_test_plan.id]["warnings"] - assert not body["ryhti_responses"][complete_test_plan.id]["errors"] - # Another plan had no identifier and has no ryhti response - assert another_test_plan.id not in body["ryhti_responses"] - - -def test_post_plan_matters_in_preparation( - post_plan_matters_in_preparation: None, main_db_params: ConnectionParameters -) -> None: - """Test the whole lambda endpoint with multiple plans and plan matters in preparation - stage. POST plan matters with mock X-Road API. - - The mock X-Road should accept POSTed plan matter and report the plan matter as being - created in Ryhti. The plan matter without identifier should not be exported. Also, - POSTing plan matter should make sure that plan documents are included in the plan matter. - """ - conn = psycopg.connect(**main_db_params) - try: - with conn.cursor() as cur: - cur.execute( - """ - SELECT - p.id, - validated_at, - validation_errors, - pm.permanent_plan_identifier, - exported_at - FROM hame.plan p JOIN hame.plan_matter pm - ON p.plan_matter_id = pm.id - ORDER BY p.modified_at DESC - """ - ) - # Exported plan should also be reported validated - row = cur.fetchone() - assert row is not None - ( - exported_plan_id, - validation_date, - errors, - permanent_plan_identifier, - exported_at, - ) = row - assert validation_date - assert errors == "Uusi kaava-asian vaihe on viety Ryhtiin." - assert permanent_plan_identifier == "MK-123456" - assert exported_at - # Check that other plan is NOT modified because it had no identifier - row = cur.fetchone() - assert row is not None - ( - _other_plan_id, - validation_date, - errors, - permanent_plan_identifier, - exported_at, - ) = row - assert not validation_date - assert not errors - assert not permanent_plan_identifier - assert not exported_at - # Document should be exported - cur.execute( - "SELECT plan_id, exported_at, exported_file_key FROM hame.document" - ) - row = cur.fetchone() - assert row is not None - plan_id, exported_at, exported_file_key = row - assert plan_id == exported_plan_id - assert exported_at - assert exported_file_key - finally: - conn.close() - - -@pytest.fixture -def post_valid_plan_matter_in_preparation( - ryhti_client_url: str, complete_test_plan: Plan, another_test_plan -) -> None: - """POST single valid plan to the mock X-Road API. Plan needs not be validated. - - A POSTed plan should make lambda return http 200 OK (to indicate that the POST - has been run successfully), with the validation errors list empty and validation - warnings returned. - """ - payload = { - "action": "get_permanent_plan_identifiers", - "plan_uuid": complete_test_plan.id, - "save_json": True, - } - r = requests.post(ryhti_client_url, data=json.dumps(payload)) - # now the plan has permanent identifier, we can proceed: - payload = { - "action": "post_plan_matters", - "plan_uuid": complete_test_plan.id, - "save_json": True, - } - r = requests.post(ryhti_client_url, data=json.dumps(payload)) - data = r.json() - print(data) - assert data["statusCode"] == 200 - body = data["body"] - assert body["title"] == "Plan matters POSTed." - # Check that other plan is NOT processed - assert len(body["details"]) == 1 - assert ( - body["details"][complete_test_plan.id] - == f"Plan matter or plan matter phase POST successful for {complete_test_plan.id}." - ) - assert len(body["ryhti_responses"]) == 1 - assert body["ryhti_responses"][complete_test_plan.id]["status"] == 201 - assert body["ryhti_responses"][complete_test_plan.id]["warnings"] - assert not body["ryhti_responses"][complete_test_plan.id]["errors"] - - -def test_post_valid_plan_matter_in_preparation( - post_valid_plan_matter_in_preparation: None, main_db_params: ConnectionParameters -) -> None: - """Test the whole lambda endpoint with a valid plan and plan matter in preparation - stage. POST plan matter with mock X-Road API. - - The mock X-Road should accept POSTed plan matter and report the plan matter as - being created in Ryhti. Also, POSTing plan matter should make sure that plan - documents are included in the plan matter. - """ - conn = psycopg.connect(**main_db_params) - try: - with conn.cursor() as cur: - cur.execute( - """ - SELECT - p.id, - validated_at, - validation_errors, - pm.permanent_plan_identifier, - exported_at - FROM hame.plan p JOIN hame.plan_matter pm - ON p.plan_matter_id = pm.id - ORDER BY p.modified_at DESC - """ - ) - # Exported plan should also be reported validated - row = cur.fetchone() - assert row is not None - ( - exported_plan_id, - validation_date, - errors, - permanent_plan_identifier, - exported_at, - ) = row - assert validation_date - assert errors == "Uusi kaava-asian vaihe on viety Ryhtiin." - assert permanent_plan_identifier == "MK-123456" - assert exported_at - # Check that other plan is NOT modified - row = cur.fetchone() - assert row is not None - ( - _other_plan_id, - validation_date, - errors, - permanent_plan_identifier, - exported_at, - ) = row - assert not validation_date - assert not errors - assert not permanent_plan_identifier - assert not exported_at - # Document should be exported - cur.execute( - "SELECT plan_id, exported_at, exported_file_key FROM hame.document" - ) - row = cur.fetchone() - assert row is not None - plan_id, exported_at, exported_file_key = row - assert plan_id == exported_plan_id - assert exported_at - assert exported_file_key - finally: - conn.close() - - @pytest.fixture def extra_data(plan_matter_instance: PlanMatter) -> dict: return {"name": "test_plan", "plan_matter_id": plan_matter_instance.id} diff --git a/test/test_triggers.py b/test/test_triggers.py index 22aa70f..07d494e 100644 --- a/test/test_triggers.py +++ b/test/test_triggers.py @@ -92,7 +92,6 @@ def test_modified_at_triggers( source_data_instance: models.SourceData, organisation_instance: models.Organisation, plan_map_instance: models.Document, - lifecycle_date_instance: models.LifeCycleDate, ) -> None: # Save old modified_at timestamps plan_old_modified_at = plan_instance.modified_at @@ -108,7 +107,6 @@ def test_modified_at_triggers( source_data_instance_old_modified_at = source_data_instance.modified_at organisation_instance_old_modified_at = organisation_instance.modified_at plan_map_instance_old_modified_at = plan_map_instance.modified_at - lifecycle_date_instance_old_modified_at = lifecycle_date_instance.modified_at # Edit tables to fire the triggers plan_instance.exported_at = datetime.now() @@ -122,7 +120,7 @@ def test_modified_at_triggers( source_data_instance.additional_information_uri = "http://test2.fi" organisation_instance.business_id = "foo" plan_map_instance.name = {"fin": "foo"} - lifecycle_date_instance.ending_at = datetime.now() + session.flush() session.refresh(plan_instance) session.refresh(land_use_area_instance) @@ -135,7 +133,6 @@ def test_modified_at_triggers( session.refresh(source_data_instance) session.refresh(organisation_instance) session.refresh(plan_map_instance) - session.refresh(lifecycle_date_instance) assert plan_instance.modified_at != plan_old_modified_at assert land_use_area_instance.modified_at != land_use_area_instance_old_modified_at @@ -154,178 +151,6 @@ def test_modified_at_triggers( assert source_data_instance.modified_at != source_data_instance_old_modified_at assert organisation_instance.modified_at != organisation_instance_old_modified_at assert plan_map_instance != plan_map_instance_old_modified_at - assert ( - lifecycle_date_instance.modified_at != lifecycle_date_instance_old_modified_at - ) - - -def test_new_object_add_lifecycle_date_triggers( - plan_instance: models.Plan, - text_plan_regulation_instance: models.PlanRegulation, - plan_proposition_instance: models.PlanProposition, - land_use_area_instance: models.LandUseArea, - other_area_instance: models.OtherArea, - line_instance: models.Line, - point_instance: models.Point, -) -> None: - assert plan_instance.lifecycle_dates - lifecycle_date = next(iter(plan_instance.lifecycle_dates)) - assert lifecycle_date.lifecycle_status == plan_instance.lifecycle_status - assert lifecycle_date.starting_at - assert not lifecycle_date.ending_at - - assert text_plan_regulation_instance.lifecycle_status - lifecycle_date = next(iter(text_plan_regulation_instance.lifecycle_dates)) - assert ( - lifecycle_date.lifecycle_status - == text_plan_regulation_instance.lifecycle_status - ) - assert lifecycle_date.starting_at - assert not lifecycle_date.ending_at - - assert plan_proposition_instance.lifecycle_dates - lifecycle_date = next(iter(plan_proposition_instance.lifecycle_dates)) - assert lifecycle_date.lifecycle_status == plan_proposition_instance.lifecycle_status - assert lifecycle_date.starting_at - assert not lifecycle_date.ending_at - - assert land_use_area_instance.lifecycle_dates - lifecycle_date = next(iter(land_use_area_instance.lifecycle_dates)) - assert lifecycle_date.lifecycle_status == land_use_area_instance.lifecycle_status - assert lifecycle_date.starting_at - assert not lifecycle_date.ending_at - - assert other_area_instance.lifecycle_dates - lifecycle_date = next(iter(other_area_instance.lifecycle_dates)) - assert lifecycle_date.lifecycle_status == other_area_instance.lifecycle_status - assert lifecycle_date.starting_at - assert not lifecycle_date.ending_at - - assert line_instance.lifecycle_dates - lifecycle_date = next(iter(line_instance.lifecycle_dates)) - assert lifecycle_date.lifecycle_status == line_instance.lifecycle_status - assert lifecycle_date.starting_at - assert not lifecycle_date.ending_at - - assert point_instance.lifecycle_dates - lifecycle_date = next(iter(point_instance.lifecycle_dates)) - assert lifecycle_date.lifecycle_status == point_instance.lifecycle_status - assert lifecycle_date.starting_at - assert not lifecycle_date.ending_at - - -def test_new_lifecycle_date_triggers( - session: Session, - plan_instance: models.Plan, - text_plan_regulation_instance: models.PlanRegulation, - plan_proposition_instance: models.PlanProposition, - land_use_area_instance: models.LandUseArea, - other_area_instance: models.OtherArea, - line_instance: models.Line, - point_instance: models.Point, - code_instance: codes.LifeCycleStatus, - another_code_instance: codes.LifeCycleStatus, -) -> None: - assert plan_instance.lifecycle_status_id != another_code_instance.id - assert text_plan_regulation_instance.lifecycle_status_id != another_code_instance.id - assert plan_proposition_instance.lifecycle_status_id != another_code_instance.id - assert land_use_area_instance.lifecycle_status_id != another_code_instance.id - assert other_area_instance.lifecycle_status_id != another_code_instance.id - assert line_instance.lifecycle_status_id != another_code_instance.id - assert point_instance.lifecycle_status_id != another_code_instance.id - - # Update lifecycle_statuses to populate starting_at fields - plan_instance.lifecycle_status = another_code_instance - text_plan_regulation_instance.lifecycle_status = another_code_instance - plan_proposition_instance.lifecycle_status = another_code_instance - land_use_area_instance.lifecycle_status = another_code_instance - other_area_instance.lifecycle_status = another_code_instance - line_instance.lifecycle_status = another_code_instance - point_instance.lifecycle_status = another_code_instance - session.flush() - - # Update again to populate ending_at fields - plan_instance.lifecycle_status = code_instance - text_plan_regulation_instance.lifecycle_status = code_instance - plan_proposition_instance.lifecycle_status = code_instance - land_use_area_instance.lifecycle_status = code_instance - other_area_instance.lifecycle_status = code_instance - line_instance.lifecycle_status = code_instance - point_instance.lifecycle_status = code_instance - session.flush() - session.refresh(plan_instance) - session.refresh(text_plan_regulation_instance) - session.refresh(plan_proposition_instance) - session.refresh(land_use_area_instance) - session.refresh(other_area_instance) - session.refresh(line_instance) - session.refresh(point_instance) - - # Get old and new entries in lifecycle_date table - def get_new_lifecycle_date(instance: models.PlanBase) -> models.LifeCycleDate: - return next( - date - for date in instance.lifecycle_dates - if date.lifecycle_status == code_instance - ) - - def get_old_lifecycle_date(instance: models.PlanBase) -> models.LifeCycleDate: - return next( - date - for date in instance.lifecycle_dates - if date.lifecycle_status == another_code_instance - ) - - plan_new_lifecycle_date = get_new_lifecycle_date(plan_instance) - plan_regulation_new_lifecycle_date = get_new_lifecycle_date( - text_plan_regulation_instance - ) - plan_proposition_new_lifecycle_date = get_new_lifecycle_date( - plan_proposition_instance - ) - land_use_area_new_lifecycle_date = get_new_lifecycle_date(land_use_area_instance) - other_area_new_lifecycle_date = get_new_lifecycle_date(other_area_instance) - line_new_lifecycle_date = get_new_lifecycle_date(line_instance) - point_new_lifecycle_date = get_new_lifecycle_date(point_instance) - plan_old_lifecycle_date = get_old_lifecycle_date(plan_instance) - plan_regulation_old_lifecycle_date = get_old_lifecycle_date( - text_plan_regulation_instance - ) - plan_proposition_old_lifecycle_date = get_old_lifecycle_date( - plan_proposition_instance - ) - land_use_area_old_lifecycle_date = get_old_lifecycle_date(land_use_area_instance) - other_area_old_lifecycle_date = get_old_lifecycle_date(other_area_instance) - line_old_lifecycle_date = get_old_lifecycle_date(line_instance) - point_old_lifecycle_date = get_old_lifecycle_date(point_instance) - - assert plan_new_lifecycle_date.lifecycle_status_id == code_instance.id - assert plan_new_lifecycle_date.starting_at is not None - assert plan_old_lifecycle_date.ending_at is not None - - assert plan_regulation_new_lifecycle_date.lifecycle_status_id == code_instance.id - assert plan_regulation_new_lifecycle_date.starting_at is not None - assert plan_regulation_old_lifecycle_date.ending_at is not None - - assert plan_proposition_new_lifecycle_date.lifecycle_status_id == code_instance.id - assert plan_proposition_new_lifecycle_date.starting_at is not None - assert plan_proposition_old_lifecycle_date.ending_at is not None - - assert land_use_area_instance.lifecycle_status_id == code_instance.id - assert land_use_area_new_lifecycle_date.starting_at is not None - assert land_use_area_old_lifecycle_date.ending_at is not None - - assert other_area_instance.lifecycle_status_id == code_instance.id - assert other_area_new_lifecycle_date.starting_at is not None - assert other_area_old_lifecycle_date.ending_at is not None - - assert line_instance.lifecycle_status_id == code_instance.id - assert line_new_lifecycle_date.starting_at is not None - assert line_old_lifecycle_date.ending_at is not None - - assert point_instance.lifecycle_status_id == code_instance.id - assert point_new_lifecycle_date.starting_at is not None - assert point_old_lifecycle_date.ending_at is not None def test_new_lifecycle_status_triggers( diff --git a/test/test_validation.py b/test/test_validation.py deleted file mode 100644 index 4e71904..0000000 --- a/test/test_validation.py +++ /dev/null @@ -1,155 +0,0 @@ -from datetime import datetime, timedelta - -import pytest -from sqlalchemy.exc import ProgrammingError -from sqlalchemy.orm import Session - -from database import codes, models - - -def test_validate_lifecycle_dates( - session: Session, - plan_instance: models.Plan, - lifecycle_date_instance: models.LifeCycleDate, -) -> None: - assert ( - lifecycle_date_instance.ending_at - and lifecycle_date_instance.starting_at < lifecycle_date_instance.ending_at - ) - session.add(lifecycle_date_instance) - # check that modified date cannot start after ending - with pytest.raises(ProgrammingError): - lifecycle_date_instance.starting_at = ( - lifecycle_date_instance.ending_at + timedelta(days=1) - ) - session.flush() - session.rollback() - # check that new date cannot start after ending - with pytest.raises(ProgrammingError): - new_lifecycle_date_instance = models.LifeCycleDate( - plan=plan_instance, - starting_at=datetime.now() + timedelta(days=1), - ending_at=datetime.now(), - ) - session.add(new_lifecycle_date_instance) - session.flush() - session.rollback() - - -def test_validate_event_dates( - session: Session, - preparation_date_instance: models.LifeCycleDate, - interaction_event_date_instance: models.EventDate, -) -> None: - assert ( - interaction_event_date_instance.ending_at - and interaction_event_date_instance.starting_at - < interaction_event_date_instance.ending_at - ) - session.add(interaction_event_date_instance) - # check that modified event cannot start after ending - with pytest.raises(ProgrammingError): - interaction_event_date_instance.starting_at = ( - interaction_event_date_instance.ending_at + timedelta(days=1) - ) - session.flush() - session.rollback() - # check that new event cannot start after ending - with pytest.raises(ProgrammingError): - new_event_date_instance = models.EventDate( - lifecycle_date=preparation_date_instance, - starting_at=datetime.now() + timedelta(days=1), - ending_at=datetime.now(), - ) - session.add(new_event_date_instance) - session.flush() - session.rollback() - - -def test_validate_event_dates_inside_status_dates( - session: Session, - preparation_date_instance: models.LifeCycleDate, - interaction_event_date_instance: models.EventDate, -) -> None: - assert ( - interaction_event_date_instance.starting_at - > preparation_date_instance.starting_at - ) - assert ( - preparation_date_instance.ending_at - and interaction_event_date_instance.ending_at - and interaction_event_date_instance.ending_at - < preparation_date_instance.ending_at - ) - # check that modified event cannot start before status starts - with pytest.raises(ProgrammingError): - interaction_event_date_instance.starting_at = ( - preparation_date_instance.starting_at - timedelta(days=1) - ) - session.flush() - session.rollback() - # check that modified event cannot end after status ends - with pytest.raises(ProgrammingError): - interaction_event_date_instance.ending_at = ( - preparation_date_instance.ending_at + timedelta(days=1) - ) - session.flush() - session.rollback() - # check that new event cannot start before status starts - with pytest.raises(ProgrammingError): - new_event_date_instance = models.EventDate( - lifecycle_date=preparation_date_instance, - starting_at=preparation_date_instance.starting_at - timedelta(days=1), - ) - session.add(new_event_date_instance) - session.flush() - session.rollback() - # check that new event cannot end after status ends - with pytest.raises(ProgrammingError): - new_event_date_instance = models.EventDate( - lifecycle_date=preparation_date_instance, - starting_at=preparation_date_instance.ending_at + timedelta(days=1), - ) - session.add(new_event_date_instance) - session.flush() - session.rollback() - - -def test_validate_event_types( - session: Session, - preparation_date_instance: models.LifeCycleDate, - approved_date_instance: models.LifeCycleDate, - decision_date_instance: models.EventDate, - participation_plan_presenting_for_public_decision: codes.NameOfPlanCaseDecision, - plan_proposal_presenting_for_public_decision: codes.NameOfPlanCaseDecision, -) -> None: - assert decision_date_instance.lifecycle_date == preparation_date_instance - assert ( - decision_date_instance.decision - == participation_plan_presenting_for_public_decision - ) - session.add(decision_date_instance) - # check that modified event cannot be added to wrong status - with pytest.raises(ProgrammingError): - decision_date_instance.lifecycle_date = approved_date_instance - decision_date_instance.starting_at = approved_date_instance.starting_at - decision_date_instance.ending_at = ( - approved_date_instance.starting_at + timedelta(days=30) - ) - session.flush() - session.rollback() - # check that modified event cannot be added to wrong event type - with pytest.raises(ProgrammingError): - decision_date_instance.decision = plan_proposal_presenting_for_public_decision - session.flush() - session.rollback() - # check that new event cannot be added to wrong status/event type combination - with pytest.raises(ProgrammingError): - new_event_date_instance = models.EventDate( - lifecycle_date=approved_date_instance, - starting_at=approved_date_instance.starting_at, - decision=participation_plan_presenting_for_public_decision, - ) - session.add(new_event_date_instance) - session.flush() - session.rollback()