Skip to content

Commit 5325f90

Browse files
nmaarnioLKajan
authored andcommitted
add legal effects of master plan
1 parent 0e93098 commit 5325f90

File tree

7 files changed

+193
-15
lines changed

7 files changed

+193
-15
lines changed

arho_feature_template/core/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,7 @@ class Plan:
578578
organisation_id: str | None = None
579579
general_regulations: list[RegulationGroup] = field(default_factory=list)
580580
documents: list[Document] = field(default_factory=list)
581+
legal_effect_ids: list[str] = field(default_factory=list)
581582
geom: QgsGeometry | None = None
582583
id_: str | None = None
583584

arho_feature_template/core/plan_manager.py

+27
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
DocumentLayer,
3838
LandUseAreaLayer,
3939
LandUsePointLayer,
40+
LegalEffectAssociationLayer,
4041
LifeCycleLayer,
4142
LineLayer,
4243
OtherAreaLayer,
@@ -547,6 +548,15 @@ def save_plan(plan: Plan) -> QgsFeature | None:
547548
):
548549
iface.messageBar().pushCritical("", "Kaavamääräysryhmän assosiaation poistaminen epäonnistui.")
549550

551+
# Check for deleted legal effects
552+
for association in LegalEffectAssociationLayer.get_dangling_associations(plan_id, plan.legal_effect_ids):
553+
if not _delete_feature(
554+
association,
555+
LegalEffectAssociationLayer.get_from_project(),
556+
"Oikeusvaikutuksen assosiaation poisto",
557+
):
558+
iface.messageBar().pushCritical("", "Oikeusvaikutuksen assosiaation poistaminen epäonnistui.")
559+
550560
# Check for documents to be deleted
551561
doc_layer = DocumentLayer.get_from_project()
552562
for doc_feature in DocumentLayer.get_documents_to_delete(plan.documents, plan.id_):
@@ -561,6 +571,10 @@ def save_plan(plan: Plan) -> QgsFeature | None:
561571
continue # Skip association saving if saving regulation group failed
562572
save_regulation_group_association(regulation_group_feature["id"], PlanLayer.name, plan_id)
563573

