Skip to content

Commit 46e6e4b

Browse files
hf-kkleinhf-fveselyhf-krechan
authored
Implement BO Ausschreibung (#341)
Co-authored-by: Franziska Vesely <franziska.vesely@hochfrequenz.de> Co-authored-by: Franziska <73471037+hf-fvesely@users.noreply.github.com> Co-authored-by: Kevin <68426071+hf-krechan@users.noreply.github.com>
1 parent 0a48a81 commit 46e6e4b

3 files changed

Lines changed: 186 additions & 19 deletions

File tree

src/bo4e/bo/ausschreibung.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""
2+
Contains Ausschreibung class and corresponding marshmallow schema for de-/serialization
3+
"""
4+
from datetime import datetime
5+
from typing import List, Optional
6+
7+
import attr
8+
from marshmallow import fields
9+
from marshmallow_enum import EnumField # type:ignore[import]
10+
11+
from bo4e.bo.geschaeftsobjekt import Geschaeftsobjekt, GeschaeftsobjektSchema
12+
from bo4e.bo.geschaeftspartner import Geschaeftspartner, GeschaeftspartnerSchema
13+
from bo4e.com.ausschreibungslos import Ausschreibungslos, AusschreibungslosSchema
14+
from bo4e.com.zeitraum import Zeitraum, ZeitraumSchema
15+
from bo4e.enum.ausschreibungsportal import Ausschreibungsportal
16+
from bo4e.enum.ausschreibungsstatus import Ausschreibungsstatus
17+
from bo4e.enum.ausschreibungstyp import Ausschreibungstyp
18+
from bo4e.enum.botyp import BoTyp
19+
from bo4e.validators import check_list_length_at_least_one
20+
21+
22+
# pylint: disable=too-few-public-methods, too-many-instance-attributes
23+
@attr.s(auto_attribs=True, kw_only=True)
24+
class Ausschreibung(Geschaeftsobjekt):
25+
"""
26+
Das BO Ausschreibung dient zur detaillierten Darstellung von ausgeschriebenen Energiemengen in der Energiewirtschaft
27+
"""
28+
29+
# required attributes
30+
bo_typ: BoTyp = attr.ib(default=BoTyp.AUSSCHREIBUNG)
31+
#: Vom Herausgeber der Ausschreibung vergebene eindeutige Nummer
32+
ausschreibungsnummer: str = attr.ib(validator=attr.validators.instance_of(str))
33+
#: Aufzählung für die Typisierung von Ausschreibungen
34+
ausschreibungstyp: Ausschreibungstyp = attr.ib(validator=attr.validators.instance_of(Ausschreibungstyp))
35+
#: Bezeichnungen für die Ausschreibungsphasen
36+
ausschreibungsstatus: Ausschreibungsstatus = attr.ib(validator=attr.validators.instance_of(Ausschreibungsstatus))
37+
#: Kennzeichen, ob die Ausschreibung kostenpflichtig ist
38+
kostenpflichtig: bool = attr.ib(validator=attr.validators.instance_of(bool))
39+
#: Gibt den Veröffentlichungszeitpunkt der Ausschreibung an
40+
veroeffentlichungszeitpunkt: datetime = attr.ib(validator=attr.validators.instance_of(datetime))
41+
ausschreibender: Geschaeftspartner = attr.ib(validator=attr.validators.instance_of(Geschaeftspartner))
42+
"""
43+
Mit diesem Objekt können Geschäftspartner übertragen werden.
44+
Sowohl Unternehmen, als auch Privatpersonen können Geschäftspartner sein
45+
"""
46+
abgabefrist: Zeitraum = attr.ib(validator=attr.validators.instance_of(Zeitraum))
47+
"""
48+
Diese Komponente wird zur Abbildung von Zeiträumen in Form von Dauern oder der Angabe von Start und Ende verwendet.
49+
Es muss daher entweder eine Dauer oder ein Zeitraum in Form von Start und Ende angegeben sein
50+
"""
51+
bindefrist: Zeitraum = attr.ib(validator=attr.validators.instance_of(Zeitraum))
52+
"""
53+
Diese Komponente wird zur Abbildung von Zeiträumen in Form von Dauern oder der Angabe von Start und Ende verwendet.
54+
Es muss daher entweder eine Dauer oder ein Zeitraum in Form von Start und Ende angegeben sein
55+
"""
56+
#: Die einzelnen Lose, aus denen sich die Ausschreibung zusammensetzt
57+
lose: List[Ausschreibungslos] = attr.ib(
58+
validator=attr.validators.deep_iterable(
59+
member_validator=attr.validators.instance_of(Ausschreibungslos),
60+
iterable_validator=check_list_length_at_least_one,
61+
)
62+
)
63+
64+
# optional attributes
65+
#: Aufzählung der unterstützten Ausschreibungsportale
66+
ausschreibungportal: Optional[Ausschreibungsportal] = attr.ib(
67+
default=None, validator=attr.validators.optional(attr.validators.instance_of(Ausschreibungsportal))
68+
)
69+
#: Internetseite, auf der die Ausschreibung veröffentlicht wurde (falls vorhanden)
70+
webseite: Optional[str] = attr.ib(
71+
default=None, validator=attr.validators.optional(attr.validators.instance_of(str))
72+
)
73+
74+
75+
class AusschreibungSchema(GeschaeftsobjektSchema):
76+
"""
77+
Schema for de-/serialization of Ausschreibung
78+
"""
79+
80+
class_name = Ausschreibung
81+
82+
# required attributes
83+
ausschreibungsnummer = fields.Str()
84+
ausschreibungstyp = EnumField(Ausschreibungstyp)
85+
ausschreibungsstatus = EnumField(Ausschreibungsstatus)
86+
kostenpflichtig = fields.Bool()
87+
veroeffentlichungszeitpunkt = fields.DateTime()
88+
ausschreibender = fields.Nested(GeschaeftspartnerSchema)
89+
abgabefrist = fields.Nested(ZeitraumSchema)
90+
bindefrist = fields.Nested(ZeitraumSchema)
91+
lose = fields.List(fields.Nested(AusschreibungslosSchema))
92+
93+
# optional attributes
94+
ausschreibungportal = EnumField(Ausschreibungsportal, allow_none=True)
95+
webseite = fields.Str(allow_none=True)

tests/test_ausschreibung.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from datetime import datetime, timezone
2+
3+
import pytest # type:ignore[import]
4+
5+
from bo4e.bo.ausschreibung import Ausschreibung, AusschreibungSchema
6+
from bo4e.bo.geschaeftspartner import Geschaeftspartner
7+
from bo4e.enum.ausschreibungsportal import Ausschreibungsportal
8+
from bo4e.enum.ausschreibungsstatus import Ausschreibungsstatus
9+
from bo4e.enum.ausschreibungstyp import Ausschreibungstyp
10+
from bo4e.enum.geschaeftspartnerrolle import Geschaeftspartnerrolle
11+
from tests.serialization_helper import assert_serialization_roundtrip # type:ignore[import]
12+
from tests.test_adresse import example_adresse # type:ignore[import]
13+
from tests.test_ausschreibungslos import example_ausschreibungslos # type:ignore[import]
14+
from tests.test_zeitraum import example_zeitraum # type:ignore[import]
15+
16+
17+
class TestAusschreibung:
18+
@pytest.mark.parametrize(
19+
"ausschreibung",
20+
[
21+
pytest.param(
22+
Ausschreibung(
23+
ausschreibungsnummer="239230",
24+
ausschreibungstyp=Ausschreibungstyp.PRIVATRECHTLICH,
25+
ausschreibungsstatus=Ausschreibungsstatus.PHASE3,
26+
kostenpflichtig=True,
27+
ausschreibungportal=Ausschreibungsportal.BMWI,
28+
webseite="https://meineausschreibungswebsite.inv/",
29+
veroeffentlichungszeitpunkt=datetime(2022, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
30+
abgabefrist=example_zeitraum,
31+
bindefrist=example_zeitraum,
32+
ausschreibender=Geschaeftspartner(
33+
name1="Batman",
34+
gewerbekennzeichnung=True,
35+
geschaeftspartnerrolle=[Geschaeftspartnerrolle.LIEFERANT],
36+
partneradresse=example_adresse,
37+
),
38+
lose=[example_ausschreibungslos],
39+
),
40+
id="maximal attributes",
41+
),
42+
pytest.param(
43+
Ausschreibung(
44+
ausschreibungsnummer="239230",
45+
ausschreibungstyp=Ausschreibungstyp.PRIVATRECHTLICH,
46+
ausschreibungsstatus=Ausschreibungsstatus.PHASE3,
47+
kostenpflichtig=True,
48+
veroeffentlichungszeitpunkt=datetime(2022, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
49+
abgabefrist=example_zeitraum,
50+
bindefrist=example_zeitraum,
51+
ausschreibender=Geschaeftspartner(
52+
name1="Batman",
53+
gewerbekennzeichnung=True,
54+
geschaeftspartnerrolle=[Geschaeftspartnerrolle.LIEFERANT],
55+
partneradresse=example_adresse,
56+
),
57+
lose=[example_ausschreibungslos],
58+
),
59+
id="minimal attributes",
60+
),
61+
],
62+
)
63+
def test_serialization_roundtrip(self, ausschreibung: Ausschreibung):
64+
assert_serialization_roundtrip(ausschreibung, AusschreibungSchema())
65+
66+
def test_missing_required_attribute(self):
67+
with pytest.raises(TypeError) as excinfo:
68+
_ = Ausschreibung()
69+
70+
assert "missing 9 required" in str(excinfo.value)

tests/test_ausschreibungslos.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,33 @@
1313
from tests.test_menge import example_menge, example_menge_dict # type:ignore[import]
1414
from tests.test_zeitraum import example_zeitraum, example_zeitraum_dict # type:ignore[import]
1515

16+
example_ausschreibungslos = Ausschreibungslos(
17+
losnummer="foo",
18+
bezeichnung="bar",
19+
bemerkung="asd",
20+
preismodell=Preismodell.FESTPREIS,
21+
energieart=Sparte.STROM,
22+
wunsch_rechnungslegung=Rechnungslegung.MONATSRECHN,
23+
wunsch_vertragsform=Vertragsform.DIREKT,
24+
betreut_durch="Max Mustermann",
25+
anzahl_lieferstellen=17,
26+
lieferstellen=[example_ausschreibungsdetail],
27+
gesamt_menge=example_menge,
28+
wunsch_mindestmenge=example_menge,
29+
wunsch_maximalmenge=example_menge,
30+
lieferzeitraum=example_zeitraum,
31+
wunsch_kuendingungsfrist=example_zeitraum,
32+
wunsch_zahlungsziel=example_zeitraum,
33+
wiederholungsintervall=example_zeitraum,
34+
)
35+
1636

1737
class TestAusschreibungslos:
1838
@pytest.mark.parametrize(
1939
"ausschreibungslos, expected_json_dict",
2040
[
2141
pytest.param(
22-
Ausschreibungslos(
23-
losnummer="foo",
24-
bezeichnung="bar",
25-
bemerkung="asd",
26-
preismodell=Preismodell.FESTPREIS,
27-
energieart=Sparte.STROM,
28-
wunsch_rechnungslegung=Rechnungslegung.MONATSRECHN,
29-
wunsch_vertragsform=Vertragsform.DIREKT,
30-
betreut_durch="Max Mustermann",
31-
anzahl_lieferstellen=17,
32-
lieferstellen=[example_ausschreibungsdetail],
33-
gesamt_menge=example_menge,
34-
wunsch_mindestmenge=example_menge,
35-
wunsch_maximalmenge=example_menge,
36-
lieferzeitraum=example_zeitraum,
37-
wunsch_kuendingungsfrist=example_zeitraum,
38-
wunsch_zahlungsziel=example_zeitraum,
39-
wiederholungsintervall=example_zeitraum,
40-
),
42+
example_ausschreibungslos,
4143
{
4244
"lieferzeitraum": example_zeitraum_dict,
4345
"preismodell": "FESTPREIS",

0 commit comments

Comments
 (0)