diff --git a/docs/api/bo4e.bo.rst b/docs/api/bo4e.bo.rst index b19c699d7..d8662da62 100644 --- a/docs/api/bo4e.bo.rst +++ b/docs/api/bo4e.bo.rst @@ -4,6 +4,14 @@ bo4e.bo package Submodules ---------- +bo4e.bo.angebot module +---------------------- + +.. automodule:: bo4e.bo.angebot + :members: + :undoc-members: + :show-inheritance: + bo4e.bo.ansprechpartner module ------------------------------ @@ -12,6 +20,22 @@ bo4e.bo.ansprechpartner module :undoc-members: :show-inheritance: +bo4e.bo.buendelvertrag module +----------------------------- + +.. automodule:: bo4e.bo.buendelvertrag + :members: + :undoc-members: + :show-inheritance: + +bo4e.bo.energiemenge module +--------------------------- + +.. automodule:: bo4e.bo.energiemenge + :members: + :undoc-members: + :show-inheritance: + bo4e.bo.geschaeftsobjekt module ------------------------------- @@ -28,6 +52,22 @@ bo4e.bo.geschaeftspartner module :undoc-members: :show-inheritance: +bo4e.bo.kosten module +--------------------- + +.. automodule:: bo4e.bo.kosten + :members: + :undoc-members: + :show-inheritance: + +bo4e.bo.lastgang module +----------------------- + +.. automodule:: bo4e.bo.lastgang + :members: + :undoc-members: + :show-inheritance: + bo4e.bo.marktlokation module ---------------------------- @@ -52,6 +92,54 @@ bo4e.bo.messlokation module :undoc-members: :show-inheritance: +bo4e.bo.preisblatt module +------------------------- + +.. automodule:: bo4e.bo.preisblatt + :members: + :undoc-members: + :show-inheritance: + +bo4e.bo.preisblattkonzessionsabgabe module +------------------------------------------ + +.. automodule:: bo4e.bo.preisblattkonzessionsabgabe + :members: + :undoc-members: + :show-inheritance: + +bo4e.bo.preisblattnetznutzung module +------------------------------------ + +.. automodule:: bo4e.bo.preisblattnetznutzung + :members: + :undoc-members: + :show-inheritance: + +bo4e.bo.region module +--------------------- + +.. automodule:: bo4e.bo.region + :members: + :undoc-members: + :show-inheritance: + +bo4e.bo.standorteigenschaften module +------------------------------------ + +.. automodule:: bo4e.bo.standorteigenschaften + :members: + :undoc-members: + :show-inheritance: + +bo4e.bo.tarifinfo module +------------------------ + +.. automodule:: bo4e.bo.tarifinfo + :members: + :undoc-members: + :show-inheritance: + bo4e.bo.vertrag module ---------------------- @@ -68,6 +156,14 @@ bo4e.bo.zaehler module :undoc-members: :show-inheritance: +bo4e.bo.zeitreihe module +------------------------ + +.. automodule:: bo4e.bo.zeitreihe + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- diff --git a/docs/api/bo4e.com.rst b/docs/api/bo4e.com.rst index 8624b0e6a..8b30b1a3c 100644 --- a/docs/api/bo4e.com.rst +++ b/docs/api/bo4e.com.rst @@ -28,6 +28,14 @@ bo4e.com.angebotsteil module :undoc-members: :show-inheritance: +bo4e.com.angebotsvariante module +-------------------------------- + +.. automodule:: bo4e.com.angebotsvariante + :members: + :undoc-members: + :show-inheritance: + bo4e.com.aufabschlag module --------------------------- @@ -68,6 +76,14 @@ bo4e.com.ausschreibungsdetail module :undoc-members: :show-inheritance: +bo4e.com.ausschreibungslos module +--------------------------------- + +.. automodule:: bo4e.com.ausschreibungslos + :members: + :undoc-members: + :show-inheritance: + bo4e.com.betrag module ---------------------- @@ -116,6 +132,14 @@ bo4e.com.externereferenz module :undoc-members: :show-inheritance: +bo4e.com.fremdkostenblock module +-------------------------------- + +.. automodule:: bo4e.com.fremdkostenblock + :members: + :undoc-members: + :show-inheritance: + bo4e.com.fremdkostenposition module ----------------------------------- @@ -164,6 +188,22 @@ bo4e.com.katasteradresse module :undoc-members: :show-inheritance: +bo4e.com.kostenblock module +--------------------------- + +.. automodule:: bo4e.com.kostenblock + :members: + :undoc-members: + :show-inheritance: + +bo4e.com.kostenposition module +------------------------------ + +.. automodule:: bo4e.com.kostenposition + :members: + :undoc-members: + :show-inheritance: + bo4e.com.kriteriumwert module ----------------------------- @@ -220,6 +260,14 @@ bo4e.com.preisgarantie module :undoc-members: :show-inheritance: +bo4e.com.preisposition module +----------------------------- + +.. automodule:: bo4e.com.preisposition + :members: + :undoc-members: + :show-inheritance: + bo4e.com.preisstaffel module ---------------------------- @@ -228,6 +276,14 @@ bo4e.com.preisstaffel module :undoc-members: :show-inheritance: +bo4e.com.rechnungsposition module +--------------------------------- + +.. automodule:: bo4e.com.rechnungsposition + :members: + :undoc-members: + :show-inheritance: + bo4e.com.regionalegueltigkeit module ------------------------------------ @@ -236,6 +292,38 @@ bo4e.com.regionalegueltigkeit module :undoc-members: :show-inheritance: +bo4e.com.regionalepreisgarantie module +-------------------------------------- + +.. automodule:: bo4e.com.regionalepreisgarantie + :members: + :undoc-members: + :show-inheritance: + +bo4e.com.regionalepreisstaffel module +------------------------------------- + +.. automodule:: bo4e.com.regionalepreisstaffel + :members: + :undoc-members: + :show-inheritance: + +bo4e.com.regionaleraufabschlag module +------------------------------------- + +.. automodule:: bo4e.com.regionaleraufabschlag + :members: + :undoc-members: + :show-inheritance: + +bo4e.com.regionaletarifpreisposition module +------------------------------------------- + +.. automodule:: bo4e.com.regionaletarifpreisposition + :members: + :undoc-members: + :show-inheritance: + bo4e.com.regionskriterium module -------------------------------- @@ -276,6 +364,14 @@ bo4e.com.standorteigenschaftengas module :undoc-members: :show-inheritance: +bo4e.com.standorteigenschaftenstrom module +------------------------------------------ + +.. automodule:: bo4e.com.standorteigenschaftenstrom + :members: + :undoc-members: + :show-inheritance: + bo4e.com.steuerbetrag module ---------------------------- @@ -308,6 +404,14 @@ bo4e.com.tarifpreis module :undoc-members: :show-inheritance: +bo4e.com.tarifpreisposition module +---------------------------------- + +.. automodule:: bo4e.com.tarifpreisposition + :members: + :undoc-members: + :show-inheritance: + bo4e.com.unterschrift module ---------------------------- diff --git a/docs/api/bo4e.enum.rst b/docs/api/bo4e.enum.rst index d90f64b89..2fe7e7c8f 100644 --- a/docs/api/bo4e.enum.rst +++ b/docs/api/bo4e.enum.rst @@ -28,6 +28,14 @@ bo4e.enum.arithmetische\_operation module :undoc-members: :show-inheritance: +bo4e.enum.artikelid module +-------------------------- + +.. automodule:: bo4e.enum.artikelid + :members: + :undoc-members: + :show-inheritance: + bo4e.enum.aufabschlagstyp module -------------------------------- diff --git a/src/bo4e/bo/angebot.py b/src/bo4e/bo/angebot.py new file mode 100644 index 000000000..f7525fb84 --- /dev/null +++ b/src/bo4e/bo/angebot.py @@ -0,0 +1,92 @@ +""" +Contains Angebot class and corresponding marshmallow schema for de-/serialization +""" + +from datetime import datetime +from typing import List, Optional + +import attr +from marshmallow import fields +from marshmallow_enum import EnumField # type:ignore[import] + +from bo4e.bo.ansprechpartner import Ansprechpartner, AnsprechpartnerSchema +from bo4e.bo.geschaeftsobjekt import Geschaeftsobjekt, GeschaeftsobjektSchema +from bo4e.bo.geschaeftspartner import Geschaeftspartner, GeschaeftspartnerSchema +from bo4e.com.angebotsvariante import Angebotsvariante, AngebotsvarianteSchema +from bo4e.enum.botyp import BoTyp +from bo4e.enum.sparte import Sparte +from bo4e.validators import check_list_length_at_least_one + + +# pylint: disable=too-few-public-methods, too-many-instance-attributes +@attr.s(auto_attribs=True, kw_only=True) +class Angebot(Geschaeftsobjekt): + """ + Mit diesem BO kann ein Versorgungsangebot zur Strom- oder Gasversorgung oder die Teilnahme an einer Ausschreibung + übertragen werden. Es können verschiedene Varianten enthalten sein (z.B. ein- und mehrjährige Laufzeit). + Innerhalb jeder Variante können Teile enthalten sein, die jeweils für eine oder mehrere Marktlokationen erstellt + werden. + """ + + bo_typ: BoTyp = attr.ib(default=BoTyp.ANGEBOT) + # required attributes + #: Eindeutige Nummer des Angebotes + angebotsnummer: str = attr.ib(validator=attr.validators.matches_re(r"^\d+$")) + #: Erstellungsdatum des Angebots + angebotsdatum: datetime = attr.ib(validator=attr.validators.instance_of(datetime)) + #: Sparte, für die das Angebot abgegeben wird (Strom/Gas) + sparte: Sparte = attr.ib(validator=attr.validators.instance_of(Sparte)) + #: Ersteller des Angebots + angebotsgeber: Geschaeftspartner = attr.ib(validator=attr.validators.instance_of(Geschaeftspartner)) + #: Empfänger des Angebots + angebotsnehmer: Geschaeftspartner = attr.ib(validator=attr.validators.instance_of(Geschaeftspartner)) + + varianten: List[Angebotsvariante] = attr.ib( + validator=attr.validators.deep_iterable( + member_validator=attr.validators.instance_of(Angebotsvariante), + iterable_validator=check_list_length_at_least_one, + ) + ) + """ Eine oder mehrere Varianten des Angebots mit den Angebotsteilen; + Ein Angebot besteht mindestens aus einer Variante.""" + + # optional attributes + anfragereferenz: Optional[str] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(str)) + ) + """ Referenz auf eine Anfrage oder Ausschreibung; + Kann dem Empfänger des Angebotes bei Zuordnung des Angebotes zur Anfrage bzw. Ausschreibung helfen.""" + #: Bis zu diesem Zeitpunkt (Tag/Uhrzeit) inklusive gilt das Angebot + bindefrist: Optional[datetime] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(datetime)) + ) + #: Person, die als Angebotsnehmer das Angebot angenommen hat + unterzeichner_angebotsnehmer: Optional[Ansprechpartner] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(Ansprechpartner)) + ) + #: Person, die als Angebotsgeber das Angebots ausgestellt hat + unterzeichner_angebotsgeber: Optional[Ansprechpartner] = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(Ansprechpartner)) + ) + + +class AngebotSchema(GeschaeftsobjektSchema): + """ + Schema for de-/serialization of Angebot + """ + + class_name = Angebot + + # required attributes + angebotsnummer = fields.Str() + angebotsdatum = fields.DateTime() + sparte = EnumField(Sparte) + angebotsgeber = fields.Nested(GeschaeftspartnerSchema) + angebotsnehmer = fields.Nested(GeschaeftspartnerSchema) + varianten = fields.List(fields.Nested(AngebotsvarianteSchema)) + + # optional attributes + anfragereferenz = fields.Str(allow_none=True) + bindefrist = fields.DateTime(allow_none=True) + unterzeichner_angebotsnehmer = fields.Nested(AnsprechpartnerSchema, allow_none=True) + unterzeichner_angebotsgeber = fields.Nested(AnsprechpartnerSchema, allow_none=True) diff --git a/tests/test_angebot.py b/tests/test_angebot.py new file mode 100644 index 000000000..be5f576ba --- /dev/null +++ b/tests/test_angebot.py @@ -0,0 +1,92 @@ +from datetime import datetime, timezone + +import pytest # type:ignore[import] + +from bo4e.bo.angebot import Angebot, AngebotSchema +from bo4e.bo.ansprechpartner import Ansprechpartner +from bo4e.bo.geschaeftspartner import Geschaeftspartner +from bo4e.enum.geschaeftspartnerrolle import Geschaeftspartnerrolle +from bo4e.enum.sparte import Sparte +from tests.serialization_helper import assert_serialization_roundtrip # type:ignore[import] +from tests.test_adresse import example_adresse # type:ignore[import] +from tests.test_angebotsvariante import example_angebotsvariante # type:ignore[import] + + +class TestAngebot: + @pytest.mark.parametrize( + "angebot", + [ + pytest.param( + Angebot( + angebotsnummer="271828", + anfragereferenz="foo", + angebotsdatum=datetime(2020, 1, 1, tzinfo=timezone.utc), + sparte=Sparte.GAS, + bindefrist=datetime(2019, 3, 2, tzinfo=timezone.utc), + angebotsgeber=Geschaeftspartner( + name1="Batman", + gewerbekennzeichnung=True, + geschaeftspartnerrolle=[Geschaeftspartnerrolle.LIEFERANT], + partneradresse=example_adresse, + ), + angebotsnehmer=Geschaeftspartner( + name1="Joker", + gewerbekennzeichnung=False, + geschaeftspartnerrolle=[Geschaeftspartnerrolle.KUNDE], + partneradresse=example_adresse, + ), + unterzeichner_angebotsnehmer=Ansprechpartner( + nachname="Titans", + geschaeftspartner=Geschaeftspartner( + name1="Wonderwoman", + gewerbekennzeichnung=False, + geschaeftspartnerrolle=[Geschaeftspartnerrolle.DIENSTLEISTER], + partneradresse=example_adresse, + ), + ), + unterzeichner_angebotsgeber=Ansprechpartner( + nachname="Titans", + geschaeftspartner=Geschaeftspartner( + name1="Robin", + gewerbekennzeichnung=False, + geschaeftspartnerrolle=[Geschaeftspartnerrolle.KUNDE], + partneradresse=example_adresse, + ), + ), + varianten=[example_angebotsvariante], + ), + id="required and optional attributes", + ), + pytest.param( + Angebot( + angebotsnummer="271828", + angebotsdatum=datetime(2020, 1, 1, tzinfo=timezone.utc), + sparte=Sparte.GAS, + angebotsgeber=Geschaeftspartner( + name1="Batman", + gewerbekennzeichnung=True, + geschaeftspartnerrolle=[Geschaeftspartnerrolle.LIEFERANT], + partneradresse=example_adresse, + ), + angebotsnehmer=Geschaeftspartner( + name1="Joker", + gewerbekennzeichnung=False, + geschaeftspartnerrolle=[Geschaeftspartnerrolle.KUNDE], + partneradresse=example_adresse, + ), + varianten=[example_angebotsvariante], + ), + id="required attributes", + ), + ], + ) + def test_serialization_roundtrip(self, angebot: Angebot): + """ + Test de-/serialisation + """ + assert_serialization_roundtrip(angebot, AngebotSchema()) + + def test_missing_required_attribute(self): + with pytest.raises(TypeError) as excinfo: + _ = Angebot() + assert "missing 6 required" in str(excinfo.value) diff --git a/tests/test_angebotsvariante.py b/tests/test_angebotsvariante.py index 62bcf5c13..188a2fba4 100644 --- a/tests/test_angebotsvariante.py +++ b/tests/test_angebotsvariante.py @@ -9,18 +9,21 @@ from tests.test_betrag import example_betrag, example_betrag_json # type:ignore[import] from tests.test_menge import example_menge # type:ignore[import] +# can be imported by other tests +example_angebotsvariante = Angebotsvariante( + angebotsstatus=Angebotsstatus.NACHGEFASST, + bindefrist=datetime.datetime(2022, 2, 1, 0, 0, 0, tzinfo=datetime.timezone.utc), + erstellungsdatum=datetime.datetime(2021, 12, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), + teile=[example_angebotsteil], +) + class TestAngebotsvariante: @pytest.mark.parametrize( "angebotsvariante, expected_json_dict", [ pytest.param( - Angebotsvariante( - angebotsstatus=Angebotsstatus.NACHGEFASST, - bindefrist=datetime.datetime(2022, 2, 1, 0, 0, 0, tzinfo=datetime.timezone.utc), - erstellungsdatum=datetime.datetime(2021, 12, 22, 0, 0, 0, tzinfo=datetime.timezone.utc), - teile=[example_angebotsteil], - ), + example_angebotsvariante, { "gesamtmenge": None, "angebotsstatus": "NACHGEFASST",