574+
# Save legal effect associations
575+
for legal_effect_id in plan.legal_effect_ids:
576+
save_legal_effect_association(plan_id, legal_effect_id)
577+
564578
# Save documents
565579
for document in plan.documents:
566580
document.plan_id = plan_id
@@ -754,6 +768,19 @@ def save_type_of_verbal_regulation_association(regulation_id: str, verbal_regula
754768
return True
755769

756770

771+
def save_legal_effect_association(plan_id: str, legal_effect_id: str) -> bool:
772+
if LegalEffectAssociationLayer.association_exists(plan_id, legal_effect_id):
773+
return True
774+
feature = LegalEffectAssociationLayer.feature_from(plan_id, legal_effect_id)
775+
layer = LegalEffectAssociationLayer.get_from_project()
776+
777+
if not _save_feature(feature=feature, layer=layer, id_=None, edit_text="Oikeusvaikutuksen assosiaation lisäys"):
778+
iface.messageBar().pushCritical("", "Oikeusvaikutuksen assosiaation tallentaminen epäonnistui.")
779+
return False
780+
781+
return True
782+
783+
757784
def save_additional_information(additional_information: AdditionalInformation) -> QgsFeature | None:
758785
feature = AdditionalInformationLayer.feature_from_model(additional_information)
759786
layer = AdditionalInformationLayer.get_from_project()

arho_feature_template/gui/components/value_input_widgets.py

+39-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from arho_feature_template.core.models import AttributeValue, AttributeValueDataType
1919
from arho_feature_template.gui.components.code_combobox import HierarchicalCodeComboBox
20-
from arho_feature_template.project.layers.code_layers import VerbalRegulationType
20+
from arho_feature_template.project.layers.code_layers import LegalEffectsLayer, VerbalRegulationType
2121

2222
logger = logging.getLogger(__name__)
2323

@@ -194,6 +194,44 @@ def get_value(self) -> str | None:
194194
return self.input_widget.value()
195195

196196

197+
class LegalEffectWidget(QWidget):
198+
def __init__(
199+
self,
200+
with_add_btn: bool = False, # noqa: FBT001, FBT002
201+
with_del_btn: bool = False, # noqa: FBT001, FBT002
202+
parent=None,
203+
):
204+
super().__init__(parent)
205+
206+
layout = QHBoxLayout()
207+
layout.setContentsMargins(0, 0, 0, 0)
208+
self.input_widget = HierarchicalCodeComboBox()
209+
self.input_widget.populate_from_code_layer(LegalEffectsLayer)
210+
layout.addWidget(self.input_widget)
211+
212+
self.add_btn: QPushButton | None = None
213+
if with_add_btn:
214+
self.add_btn = QPushButton()
215+
self.add_btn.setMaximumWidth(30)
216+
self.add_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg"))
217+
layout.addWidget(self.add_btn)
218+
219+
self.del_btn: QPushButton | None = None
220+
if with_del_btn:
221+
self.del_btn = QPushButton()
222+
self.del_btn.setMaximumWidth(30)
223+
self.del_btn.setIcon(QgsApplication.getThemeIcon("mActionDeleteSelected.svg"))
224+
layout.addWidget(self.del_btn)
225+
226+
self.setLayout(layout)
227+
228+
def set_value(self, value: str | None):
229+
self.input_widget.set_value(value)
230+
231+
def get_value(self) -> str | None:
232+
return self.input_widget.value()
233+
234+
197235
class ValueWidgetManager:
198236
def __init__(self, value: AttributeValue | None, default_value: AttributeValue):
199237
if value is None:

arho_feature_template/gui/dialogs/plan_attribute_form.py

+68-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
from __future__ import annotations
22

33
from importlib import resources
4-
from typing import TYPE_CHECKING
4+
from typing import TYPE_CHECKING, cast
55

66
from qgis.core import QgsApplication
77
from qgis.PyQt import uic
8-
from qgis.PyQt.QtWidgets import (
9-
QDialog,
10-
QDialogButtonBox,
11-
QLineEdit,
12-
QPushButton,
13-
QTextEdit,
14-
)
8+
from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox, QLabel, QLineEdit, QPushButton, QTextEdit
159

1610
from arho_feature_template.core.models import Document, Plan, RegulationGroup, RegulationGroupLibrary
1711
from arho_feature_template.gui.components.general_regulation_group_widget import GeneralRegulationGroupWidget
1812

1913
# from arho_feature_template.gui.components.plan_regulation_group_widget import RegulationGroupWidget
2014
from arho_feature_template.gui.components.plan_document_widget import DocumentWidget
15+
from arho_feature_template.gui.components.value_input_widgets import LegalEffectWidget
2116
from arho_feature_template.project.layers.code_layers import (
2217
LifeCycleStatusLayer,
2318
OrganisationLayer,
@@ -26,7 +21,7 @@
2621
from arho_feature_template.utils.misc_utils import disconnect_signal
2722

2823
if TYPE_CHECKING:
29-
from qgis.PyQt.QtWidgets import QLineEdit, QPushButton, QTextEdit, QVBoxLayout
24+
from qgis.PyQt.QtWidgets import QFormLayout, QLineEdit, QTextEdit, QVBoxLayout
3025

3126
from arho_feature_template.gui.components.code_combobox import CodeComboBox, HierarchicalCodeComboBox
3227

@@ -61,6 +56,8 @@ def __init__(self, plan: Plan, _regulation_group_libraries: list[RegulationGroup
6156

6257
self.setupUi(self)
6358

59+
self.general_data_layout: QFormLayout
60+
6461
self.plan = plan
6562
self.lifecycle_models = plan.lifecycles
6663

@@ -87,10 +84,12 @@ def __init__(self, plan: Plan, _regulation_group_libraries: list[RegulationGroup
8784
self.name_line_edit.textChanged.connect(self._check_required_fields)
8885
self.organisation_combo_box.currentIndexChanged.connect(self._check_required_fields)
8986
self.plan_type_combo_box.currentIndexChanged.connect(self._check_required_fields)
87+
self.plan_type_combo_box.currentIndexChanged.connect(self._update_legal_effect_widgets_visibility)
9088
self.lifecycle_status_combo_box.currentIndexChanged.connect(self._check_required_fields)
9189

9290
self.scroll_area_spacer = None
9391
self.regulation_group_widgets: list[GeneralRegulationGroupWidget] = []
92+
self.legal_effect_widgets: list[tuple[QLabel, LegalEffectWidget]] = []
9493
# self.regulation_groups_selection_widget = TreeWithSearchWidget()
9594
# self.regulation_groups_tree_layout.insertWidget(2, self.regulation_groups_selection_widget)
9695
# for library in regulation_group_libraries:
@@ -105,6 +104,12 @@ def __init__(self, plan: Plan, _regulation_group_libraries: list[RegulationGroup
105104
for document in plan.documents:
106105
self.add_document(document)
107106

107+
# Legal effects
108+
for legal_effect_id in plan.legal_effect_ids:
109+
self.add_legal_effect_widget(legal_effect_id)
110+
if len(self.legal_effect_widgets) == 0:
111+
self.add_legal_effect_widget()
112+
108113
self.add_general_regulation_group_btn.clicked.connect(self.add_new_regulation_group)
109114
self.add_general_regulation_group_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg"))
110115

@@ -115,6 +120,7 @@ def __init__(self, plan: Plan, _regulation_group_libraries: list[RegulationGroup
115120
self.button_box.accepted.connect(self._on_ok_clicked)
116121

117122
self._check_required_fields()
123+
self._update_legal_effect_widgets_visibility()
118124

119125
def _check_required_fields(self) -> None:
120126
ok_button = self.button_box.button(QDialogButtonBox.Ok)
@@ -129,6 +135,53 @@ def _check_required_fields(self) -> None:
129135
else:
130136
ok_button.setEnabled(False)
131137

138+
def _update_legal_effect_widgets_visibility(self):
139+
plan_type_id = self.plan_type_combo_box.value()
140+
if PlanTypeLayer.is_general_plan_type(plan_type_id):
141+
self._show_legal_effect_widgets()
142+
else:
143+
self._hide_legal_effect_widgets()
144+
145+
def _show_legal_effect_widgets(self):
146+
for label, widget in self.legal_effect_widgets:
147+
label.show()
148+
widget.show()
149+
150+
def _hide_legal_effect_widgets(self):
151+
for label, widget in self.legal_effect_widgets:
152+
label.hide()
153+
widget.hide()
154+
155+
def add_legal_effect_widget(self, legal_effect_id: str | None = None):
156+
"""
157+
Adds a legal effect widget to the form.
158+
159+
If no legal widgets exist yet, includes an "add" button, otherwise a
160+
"delete" button.
161+
"""
162+
if len(self.legal_effect_widgets) == 0:
163+
widget = LegalEffectWidget(with_add_btn=True)
164+
btn = cast(QPushButton, widget.add_btn)
165+
btn.clicked.connect(self.add_legal_effect_widget)
166+
else:
167+
widget = LegalEffectWidget(with_del_btn=True)
168+
btn = cast(QPushButton, widget.del_btn)
169+
btn.clicked.connect(lambda: self.delete_legal_effect_widget(widget))
170+
171+
if legal_effect_id:
172+
widget.set_value(legal_effect_id)
173+
174+
label = QLabel("Oikeusvaikutus")
175+
self.legal_effect_widgets.append((label, widget))
176+
self.general_data_layout.addRow(label, widget)
177+
178+
def delete_legal_effect_widget(self, widget_to_delete: LegalEffectWidget):
179+
for i, (label, widget) in enumerate(self.legal_effect_widgets):
180+
if widget is widget_to_delete:
181+
self.legal_effect_widgets.pop(i)
182+
widget.deleteLater()
183+
label.deleteLater()
184+
132185
# --- COPIED FROM PLAN FEATURE FORM ---
133186

134187
# def add_selected_plan_regulation_group(self, item: QTreeWidgetItem, column: int):
@@ -190,6 +243,11 @@ def delete_document(self, document_widget: DocumentWidget):
190243
# ---
191244

192245
def into_model(self) -> Plan:
246+
if PlanTypeLayer.is_general_plan_type(self.plan_type_combo_box.value()):
247+
legal_effect_ids = [legal_effect_widget[1].get_value() for legal_effect_widget in self.legal_effect_widgets]
248+
else:
249+
legal_effect_ids = []
250+
193251
return Plan(
194252
id_=self.plan.id_,
195253
name=self.name_line_edit.text(),
@@ -202,6 +260,7 @@ def into_model(self) -> Plan:
202260
matter_management_identifier=self.matter_management_identifier_line_edit.text() or None,
203261
lifecycle_status_id=self.lifecycle_status_combo_box.value(),
204262
general_regulations=[reg_group_widget.into_model() for reg_group_widget in self.regulation_group_widgets],
263+
legal_effect_ids=[value for value in legal_effect_ids if value is not None],
205264
documents=[document_widget.into_model() for document_widget in self.document_widgets],
206265
geom=self.plan.geom,
207266
)

arho_feature_template/gui/dialogs/plan_attribute_form.ui

+5-5
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
<number>9</number>
7777
</property>
7878
<item>
79-
<layout class="QFormLayout" name="formLayout">
79+
<layout class="QFormLayout" name="general_data_layout">
8080
<item row="0" column="0">
8181
<widget class="QLabel" name="label">
8282
<property name="text">
@@ -246,8 +246,8 @@
246246
<rect>
247247
<x>0</x>
248248
<y>0</y>
249-
<width>831</width>
250-
<height>562</height>
249+
<width>193</width>
250+
<height>49</height>
251251
</rect>
252252
</property>
253253
<layout class="QVBoxLayout" name="regulations_layout">
@@ -301,8 +301,8 @@
301301
<rect>
302302
<x>0</x>
303303
<y>0</y>
304-
<width>831</width>
305-
<height>562</height>
304+
<width>119</width>
305+
<height>49</height>
306306
</rect>
307307
</property>
308308
<layout class="QVBoxLayout" name="documents_layout">

arho_feature_template/project/layers/code_layers.py

+14
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ class PlanTypeLayer(AbstractCodeLayer):
1515
name = "Kaavalaji"
1616

1717
category_only_codes: ClassVar[list[str]] = ["1", "2", "3"] # "Maakuntakaava", "Asemakaava", "Yleiskaava"
18+
general_plan_type_values: ClassVar[list[int]] = [2, 21, 22, 23, 24, 25]
19+
20+
@classmethod
21+
def is_general_plan_type(cls, _id: str | None) -> bool:
22+
if _id is None:
23+
return False
24+
attribute_value = cls.get_attribute_value_by_another_attribute_value("value", "id", _id)
25+
return int(attribute_value) in cls.general_plan_type_values if attribute_value is not None else False
1826

1927

2028
class LifeCycleStatusLayer(AbstractCodeLayer):
@@ -123,4 +131,10 @@ class RetentionTimeLayer(AbstractCodeLayer):
123131
category_only_codes: ClassVar[list[str]] = []
124132

125133

134+
class LegalEffectsLayer(AbstractCodeLayer):
135+
name = "Yleiskaavan oikeusvaikutus"
136+
137+
category_only_codes: ClassVar[list[str]] = []
138+
139+
126140
code_layers = AbstractCodeLayer.__subclasses__()

arho_feature_template/project/layers/plan_layers.py

+39
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def model_from_feature(cls, feature: QgsFeature) -> Plan:
120120
for feat in general_regulation_features
121121
if feat is not None
122122
],
123+
legal_effect_ids=list(LegalEffectAssociationLayer.get_legal_effect_ids_for_plan(feature["id"])),
123124
documents=[
124125
DocumentLayer.model_from_feature(feat)
125126
for feat in DocumentLayer.get_features_by_attribute_value("plan_id", feature["id"])
@@ -485,6 +486,44 @@ def get_dangling_associations(cls, regulation_id: str, updated_type_ids: list[st
485486
return [assoc for assoc in associations if assoc["type_of_verbal_plan_regulation_id"] not in updated_type_ids]
486487

487488

489+
class LegalEffectAssociationLayer(AbstractPlanLayer):
490+
name = "Yleiskaavan oikeusvaikutusten assosiaatiot"
491+
filter_template = None
492+
493+
@classmethod
494+
def feature_from(cls, plan_id: str, legal_effect_id: str) -> QgsFeature | None:
495+
layer = cls.get_from_project()
496+
497+
feature = QgsVectorLayerUtils.createFeature(layer)
498+
feature["plan_id"] = plan_id
499+
feature["legal_effects_of_master_plan_id"] = legal_effect_id
500+
return feature
501+
502+
@classmethod
503+
def association_exists(cls, plan_id: str, legal_effect_id: str) -> bool:
504+
for feature in cls.get_features_by_attribute_value("plan_id", plan_id):
505+
if feature["legal_effects_of_master_plan_id"] == legal_effect_id:
506+
return True
507+
return False
508+
509+
@classmethod
510+
def get_associations_for_plan(cls, plan_id: str) -> Generator[QgsFeature]:
511+
return cls.get_features_by_attribute_value("plan_id", plan_id)
512+
513+
@classmethod
514+
def get_legal_effect_ids_for_plan(cls, plan_id: str) -> Generator[QgsFeature]:
515+
return cls.get_attribute_values_by_another_attribute_value(
516+
"legal_effects_of_master_plan_id", "plan_id", plan_id
517+
)
518+
519+
@classmethod
520+
def get_dangling_associations(cls, plan_id: str, updated_legal_effect_ids: list[str]) -> list[QgsFeature]:
521+
associations = cls.get_associations_for_plan(plan_id)
522+
return [
523+
assoc for assoc in associations if assoc["legal_effects_of_master_plan_id"] not in updated_legal_effect_ids
524+
]
525+
526+
488527
class PlanPropositionLayer(AbstractPlanLayer):
489528
name = "Kaavasuositus"
490529
filter_template = Template(

0 commit comments

Comments
 (0)