diff --git a/src/bo4e/__init__.py b/src/bo4e/__init__.py index 6fac9267d..0bbf83b1c 100644 --- a/src/bo4e/__init__.py +++ b/src/bo4e/__init__.py @@ -11,6 +11,7 @@ __all__ = [ "Angebot", "Ausschreibung", + "Avis", "Buendelvertrag", "Energiemenge", "Fremdkosten", @@ -40,6 +41,8 @@ "Vertrag", "Zaehler", "Zeitreihe", + "Abweichung", + "Abweichungsposition", "Adresse", "Angebotsposition", "Angebotsteil", @@ -50,6 +53,7 @@ "AufAbschlagstaffelProOrt", "Ausschreibungsdetail", "Ausschreibungslos", + "Avisposition", "Betrag", "COM", "Dienstleistung", @@ -79,6 +83,7 @@ "RegionalerAufAbschlag", "RegionaleTarifpreisposition", "Regionskriterium", + "Rueckmeldungsposition", "Sigmoidparameter", "StandorteigenschaftenGas", "StandorteigenschaftenStrom", @@ -101,6 +106,7 @@ "Zeitspanne", "Zustaendigkeit", "AbgabeArt", + "Abweichungsgrund", "Angebotsstatus", "Anrede", "ArithmetischeOperation", @@ -110,6 +116,7 @@ "Ausschreibungsportal", "Ausschreibungsstatus", "Ausschreibungstyp", + "AvisTyp", "BDEWArtikelnummer", "Befestigungsart", "Bemessungsgroesse", @@ -190,6 +197,7 @@ # Import BOs from .bo.angebot import Angebot from .bo.ausschreibung import Ausschreibung +from .bo.avis import Avis from .bo.buendelvertrag import Buendelvertrag from .bo.energiemenge import Energiemenge from .bo.fremdkosten import Fremdkosten @@ -221,6 +229,8 @@ from .bo.zeitreihe import Zeitreihe # Import COMs +from .com.abweichung import Abweichung +from .com.abweichungsposition import Abweichungsposition from .com.adresse import Adresse from .com.angebotsposition import Angebotsposition from .com.angebotsteil import Angebotsteil @@ -231,6 +241,7 @@ from .com.aufabschlagstaffelproort import AufAbschlagstaffelProOrt from .com.ausschreibungsdetail import Ausschreibungsdetail from .com.ausschreibungslos import Ausschreibungslos +from .com.avisposition import Avisposition from .com.betrag import Betrag from .com.com import COM from .com.dienstleistung import Dienstleistung @@ -260,6 +271,7 @@ from .com.regionaleraufabschlag import RegionalerAufAbschlag from .com.regionaletarifpreisposition import RegionaleTarifpreisposition from .com.regionskriterium import Regionskriterium +from .com.rueckmeldungsposition import Rueckmeldungsposition from .com.sigmoidparameter import Sigmoidparameter from .com.standorteigenschaftengas import StandorteigenschaftenGas from .com.standorteigenschaftenstrom import StandorteigenschaftenStrom @@ -284,6 +296,7 @@ # Import Enums from .enum.abgabeart import AbgabeArt +from .enum.abweichungsgrund import Abweichungsgrund from .enum.angebotsstatus import Angebotsstatus from .enum.anrede import Anrede from .enum.arithmetische_operation import ArithmetischeOperation @@ -293,6 +306,7 @@ from .enum.ausschreibungsportal import Ausschreibungsportal from .enum.ausschreibungsstatus import Ausschreibungsstatus from .enum.ausschreibungstyp import Ausschreibungstyp +from .enum.avistyp import AvisTyp from .enum.bdewartikelnummer import BDEWArtikelnummer from .enum.befestigungsart import Befestigungsart from .enum.bemessungsgroesse import Bemessungsgroesse diff --git a/src/bo4e/bo/avis.py b/src/bo4e/bo/avis.py new file mode 100644 index 000000000..e12c14793 --- /dev/null +++ b/src/bo4e/bo/avis.py @@ -0,0 +1,22 @@ +"""Contains class Avis""" +from typing import Annotated, Optional + +from pydantic import Field + +from bo4e.bo.geschaeftsobjekt import Geschaeftsobjekt +from bo4e.com.avisposition import Avisposition +from bo4e.com.betrag import Betrag +from bo4e.enum.avistyp import AvisTyp + +from ..enum.typ import Typ + + +class Avis(Geschaeftsobjekt): + """Avis BO""" + + typ: Annotated[Optional[Typ], Field(alias="_typ")] = Typ.AVIS + + avis_nummer: str #: Eine im Verwendungskontext eindeutige Nummer für das Avis. + avis_typ: AvisTyp #: Gibt den Typ des Avis an. + positionen: list[Avisposition] #: Avispositionen + zu_zahlen: Betrag #: Summenbetrag diff --git a/src/bo4e/com/abweichung.py b/src/bo4e/com/abweichung.py new file mode 100644 index 000000000..72db2d7ff --- /dev/null +++ b/src/bo4e/com/abweichung.py @@ -0,0 +1,16 @@ +"""Contains Class: Abweichung.""" +from typing import Optional + +from bo4e.com.com import COM +from bo4e.enum.abweichungsgrund import Abweichungsgrund + + +class Abweichung(COM): + """Zur Angabe einer Abweichung bei Ablehnung einer COMDIS. (REMADV SG5 RFF und SG7 AJT/FTX).""" + + abweichungsgrund: Optional[Abweichungsgrund] = None #: Angabe Abweichungsgrund + abweichungsgrund_bemerkung: Optional[str] = None #: Nähere Erläuterung zum Abweichungsgrund + zugehoerige_rechnung: Optional[str] = None #: Zugehoerige Rechnung + abschlagsrechnung: Optional[str] = None #: Abschlagsrechnung + abweichungsgrund_code: Optional[str] = None #: Angabe Abweichungsgrund(Code) + abweichungsgrund_codeliste: Optional[str] = None #: Angabe Abweichungsgrund(Codeliste) diff --git a/src/bo4e/com/abweichungsposition.py b/src/bo4e/com/abweichungsposition.py new file mode 100644 index 000000000..67a57c4ba --- /dev/null +++ b/src/bo4e/com/abweichungsposition.py @@ -0,0 +1,14 @@ +"""Contains class Abweichungsposition.""" +from typing import Optional + +from bo4e.com.com import COM + + +class Abweichungsposition(COM): + """Zur Angabe einer Abweichung einer einzelnen Position.""" + + abweichungsgrund_code: Optional[str] = None #: Angabe Abweichungsgrund(Code) + abweichungsgrund_codeliste: Optional[str] = None #:Angabe Abweichungsgrund(Codeliste) + abweichungsgrund_bemerkung: Optional[str] = None #: Nähere Erläuterung zum Abweichungsgrund + zugehoerige_rechnung: Optional[str] = None #: Zugehörige Rechnung + zugehoerige_bestellung: Optional[str] = None #: Zugehörige Bestellung diff --git a/src/bo4e/com/avisposition.py b/src/bo4e/com/avisposition.py new file mode 100644 index 000000000..f4d7b72f2 --- /dev/null +++ b/src/bo4e/com/avisposition.py @@ -0,0 +1,25 @@ +"""Contains class Avisposition""" +from datetime import datetime +from typing import Optional + +from bo4e.com.abweichung import Abweichung +from bo4e.com.betrag import Betrag +from bo4e.com.com import COM +from bo4e.com.rueckmeldungsposition import Rueckmeldungsposition + + +class Avisposition(COM): + """Die Position eines Avis""" + + rechnungs_nummer: str #: Die Rechnungsnummer der Rechnung, auf welche sich das Avis bezieht. + rechnungs_datum: datetime #: Das Rechnungsdatum der Rechnung, auf die sich das Avis bezieht. + ist_storno: bool + #: Kennzeichnung, ob es sich bei der Rechnung auf die sich das Avis bezieht, um eine Stornorechnung handelt. + gesamtbrutto: Betrag #: Überweisungsbetrag + zu_zahlen: Betrag #: Geforderter Rechnungsbetrag + + ist_selbstausgestellt: Optional[bool] = None + #: Kennzeichnung, ob es sich bei der Rechnung auf die sich das Avis bezieht, um eine Stornorechnung handelt. + referenz: Optional[str] = None #: Referenzierung auf eine vorherige COMDIS-Nachricht + abweichungen: Optional[list[Abweichung]] = None #: Abweichungen bei Ablehnung einer COMDIS + positionen: Optional[list[Rueckmeldungsposition]] = None #: Rückmeldungspositionen diff --git a/src/bo4e/com/rueckmeldungsposition.py b/src/bo4e/com/rueckmeldungsposition.py new file mode 100644 index 000000000..2b0ea5fc7 --- /dev/null +++ b/src/bo4e/com/rueckmeldungsposition.py @@ -0,0 +1,25 @@ +"""Contains class Rueckmeldeposition""" +from typing import Optional + +from pydantic import model_validator + +from bo4e.com.abweichungsposition import Abweichungsposition +from bo4e.com.com import COM + + +class Rueckmeldungsposition(COM): + """Zur Angabe einer Rückmeldung einer einzelnen Position.""" + + positionsnummer: Optional[str] = None #: Positionsnummer der Referenzierung + abweichungspositionen: Optional[list[Abweichungsposition]] = None #: Abweichungspositionen + + @model_validator(mode="after") + def _field_combination_xor(self) -> "Rueckmeldungsposition": + """Model validator. + Ensures that either both or no attributes are set + """ + if (self.positionsnummer and self.abweichungspositionen is None) or ( + self.abweichungspositionen and self.positionsnummer is None + ): + raise ValueError("Attributes missing") + return self diff --git a/src/bo4e/enum/abweichungsgrund.py b/src/bo4e/enum/abweichungsgrund.py new file mode 100644 index 000000000..fb0b058fa --- /dev/null +++ b/src/bo4e/enum/abweichungsgrund.py @@ -0,0 +1,45 @@ +"""Contains Enums for Abweichungsgrund.""" +from bo4e.enum.strenum import StrEnum + + +class Abweichungsgrund(StrEnum): + """Gibt einen Abweichungsgrund bei Ablehung einer COMDIS an. (REMADV SG7 AJT 4465)""" + + PREIS_RECHENREGEL_FALSCH = "PREIS_RECHENREGEL_FALSCH" #: PREIS_RECHENREGEL_FALSCH, 5 + FALSCHER_ABRECHNUNGSZEITRAUM = "FALSCHER_ABRECHNUNGSZEITRAUM" #: FALSCHER_ABRECHNUNGSZEITRAUM, 9 + UNBEKANNTE_MARKTLOKATION_MESSLOKATION = "UNBEKANNTE_MARKTLOKATION_MESSLOKATION" + """UNBEKANNTE_MARKTLOKATION_MESSLOKATION, 14.""" + SONSTIGER_ABWEICHUNGSGRUND = "SONSTIGER_ABWEICHUNGSGRUND" #: SONSTIGER_ABWEICHUNGSGRUND, 28 + DOPPELTE_RECHNUNG = "DOPPELTE_RECHNUNG" #: DOPPELTE_RECHNUNG, 53 + ABRECHNUNGSBEGINN_UNGLEICH_VERTRAGSBEGINN = "ABRECHNUNGSBEGINN_UNGLEICH_VERTRAGSBEGINN" + """ABRECHNUNGSBEGINN_UNGLEICH_VERTRAGSBEGINN, Z01.""" + ABRECHNUNGSENDE_UNGLEICH_VERTRAGSENDE = "ABRECHNUNGSENDE_UNGLEICH_VERTRAGSENDE" + """ABRECHNUNGSENDE_UNGLEICH_VERTRAGSENDE, Z02.""" + BETRAG_DER_ABSCHLAGSRECHNUNG_FALSCH = "BETRAG_DER_ABSCHLAGSRECHNUNG_FALSCH" + "BETRAG_DER_ABSCHLAGSRECHNUNG_FALSCH, Z03." + VORAUSBEZAHLTER_BETRAG_FALSCH = "VORAUSBEZAHLTER_BETRAG_FALSCH" #: VORAUSBEZAHLTER_BETRAG_FALSCH, Z04 + ARTIKEL_NICHT_VEREINBART = "ARTIKEL_NICHT_VEREINBART" #: ARTIKEL_NICHT_VEREINBART, Z06 + NETZNUTZUNGSMESSWERTE_ENERGIEMENGEN_FEHLEN = "NETZNUTZUNGSMESSWERTE_ENERGIEMENGEN_FEHLEN" + """NETZNUTZUNGSMESSWERTE_ENERGIEMENGEN_FEHLEN, Z07.""" + RECHNUNGSNUMMER_BEREITS_ERHALTEN = "RECHNUNGSNUMMER_BEREITS_ERHALTEN" #: RECHNUNGSNUMMER_BEREITS_ERHALTEN, Z08 + NETZNUTZUNGSMESSWERTE_ENERGIEMENGEN_FALSCH = "NETZNUTZUNGSMESSWERTE_ENERGIEMENGEN_FALSCH" + """NETZNUTZUNGSMESSWERTE_ENERGIEMENGEN_FALSCH, Z10.""" + ZEITLICHE_MENGENANGABE_FEHLERHAFT = "ZEITLICHE_MENGENANGABE_FEHLERHAFT" #: ZEITLICHE_MENGENANGABE_FEHLERHAFT, Z33 + FALSCHER_BILANZIERUNGSBEGINN = "FALSCHER_BILANZIERUNGSBEGINN" #: FALSCHER_BILANZIERUNGSBEGINN, Z35 + FALSCHES_NETZNUTZUNGSENDE = "FALSCHES_NETZNUTZUNGSENDE" #: FALSCHES_NETZNUTZUNGSENDE, Z36 + BILANZIERTE_MENGE_FEHLT = "BILANZIERTE_MENGE_FEHLT" #: BILANZIERTE_MENGE_FEHLT, Z37 + BILANZIERTE_MENGE_FALSCH = "BILANZIERTE_MENGE_FALSCH" #: BILANZIERTE_MENGE_FALSCH, Z38 + NETZNUTZUNGSABRECHNUNG_FEHLT = "NETZNUTZUNGSABRECHNUNG_FEHLT" #: NETZNUTZUNGSABRECHNUNG_FEHLT, Z39 + REVERSE_CHARGE_ANWENDUNG_FEHLT_ODER_FEHLERHAFT = "REVERSE_CHARGE_ANWENDUNG_FEHLT_ODER_FEHLERHAFT" + """REVERSE_CHARGE_ANWENDUNG_FEHLT_ODER_FEHLERHAFT, Z40.""" + ALLOKATIONSLISTE_FEHLT = "ALLOKATIONSLISTE_FEHLT" #: ALLOKATIONSLISTE_FEHLT, Z41 + MEHR_MINDERMENGE_FALSCH = "MEHR_MINDERMENGE_FALSCH" #: MEHR_MINDERMENGE_FALSCH, Z42 + UNGUELTIGES_RECHNUNGSDATUM = "UNGUELTIGES_RECHNUNGSDATUM" #: UNGUELTIGES_RECHNUNGSDATUM, Z43 + ZEITINTERVALL_DER_BILANZIERTEN_MENGE_INKONSISTENT = "ZEITINTERVALL_DER_BILANZIERTEN_MENGE_INKONSISTENT" + "ZEITINTERVALL_DER_BILANZIERTEN_MENGE_INKONSISTENT, Z44." + RECHNUNGSEMPFAENGER_WIDERSPRICHT_DER_STEUERRECHTLICHEN_EINSCHAETZUNG_DES_RECHNUNGSSTELLERS = "RECHNUNGSEMPFAENGER_WIDERSPRICHT_DER_STEUERRECHTLICHEN_EINSCHAETZUNG_DES_RECHNUNGSSTELLERS" #: RECHNUNGSEMPFAENGER_WIDERSPRICHT_DER_STEUERRECHTLICHEN_EINSCHAETZUNG_DES_RECHNUNGSSTELLERS, Z45 + ANGEGEBENE_QUOTES_AN_MARKTLOKATION_NICHT_VORHANDEN = "ANGEGEBENE_QUOTES_AN_MARKTLOKATION_NICHT_VORHANDEN" + """ANGEGEBENE_QUOTES_AN_MARKTLOKATION_NICHT_VORHANDEN, Z52.""" + RECHNUNGSABWICKLUNG_NICHT_VEREINBART = "RECHNUNGSABWICKLUNG_NICHT_VEREINBART" + """RECHNUNGSABWICKLUNG_NICHT_VEREINBART, Z53.""" + COMDIS_WIRD_ABGELEHNT = "COMDIS_WIRD_ABGELEHNT" #: COMDIS_WIRD_ABGELEHNT, Z63 diff --git a/src/bo4e/enum/avistyp.py b/src/bo4e/enum/avistyp.py new file mode 100644 index 000000000..276273c5d --- /dev/null +++ b/src/bo4e/enum/avistyp.py @@ -0,0 +1,9 @@ +"""Contains Enums for Avisposition""" +from bo4e.enum.strenum import StrEnum + + +class AvisTyp(StrEnum): + """Gibt den Typ des Avis an. (REMADV BGM 1001).""" + + ABGELEHNTE_FORDERUNG = "ABGELEHNTE_FORDERUNG" #: Abgelehnte Forderung + ZAHLUNGSAVIS = "ZAHLUNGSAVIS" #: Zahlungsavis diff --git a/src/bo4e/enum/typ.py b/src/bo4e/enum/typ.py index 1124bb99e..76aefbaf9 100644 --- a/src/bo4e/enum/typ.py +++ b/src/bo4e/enum/typ.py @@ -9,6 +9,7 @@ class Typ(StrEnum): ANGEBOT = "ANGEBOT" AUSSCHREIBUNG = "AUSSCHREIBUNG" + AVIS = "AVIS" BUENDELVERTRAG = "BUENDELVERTRAG" ENERGIEMENGE = "ENERGIEMENGE" FREMDKOSTEN = "FREMDKOSTEN" diff --git a/tests/test_abweichung.py b/tests/test_abweichung.py new file mode 100644 index 000000000..e282e2cbc --- /dev/null +++ b/tests/test_abweichung.py @@ -0,0 +1,28 @@ +import pytest + +from bo4e.com.abweichung import Abweichung +from bo4e.enum.abweichungsgrund import Abweichungsgrund +from tests.serialization_helper import assert_serialization_roundtrip + + +class Test_Abweichung: + @pytest.mark.parametrize( + "abweichung", + [ + pytest.param( + Abweichung( + abweichungsgrund=Abweichungsgrund.UNBEKANNTE_MARKTLOKATION_MESSLOKATION, + abweichungsgrund_bemerkung="sonst", + zugehoerige_rechnung="458011", + abschlagsrechnung="4580112", + abweichungsgrund_code="14", + abweichungsgrund_codeliste="G_0081", + ) + ), + ], + ) + def test_serialization_roundtrip(self, abweichung: Abweichung) -> None: + """ + Test de-/serialisation of Abweichung. + """ + assert_serialization_roundtrip(abweichung) diff --git a/tests/test_abweichungsposition.py b/tests/test_abweichungsposition.py new file mode 100644 index 000000000..904afeb51 --- /dev/null +++ b/tests/test_abweichungsposition.py @@ -0,0 +1,26 @@ +import pytest + +from bo4e.com.abweichungsposition import Abweichungsposition +from tests.serialization_helper import assert_serialization_roundtrip + + +class TestAbweichungsposition: + @pytest.mark.parametrize( + "abweichungsposition", + [ + pytest.param( + Abweichungsposition( + abweichungsgrund_code="14", + abweichungsgrund_codeliste="G_0081", + abweichungsgrund_bemerkung="Umsatzsteuersatz", + zugehoerige_rechnung="458011", + zugehoerige_bestellung="foo", + ) + ), + ], + ) + def test_serialization_roundtrip(self, abweichungsposition: Abweichungsposition) -> None: + """ + Test de-/serialisation of Abweichungsposition. + """ + assert_serialization_roundtrip(abweichungsposition) diff --git a/tests/test_avis.py b/tests/test_avis.py new file mode 100644 index 000000000..063e630e4 --- /dev/null +++ b/tests/test_avis.py @@ -0,0 +1,79 @@ +from datetime import datetime + +import pytest +from _decimal import Decimal + +from bo4e.bo.avis import Avis +from bo4e.com.abweichung import Abweichung +from bo4e.com.abweichungsposition import Abweichungsposition +from bo4e.com.avisposition import Avisposition +from bo4e.com.betrag import Betrag +from bo4e.com.rueckmeldungsposition import Rueckmeldungsposition +from bo4e.enum.abweichungsgrund import Abweichungsgrund +from bo4e.enum.avistyp import AvisTyp +from bo4e.enum.waehrungscode import Waehrungscode +from tests.serialization_helper import assert_serialization_roundtrip + + +class TestAvis: + @pytest.mark.parametrize( + "avis", + [ + pytest.param( + Avis( + avis_nummer="654321", + avis_typ=AvisTyp.ZAHLUNGSAVIS, + positionen=[ + Avisposition( + rechnungs_nummer="12345", + rechnungs_datum=datetime(2022, 1, 1, 0, 0, 0), + ist_storno=True, + gesamtbrutto=Betrag( + wert=Decimal(100.5), + waehrung=Waehrungscode.EUR, + ), + zu_zahlen=Betrag( + wert=Decimal(15.5), + waehrung=Waehrungscode.EUR, + ), + ist_selbstausgestellt=True, + referenz="1234", + abweichungen=[ + Abweichung( + abweichungsgrund=Abweichungsgrund.UNBEKANNTE_MARKTLOKATION_MESSLOKATION, + abweichungsgrund_bemerkung="sonst", + zugehoerige_rechnung="458011", + abschlagsrechnung="4580112", + abweichungsgrund_code="14", + abweichungsgrund_codeliste="G_0081", + ), + ], + positionen=[ + Rueckmeldungsposition( + positionsnummer="1", + abweichungspositionen=[ + Abweichungsposition( + abweichungsgrund_code="foo", + abweichungsgrund_codeliste="foo", + abweichungsgrund_bemerkung="foo", + zugehoerige_rechnung="458011", + zugehoerige_bestellung="foo", + ), + ], + ), + ], + ), + ], + zu_zahlen=Betrag( + wert=Decimal(15.5), + waehrung=Waehrungscode.EUR, + ), + ) + ), + ], + ) + def test_serialization_roundtrip(self, avis: Avis) -> None: + """ + Test de-/serialisation of Avis. + """ + assert_serialization_roundtrip(avis) diff --git a/tests/test_avisposition.py b/tests/test_avisposition.py new file mode 100644 index 000000000..ad643986a --- /dev/null +++ b/tests/test_avisposition.py @@ -0,0 +1,67 @@ +from datetime import datetime + +import pytest +from _decimal import Decimal + +from bo4e.com.abweichung import Abweichung +from bo4e.com.abweichungsposition import Abweichungsposition +from bo4e.com.avisposition import Avisposition +from bo4e.com.betrag import Betrag +from bo4e.com.rueckmeldungsposition import Rueckmeldungsposition +from bo4e.enum.abweichungsgrund import Abweichungsgrund +from bo4e.enum.waehrungscode import Waehrungscode +from tests.serialization_helper import assert_serialization_roundtrip + + +class TestAvisposition: + @pytest.mark.parametrize( + "avisposition", + [ + pytest.param( + Avisposition( + rechnungs_nummer="12345", + rechnungs_datum=datetime(2022, 1, 1, 0, 0, 0), + ist_storno=True, + gesamtbrutto=Betrag( + wert=Decimal(100.5), + waehrung=Waehrungscode.EUR, + ), + zu_zahlen=Betrag( + wert=Decimal(15.5), + waehrung=Waehrungscode.EUR, + ), + ist_selbstausgestellt=True, + referenz="1234", + abweichungen=[ + Abweichung( + abweichungsgrund=Abweichungsgrund.UNBEKANNTE_MARKTLOKATION_MESSLOKATION, + abweichungsgrund_bemerkung="sonst", + zugehoerige_rechnung="458011", + abschlagsrechnung="4580112", + abweichungsgrund_code="14", + abweichungsgrund_codeliste="G_0081", + ), + ], + positionen=[ + Rueckmeldungsposition( + positionsnummer="1", + abweichungspositionen=[ + Abweichungsposition( + abweichungsgrund_code="foo", + abweichungsgrund_codeliste="foo", + abweichungsgrund_bemerkung="foo", + zugehoerige_rechnung="458011", + zugehoerige_bestellung="foo", + ), + ], + ), + ], + ) + ), + ], + ) + def test_serialization_roundtrip(self, avisposition: Avisposition) -> None: + """ + Test de-/serialisation of Avisposition. + """ + assert_serialization_roundtrip(avisposition) diff --git a/tests/test_rueckmeldungsposition.py b/tests/test_rueckmeldungsposition.py new file mode 100644 index 000000000..5b2d82af4 --- /dev/null +++ b/tests/test_rueckmeldungsposition.py @@ -0,0 +1,57 @@ +import pytest +from pydantic import ValidationError + +from bo4e.com.abweichungsposition import Abweichungsposition +from bo4e.com.rueckmeldungsposition import Rueckmeldungsposition +from tests.serialization_helper import assert_serialization_roundtrip + + +class TestRueckmeldungsposition: + @pytest.mark.parametrize( + "rueckmeldungsposition", + [ + pytest.param( + Rueckmeldungsposition( + positionsnummer="1", + abweichungspositionen=[ + Abweichungsposition( + abweichungsgrund_code="A15", + abweichungsgrund_codeliste="E_0210", + abweichungsgrund_bemerkung="Umsatzsteuersatz", + zugehoerige_rechnung="458011", + zugehoerige_bestellung="foo", + ), + ], + ) + ), + ], + ) + def test_serialization_roundtrip(self, rueckmeldungsposition: Rueckmeldungsposition) -> None: + """ + Test de-/serialisation of Rueckmeldungsposition. + """ + assert_serialization_roundtrip(rueckmeldungsposition) + + def test_required_field_combinations(self) -> None: + """ + Test different Rueckmeldungspositionen. + If one field is given the other is required as well. + """ + # todo: is this useful? + with pytest.raises(ValidationError) as excinfo: + _ = Rueckmeldungsposition(positionsnummer="1") + assert "1 validation error" in str(excinfo.value) + + with pytest.raises(ValidationError) as excinfo: + _ = Rueckmeldungsposition( + abweichungspositionen=[ + Abweichungsposition( + abweichungsgrund_code="A15", + abweichungsgrund_codeliste="E_0210", + abweichungsgrund_bemerkung="Umsatzsteuersatz", + zugehoerige_rechnung="458011", + zugehoerige_bestellung="foo", + ) + ] + ) + assert "1 validation error" in str(excinfo.value)