Repository: bo4e/BO4E-python (language: python)

Issue:
Implement BO Buendelvertrag
https://www.bo4e.de/dokumentation/geschaeftsobjekte/bo-buendelvertrag
Buendelvertrag Vererbung ist vermutlich falsch
![grafik](https://user-images.githubusercontent.com/23094997/148682869-fb595db2-3eee-4d91-8fb5-6a677e291da0.png)
https://www.bo4e.de/dokumentation/geschaeftsobjekte/bo-buendelvertrag

Der Buendelvertrag sollte meiner Meinung nach _nicht_ von Vertrag erben. Denn wenn alle Eigenschaften der Einzelverträge alle gleich sind (das impliziert ja die Vererbung von einem Vertrag), warum brauchen wir dann die Einzelverträge überhaupt?


Discussion / hints:

Fix patch:
diff --git a/src/bo4e/bo/buendelvertrag.py b/src/bo4e/bo/buendelvertrag.py
new file mode 100644
index 000000000..33912c773
--- /dev/null
+++ b/src/bo4e/bo/buendelvertrag.py
@@ -0,0 +1,41 @@
+"""
+Contains Buendelvertrag class and corresponding marshmallow schema for de-/serialization
+"""
+from typing import List
+
+import attr
+from marshmallow import fields
+
+from bo4e.bo.geschaeftsobjekt import Geschaeftsobjekt, GeschaeftsobjektSchema
+from bo4e.bo.vertrag import Vertrag, VertragSchema
+from bo4e.enum.botyp import BoTyp
+from bo4e.validators import check_list_length_at_least_one
+
+
+# pylint: disable=too-few-public-methods
+@attr.s(auto_attribs=True, kw_only=True)
+class Buendelvertrag(Geschaeftsobjekt):
+    """
+    Abbildung eines Bündelvertrags.
+    Es handelt sich hierbei um eine Liste von Einzelverträgen, die in einem Vertragsobjekt gebündelt sind.
+    """
+
+    # required attributes
+    bo_typ: BoTyp = attr.ib(default=BoTyp.BUENDELVERTRAG)
+    #: Die Liste mit den Einzelverträgen zu den Abnahmestellen
+    einzelvertraege: List[Vertrag] = attr.ib(
+        validator=attr.validators.deep_iterable(
+            member_validator=attr.validators.instance_of(Vertrag), iterable_validator=check_list_length_at_least_one
+        )
+    )
+
+
+class BuendelvertragSchema(GeschaeftsobjektSchema):
+    """
+    Schema for de-/serialization of Buendelvertrag
+    """
+
+    class_name = Buendelvertrag
+
+    # required attributes
+    einzelvertraege = fields.List(fields.Nested(VertragSchema))
diff --git a/src/bo4e/enum/botyp.py b/src/bo4e/enum/botyp.py
index 26a7e0b4c..fd57e50b5 100644
--- a/src/bo4e/enum/botyp.py
+++ b/src/bo4e/enum/botyp.py
@@ -10,6 +10,7 @@ class BoTyp(StrEnum):
     ANGEBOT = "ANGEBOT"
     ANSPRECHPARTNER = "ANSPRECHPARTNER"
     AUSSCHREIBUNG = "AUSSCHREIUNG"
+    BUENDELVERTRAG = "BUENDELVERTRAG"
     ENERGIEMENGE = "ENERGIEMENGE"
     # It is practical to use the BoTyp Enum as discriminator in the database.
     # Therefore, we added one additional entry for GESCHAEFTSOBJEKT


Test patch:
diff --git a/tests/test_buendelvertrag.py b/tests/test_buendelvertrag.py
new file mode 100644
index 000000000..d651de92c
--- /dev/null
+++ b/tests/test_buendelvertrag.py
@@ -0,0 +1,22 @@
+import pytest  # type:ignore[import]
+
+from bo4e.bo.buendelvertrag import Buendelvertrag, BuendelvertragSchema
+from tests.serialization_helper import assert_serialization_roundtrip  # type:ignore[import]
+from tests.test_vertrag import TestVertrag  # type:ignore[import]
+
+
+class TestBuendelvertrag:
+    @pytest.mark.parametrize(
+        "buendelvertrag",
+        [
+            pytest.param(Buendelvertrag(einzelvertraege=[TestVertrag().get_example_vertrag()])),
+        ],
+    )
+    def test_serialization_roundtrip(self, buendelvertrag: Buendelvertrag):
+        assert_serialization_roundtrip(buendelvertrag, BuendelvertragSchema())
+
+    def test_missing_required_attribute(self):
+        with pytest.raises(TypeError) as excinfo:
+            _ = Buendelvertrag()
+
+        assert "missing 1 required" in str(excinfo.value)
diff --git a/tests/test_vertrag.py b/tests/test_vertrag.py
index 106f57f4e..a84e434c7 100644
--- a/tests/test_vertrag.py
+++ b/tests/test_vertrag.py
@@ -62,11 +62,8 @@ class TestVertrag:
         )
     ]
 
-    def test_serialisation_only_required_attributes(self):
-        """
-        Test de-/serialisation of Vertrag with minimal attributes.
-        """
-        vertrag = Vertrag(
+    def get_example_vertrag(self) -> Vertrag:
+        return Vertrag(
             vertragsnummer=self._vertragsnummer,
             vertragsart=self._vertragsart,
             vertragsstatus=self._vertragsstatus,
@@ -78,6 +75,12 @@ def test_serialisation_only_required_attributes(self):
             vertragsteile=self._vertragsteile,
         )
 
+    def test_serialisation_only_required_attributes(self):
+        """
+        Test de-/serialisation of Vertrag with minimal attributes.
+        """
+        vertrag = self.get_example_vertrag()
+
         schema = VertragSchema()
         json_string = schema.dumps(vertrag, ensure_ascii=False)