diff --git a/com/rechnungsposition.go b/com/rechnungsposition.go index d95e007..5254920 100644 --- a/com/rechnungsposition.go +++ b/com/rechnungsposition.go @@ -3,31 +3,38 @@ package com import ( "time" + "github.com/hochfrequenz/go-bo4e/enum/rechnungspositionsabschlag" + "github.com/hochfrequenz/go-bo4e/enum/rechnungspositionszuschlag" + "github.com/go-playground/validator/v10" "github.com/hochfrequenz/go-bo4e/enum/bdewartikelnummer" "github.com/hochfrequenz/go-bo4e/enum/waehrungscode" "github.com/hochfrequenz/go-bo4e/enum/waehrungseinheit" "github.com/hochfrequenz/go-bo4e/enum/zeiteinheit" + "github.com/shopspring/decimal" ) // Rechnungsposition en sind Teil von Rechnung en. In einem Rechnungsteil wird jeweils eine in sich geschlossene LEISTUNG abgerechnet. type Rechnungsposition struct { - Positionsnummer int `json:"positionsnummer,omitempty" validate:"required"` // Positionsnummer ist eine fortlaufende Nummer für die Rechnungsposition - LieferungVon time.Time `json:"lieferungVon,omitempty"` // LieferungVon ist ein _inklusiver_ Start der Lieferung für die abgerechnete LEISTUNG - LieferungBis time.Time `json:"lieferungBis,omitempty" validate:"omitempty,gtfield=LieferungVon"` // LieferungBis ist ein _exklusives_ Ende der Lieferung für die abgerechnete LEISTUNG - Positionstext string `json:"positionstext,omitempty" validate:"required"` // Positionstext ist eine Bezeichung für die abgerechnete Position. - Zeiteinheit zeiteinheit.Zeiteinheit `json:"zeiteinheit,omitempty"` // Zeiteinheit wird angegeben, falls sich der Preis auf eine Zeit bezieht, steht hier die Einheit, z.B. JAHR - Artikelnummer *bdewartikelnummer.BDEWArtikelnummer `json:"artikelnummer,omitempty"` // Artikelnummer ist eine Kennzeichnung der Rechnungsposition mit der Standard-Artikelnummer des BDEW - LokationsId string `json:"lokationsId,omitempty" validate:"omitempty,min=11,max=11,numeric"` // LokationsId ist die MarktlokationsId zu der diese Position gehört - PositionsMenge Menge `json:"positionsMenge,omitempty" validate:"required"` // PositionsMenge ist die abgerechnete Menge mit Einheit. Z.B. 4372 kWh - ZeitbezogeneMenge *Menge `json:"zeitbezogeneMenge,omitempty"` // ZeitbezogeneMenge ist eine optionale, auf die Zeiteinheit bezogene Untermenge. Z.B. bei einem Jahrespreis, 3 Monate oder 146 Tage. Basierend darauf wird der Preis aufgeteilt - Korrekturfaktor *float32 `json:"korrekturfaktor,omitempty" validate:"omitempty,min=-1,max=-1,numeric"` // Korrekturfaktor ist ein Faktor -1 der ggf. in Mehrmengen-Rechnungen vorkommt - Einzelpreis Preis `json:"einzelpreis,omitempty" validate:"required"` // Einzelpreis ist der Preis für eine Einheit der energetischen Menge - TeilsummeNetto Betrag `json:"teilsummeNetto,omitempty" validate:"required"` // TeilsummeNetto ist das Ergebnis der Multiplikation aus einzelpreis * positionsMenge * (Faktor aus zeitbezogeneMenge). Z.B. 12,60€ * 120 kW * 3/12 (für 3 Monate). - TeilsummeSteuer Steuerbetrag `json:"teilsummeSteuer,omitempty" validate:"required"` // TeilsummeSteuer ist der auf die Position entfallende Steuer, bestehend aus Steuersatz und Betrag - TeilrabattNetto *Betrag `json:"teilrabattNetto,omitempty"` // TeilrabattNetto ist der Rabatt für diese Position - ArtikelId *string `json:"artikelId,omitempty"` // Die ArtikelId ist Artikel-ID (zu verwenden seit 2022-10-01) - Ausfuehrungsdatum time.Time `json:"ausfuehrungsdatum,omitempty"` // Das Ausfuehrungsdatum leitet sich aus den Qualifier 203 Ausführungsdatum/-zeit ab + Positionsnummer int `json:"positionsnummer,omitempty" validate:"required"` // Positionsnummer ist eine fortlaufende Nummer für die Rechnungsposition + LieferungVon time.Time `json:"lieferungVon,omitempty"` // LieferungVon ist ein _inklusiver_ Start der Lieferung für die abgerechnete LEISTUNG + LieferungBis time.Time `json:"lieferungBis,omitempty" validate:"omitempty,gtfield=LieferungVon"` // LieferungBis ist ein _exklusives_ Ende der Lieferung für die abgerechnete LEISTUNG + Positionstext string `json:"positionstext,omitempty" validate:"required"` // Positionstext ist eine Bezeichung für die abgerechnete Position. + Zeiteinheit zeiteinheit.Zeiteinheit `json:"zeiteinheit,omitempty"` // Zeiteinheit wird angegeben, falls sich der Preis auf eine Zeit bezieht, steht hier die Einheit, z.B. JAHR + Artikelnummer *bdewartikelnummer.BDEWArtikelnummer `json:"artikelnummer,omitempty"` // Artikelnummer ist eine Kennzeichnung der Rechnungsposition mit der Standard-Artikelnummer des BDEW + LokationsId string `json:"lokationsId,omitempty" validate:"omitempty,min=11,max=11,numeric"` // LokationsId ist die MarktlokationsId zu der diese Position gehört + PositionsMenge Menge `json:"positionsMenge,omitempty" validate:"required"` // PositionsMenge ist die abgerechnete Menge mit Einheit. Z.B. 4372 kWh + ZeitbezogeneMenge *Menge `json:"zeitbezogeneMenge,omitempty"` // ZeitbezogeneMenge ist eine optionale, auf die Zeiteinheit bezogene Untermenge. Z.B. bei einem Jahrespreis, 3 Monate oder 146 Tage. Basierend darauf wird der Preis aufgeteilt + Korrekturfaktor *float32 `json:"korrekturfaktor,omitempty" validate:"omitempty,min=-1,max=-1,numeric"` // Korrekturfaktor ist ein Faktor -1 der ggf. in Mehrmengen-Rechnungen vorkommt + Einzelpreis Preis `json:"einzelpreis,omitempty" validate:"required"` // Einzelpreis ist der Preis für eine Einheit der energetischen Menge + TeilsummeNetto Betrag `json:"teilsummeNetto,omitempty" validate:"required"` // TeilsummeNetto ist das Ergebnis der Multiplikation aus einzelpreis * positionsMenge * (Faktor aus zeitbezogeneMenge). Z.B. 12,60€ * 120 kW * 3/12 (für 3 Monate). + TeilsummeSteuer Steuerbetrag `json:"teilsummeSteuer,omitempty" validate:"required"` // TeilsummeSteuer ist der auf die Position entfallende Steuer, bestehend aus Steuersatz und Betrag + TeilrabattNetto *Betrag `json:"teilrabattNetto,omitempty"` // TeilrabattNetto ist der Rabatt für diese Position + ArtikelId *string `json:"artikelId,omitempty"` // Die ArtikelId ist Artikel-ID (zu verwenden seit 2022-10-01) + Ausfuehrungsdatum time.Time `json:"ausfuehrungsdatum,omitempty"` // Das Ausfuehrungsdatum leitet sich aus den Qualifier 203 Ausführungsdatum/-zeit ab + Zuschlag *rechnungspositionszuschlag.RechnungspositionsZuschlag `json:"zuschlag,omitempty"` // Art des Zuschlag auf die Position + Abschlag *rechnungspositionsabschlag.RechnungspositionsAbschlag `json:"abschlag,omitempty"` // Art des Abschlag auf die Position + Gesamtzuabschlagsbetrag *decimal.Decimal `json:"gesamtzuabschlagsbetrag,omitempty"` // Der Betrag des Zu/Abschlages } // RechnungspositionStructLevelValidation does a cross check on a Rechnungsposition object diff --git a/com/rechnungsposition_test.go b/com/rechnungsposition_test.go index 4fc63ca..31256ac 100644 --- a/com/rechnungsposition_test.go +++ b/com/rechnungsposition_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/hochfrequenz/go-bo4e/enum/rechnungspositionszuschlag" + "github.com/hochfrequenz/go-bo4e/internal" "github.com/corbym/gocrest/is" @@ -59,11 +61,14 @@ func Test_Rechnungsposition_Deserialization(t *testing.T) { Steuerwert: newDecimalFromString("7"), Waehrung: waehrungscode.EUR, }, - TeilrabattNetto: nil, + TeilrabattNetto: nil, + Zuschlag: internal.Ptr(rechnungspositionszuschlag.ANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG), + Gesamtzuabschlagsbetrag: internal.Ptr(newDecimalFromString("13.5")), } serializedRechnungsposition, err := json.Marshal(rechnungsposition) jsonString := string(serializedRechnungsposition) then.AssertThat(t, strings.Contains(jsonString, "ABGABE_KWKG"), is.True()) // stringified enum + then.AssertThat(t, strings.Contains(jsonString, "zuschlag"), is.True()) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, serializedRechnungsposition, is.Not(is.NilArray[byte]())) var deserializedRechnungsposition com.Rechnungsposition diff --git a/enum/rechnungspositionsabschlag/rechnungspositionsabschlag.go b/enum/rechnungspositionsabschlag/rechnungspositionsabschlag.go new file mode 100644 index 0000000..564f1c0 --- /dev/null +++ b/enum/rechnungspositionsabschlag/rechnungspositionsabschlag.go @@ -0,0 +1,13 @@ +package rechnungspositionsabschlag + +// Zeitreihentyp sind die Codes der Summenzeitreihentypen +// Note that this enum is not official BO4E standard (yet)! +// +//go:generate stringer --type RechnungspositionsAbschlag +//go:generate jsonenums --type RechnungspositionsAbschlag +type RechnungspositionsAbschlag int + +const ( + GEMEINDERABATT RechnungspositionsAbschlag = iota + 1 // Gemeinderabatt nach Konzessionsabgabenverordnung + ABSCHLAG_ANPASSUNG // Anpassung nach § 19, Absatz 2 Stromnetzentgeltverordnung +) diff --git a/enum/rechnungspositionsabschlag/rechnungspositionsabschlag_jsonenums.go b/enum/rechnungspositionsabschlag/rechnungspositionsabschlag_jsonenums.go new file mode 100644 index 0000000..3edc18f --- /dev/null +++ b/enum/rechnungspositionsabschlag/rechnungspositionsabschlag_jsonenums.go @@ -0,0 +1,56 @@ +// Code generated by jsonenums --type RechnungspositionsAbschlag; DO NOT EDIT. + +package rechnungspositionsabschlag + +import ( + "encoding/json" + "fmt" +) + +var ( + _RechnungspositionsAbschlagNameToValue = map[string]RechnungspositionsAbschlag{ + "GEMEINDERABATT": GEMEINDERABATT, + "ABSCHLAG_ANPASSUNG": ABSCHLAG_ANPASSUNG, + } + + _RechnungspositionsAbschlagValueToName = map[RechnungspositionsAbschlag]string{ + GEMEINDERABATT: "GEMEINDERABATT", + ABSCHLAG_ANPASSUNG: "ABSCHLAG_ANPASSUNG", + } +) + +func init() { + var v RechnungspositionsAbschlag + if _, ok := interface{}(v).(fmt.Stringer); ok { + _RechnungspositionsAbschlagNameToValue = map[string]RechnungspositionsAbschlag{ + interface{}(GEMEINDERABATT).(fmt.Stringer).String(): GEMEINDERABATT, + interface{}(ABSCHLAG_ANPASSUNG).(fmt.Stringer).String(): ABSCHLAG_ANPASSUNG, + } + } +} + +// MarshalJSON is generated so RechnungspositionsAbschlag satisfies json.Marshaler. +func (r RechnungspositionsAbschlag) MarshalJSON() ([]byte, error) { + if s, ok := interface{}(r).(fmt.Stringer); ok { + return json.Marshal(s.String()) + } + s, ok := _RechnungspositionsAbschlagValueToName[r] + if !ok { + return nil, fmt.Errorf("invalid RechnungspositionsAbschlag: %d", r) + } + return json.Marshal(s) +} + +// UnmarshalJSON is generated so RechnungspositionsAbschlag satisfies json.Unmarshaler. +func (r *RechnungspositionsAbschlag) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("RechnungspositionsAbschlag should be a string, got %s", data) + } + v, ok := _RechnungspositionsAbschlagNameToValue[s] + if !ok { + return fmt.Errorf("invalid RechnungspositionsAbschlag %q", s) + } + *r = v + return nil +} diff --git a/enum/rechnungspositionsabschlag/rechnungspositionsabschlag_scanner_valuer.go b/enum/rechnungspositionsabschlag/rechnungspositionsabschlag_scanner_valuer.go new file mode 100644 index 0000000..a4e8468 --- /dev/null +++ b/enum/rechnungspositionsabschlag/rechnungspositionsabschlag_scanner_valuer.go @@ -0,0 +1,32 @@ +// Code auto-generated; DO NOT EDIT. +package rechnungspositionsabschlag + +import ( + "database/sql/driver" + "fmt" + + "github.com/hochfrequenz/go-bo4e/internal/typemapper" +) + +// Value returns the string representation of r or an error, if no string representation exists. +// It implements the sql.Valuer interface to be useable by sql drivers when storing enums. +func (r RechnungspositionsAbschlag) Value() (driver.Value, error) { + s, ok := _RechnungspositionsAbschlagValueToName[r] + if ok { + return s, nil + } + return nil, fmt.Errorf("could not stringify %s", r) +} + +// Scan sets r to the enum value represented by src. +// It implements the sql.Scanner interface to be useable by sql drivers when reading from database. +func (r *RechnungspositionsAbschlag) Scan(src interface{}) error { + f := typemapper.TypeFromValue[RechnungspositionsAbschlag] + v, err := f(src, _RechnungspositionsAbschlagNameToValue) + if err != nil { + return err + } + + *r = v + return nil +} diff --git a/enum/rechnungspositionsabschlag/rechnungspositionsabschlag_string.go b/enum/rechnungspositionsabschlag/rechnungspositionsabschlag_string.go new file mode 100644 index 0000000..55ff162 --- /dev/null +++ b/enum/rechnungspositionsabschlag/rechnungspositionsabschlag_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer --type RechnungspositionsAbschlag"; DO NOT EDIT. + +package rechnungspositionsabschlag + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[GEMEINDERABATT-1] + _ = x[ABSCHLAG_ANPASSUNG-2] +} + +const _RechnungspositionsAbschlag_name = "GEMEINDERABATTABSCHLAG_ANPASSUNG" + +var _RechnungspositionsAbschlag_index = [...]uint8{0, 14, 32} + +func (i RechnungspositionsAbschlag) String() string { + i -= 1 + if i < 0 || i >= RechnungspositionsAbschlag(len(_RechnungspositionsAbschlag_index)-1) { + return "RechnungspositionsAbschlag(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _RechnungspositionsAbschlag_name[_RechnungspositionsAbschlag_index[i]:_RechnungspositionsAbschlag_index[i+1]] +} diff --git a/enum/rechnungspositionszuschlag/rechnungspositionszuschlag.go b/enum/rechnungspositionszuschlag/rechnungspositionszuschlag.go new file mode 100644 index 0000000..41ce900 --- /dev/null +++ b/enum/rechnungspositionszuschlag/rechnungspositionszuschlag.go @@ -0,0 +1,15 @@ +package rechnungspositionszuschlag + +// Zeitreihentyp sind die Codes der Summenzeitreihentypen +// Note that this enum is not official BO4E standard (yet)! +// +//go:generate stringer --type RechnungspositionsZuschlag +//go:generate jsonenums --type RechnungspositionsZuschlag +type RechnungspositionsZuschlag int + +const ( + UMSPANNUNGSZUSCHLAG RechnungspositionsZuschlag = iota + 1 // + ALLEIN_GENUTZTE_BETRIEBSMITTEL + ZUSCHLAG_ANPASSUNG + ANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG +) diff --git a/enum/rechnungspositionszuschlag/rechnungspositionszuschlag_jsonenums.go b/enum/rechnungspositionszuschlag/rechnungspositionszuschlag_jsonenums.go new file mode 100644 index 0000000..175d8c1 --- /dev/null +++ b/enum/rechnungspositionszuschlag/rechnungspositionszuschlag_jsonenums.go @@ -0,0 +1,62 @@ +// Code generated by jsonenums --type RechnungspositionsZuschlag; DO NOT EDIT. + +package rechnungspositionszuschlag + +import ( + "encoding/json" + "fmt" +) + +var ( + _RechnungspositionsZuschlagNameToValue = map[string]RechnungspositionsZuschlag{ + "UMSPANNUNGSZUSCHLAG": UMSPANNUNGSZUSCHLAG, + "ALLEIN_GENUTZTE_BETRIEBSMITTEL": ALLEIN_GENUTZTE_BETRIEBSMITTEL, + "ZUSCHLAG_ANPASSUNG": ZUSCHLAG_ANPASSUNG, + "ANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG": ANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG, + } + + _RechnungspositionsZuschlagValueToName = map[RechnungspositionsZuschlag]string{ + UMSPANNUNGSZUSCHLAG: "UMSPANNUNGSZUSCHLAG", + ALLEIN_GENUTZTE_BETRIEBSMITTEL: "ALLEIN_GENUTZTE_BETRIEBSMITTEL", + ZUSCHLAG_ANPASSUNG: "ZUSCHLAG_ANPASSUNG", + ANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG: "ANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG", + } +) + +func init() { + var v RechnungspositionsZuschlag + if _, ok := interface{}(v).(fmt.Stringer); ok { + _RechnungspositionsZuschlagNameToValue = map[string]RechnungspositionsZuschlag{ + interface{}(UMSPANNUNGSZUSCHLAG).(fmt.Stringer).String(): UMSPANNUNGSZUSCHLAG, + interface{}(ALLEIN_GENUTZTE_BETRIEBSMITTEL).(fmt.Stringer).String(): ALLEIN_GENUTZTE_BETRIEBSMITTEL, + interface{}(ZUSCHLAG_ANPASSUNG).(fmt.Stringer).String(): ZUSCHLAG_ANPASSUNG, + interface{}(ANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG).(fmt.Stringer).String(): ANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG, + } + } +} + +// MarshalJSON is generated so RechnungspositionsZuschlag satisfies json.Marshaler. +func (r RechnungspositionsZuschlag) MarshalJSON() ([]byte, error) { + if s, ok := interface{}(r).(fmt.Stringer); ok { + return json.Marshal(s.String()) + } + s, ok := _RechnungspositionsZuschlagValueToName[r] + if !ok { + return nil, fmt.Errorf("invalid RechnungspositionsZuschlag: %d", r) + } + return json.Marshal(s) +} + +// UnmarshalJSON is generated so RechnungspositionsZuschlag satisfies json.Unmarshaler. +func (r *RechnungspositionsZuschlag) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("RechnungspositionsZuschlag should be a string, got %s", data) + } + v, ok := _RechnungspositionsZuschlagNameToValue[s] + if !ok { + return fmt.Errorf("invalid RechnungspositionsZuschlag %q", s) + } + *r = v + return nil +} diff --git a/enum/rechnungspositionszuschlag/rechnungspositionszuschlag_scanner_valuer.go b/enum/rechnungspositionszuschlag/rechnungspositionszuschlag_scanner_valuer.go new file mode 100644 index 0000000..f890033 --- /dev/null +++ b/enum/rechnungspositionszuschlag/rechnungspositionszuschlag_scanner_valuer.go @@ -0,0 +1,32 @@ +// Code auto-generated; DO NOT EDIT. +package rechnungspositionszuschlag + +import ( + "database/sql/driver" + "fmt" + + "github.com/hochfrequenz/go-bo4e/internal/typemapper" +) + +// Value returns the string representation of r or an error, if no string representation exists. +// It implements the sql.Valuer interface to be useable by sql drivers when storing enums. +func (r RechnungspositionsZuschlag) Value() (driver.Value, error) { + s, ok := _RechnungspositionsZuschlagValueToName[r] + if ok { + return s, nil + } + return nil, fmt.Errorf("could not stringify %s", r) +} + +// Scan sets r to the enum value represented by src. +// It implements the sql.Scanner interface to be useable by sql drivers when reading from database. +func (r *RechnungspositionsZuschlag) Scan(src interface{}) error { + f := typemapper.TypeFromValue[RechnungspositionsZuschlag] + v, err := f(src, _RechnungspositionsZuschlagNameToValue) + if err != nil { + return err + } + + *r = v + return nil +} diff --git a/enum/rechnungspositionszuschlag/rechnungspositionszuschlag_string.go b/enum/rechnungspositionszuschlag/rechnungspositionszuschlag_string.go new file mode 100644 index 0000000..727f198 --- /dev/null +++ b/enum/rechnungspositionszuschlag/rechnungspositionszuschlag_string.go @@ -0,0 +1,27 @@ +// Code generated by "stringer --type RechnungspositionsZuschlag"; DO NOT EDIT. + +package rechnungspositionszuschlag + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[UMSPANNUNGSZUSCHLAG-1] + _ = x[ALLEIN_GENUTZTE_BETRIEBSMITTEL-2] + _ = x[ZUSCHLAG_ANPASSUNG-3] + _ = x[ANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG-4] +} + +const _RechnungspositionsZuschlag_name = "UMSPANNUNGSZUSCHLAGALLEIN_GENUTZTE_BETRIEBSMITTELZUSCHLAG_ANPASSUNGANPASSUNG_PAUSCHALE_NETZENTGELTREDUZIERUNG" + +var _RechnungspositionsZuschlag_index = [...]uint8{0, 19, 49, 67, 109} + +func (i RechnungspositionsZuschlag) String() string { + i -= 1 + if i < 0 || i >= RechnungspositionsZuschlag(len(_RechnungspositionsZuschlag_index)-1) { + return "RechnungspositionsZuschlag(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _RechnungspositionsZuschlag_name[_RechnungspositionsZuschlag_index[i]:_RechnungspositionsZuschlag_index[i+1]] +}