use rust_decimal::Decimal;
use rust_decimal_macros::dec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum SolarBauform {
Gebaeude,
Laermschutzwand,
Freiflaeche,
AgriPv,
Floating,
Parkplatz,
SteckerPv,
}
impl SolarBauform {
#[must_use]
pub fn volleinspeisung_bonus_eligible(self) -> bool {
matches!(
self,
Self::Gebaeude
| Self::Laermschutzwand
| Self::AgriPv
| Self::Floating
| Self::Parkplatz
)
}
#[must_use]
pub fn has_freiflaechen_restriction(self) -> bool {
self == Self::Freiflaeche
}
#[must_use]
pub fn auction_threshold_kwp(self) -> Decimal {
match self {
Self::SteckerPv => dec!(2), Self::AgriPv | Self::Floating | Self::Parkplatz => dec!(6000), _ => dec!(1000), }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum EinspeisungsModus {
Volleinspeisung,
#[default]
Ueberschusseinspeisung,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SolarAnlageData {
pub bauform: SolarBauform,
pub einspeisungs_modus: EinspeisungsModus,
pub ustg_12_3_zero_vat: bool,
pub mastr_registriert: bool,
pub agripv_zertifiziert: bool,
}
impl SolarAnlageData {
#[must_use]
pub fn volleinspeisung_sanktionspflichtig(&self) -> bool {
self.einspeisungs_modus == EinspeisungsModus::Volleinspeisung
}
}
#[must_use]
pub fn requires_ausschreibung(leistung_kwp: Decimal, bauform: SolarBauform) -> bool {
leistung_kwp > bauform.auction_threshold_kwp()
}
#[must_use]
pub fn ustg_12_3_applies(
leistung_kwp: Decimal,
on_residential_or_public_building: bool,
inbetriebnahme: time::Date,
) -> bool {
use time::macros::date;
inbetriebnahme >= date!(2023 - 01 - 01)
&& on_residential_or_public_building
&& leistung_kwp <= dec!(30)
}
pub const SECT51A_SOLAR_FACTOR_DENOMINATOR: u64 = 2;
#[cfg(test)]
mod tests {
use super::*;
use time::macros::date;
#[test]
fn gebaeude_qualifies_for_volleinspeisung_bonus() {
assert!(SolarBauform::Gebaeude.volleinspeisung_bonus_eligible());
assert!(SolarBauform::AgriPv.volleinspeisung_bonus_eligible());
assert!(!SolarBauform::Freiflaeche.volleinspeisung_bonus_eligible());
}
#[test]
fn freiflaeche_has_location_restriction() {
assert!(SolarBauform::Freiflaeche.has_freiflaechen_restriction());
assert!(!SolarBauform::Gebaeude.has_freiflaechen_restriction());
}
#[test]
fn agripv_has_higher_auction_threshold() {
assert_eq!(SolarBauform::Gebaeude.auction_threshold_kwp(), dec!(1000));
assert_eq!(SolarBauform::AgriPv.auction_threshold_kwp(), dec!(6000));
}
#[test]
fn auction_required_above_1mwp_gebaeude() {
assert!(!requires_ausschreibung(dec!(1000), SolarBauform::Gebaeude)); assert!(requires_ausschreibung(dec!(1001), SolarBauform::Gebaeude)); }
#[test]
fn no_auction_for_agripv_below_6mwp() {
assert!(!requires_ausschreibung(dec!(5999), SolarBauform::AgriPv));
assert!(requires_ausschreibung(dec!(6001), SolarBauform::AgriPv));
}
#[test]
fn stecker_pv_never_requires_auction() {
assert!(!requires_ausschreibung(dec!(2), SolarBauform::SteckerPv));
}
#[test]
fn zero_vat_applies_post_2023_residential_leq30kwp() {
assert!(ustg_12_3_applies(dec!(10), true, date!(2024 - 03 - 15)));
assert!(ustg_12_3_applies(dec!(30), true, date!(2023 - 01 - 01))); }
#[test]
fn zero_vat_does_not_apply_pre_2023() {
assert!(!ustg_12_3_applies(dec!(10), true, date!(2022 - 12 - 31)));
}
#[test]
fn zero_vat_does_not_apply_above_30kwp() {
assert!(!ustg_12_3_applies(dec!(31), true, date!(2024 - 01 - 01)));
}
#[test]
fn zero_vat_does_not_apply_non_residential() {
assert!(!ustg_12_3_applies(dec!(10), false, date!(2024 - 01 - 01)));
}
#[test]
fn volleinspeisung_triggers_sanktionspflicht() {
let anlage = SolarAnlageData {
bauform: SolarBauform::Gebaeude,
einspeisungs_modus: EinspeisungsModus::Volleinspeisung,
ustg_12_3_zero_vat: true,
mastr_registriert: true,
agripv_zertifiziert: false,
};
assert!(anlage.volleinspeisung_sanktionspflichtig());
}
#[test]
fn ueberschuss_no_sanktionspflicht() {
let anlage = SolarAnlageData {
bauform: SolarBauform::Gebaeude,
einspeisungs_modus: EinspeisungsModus::Ueberschusseinspeisung,
ustg_12_3_zero_vat: false,
mastr_registriert: true,
agripv_zertifiziert: false,
};
assert!(!anlage.volleinspeisung_sanktionspflichtig());
}
#[test]
fn sect51a_solar_uses_half_factor() {
assert_eq!(
crate::foerderdauer::verguetungszeitraum_verlaengerung_qh(200, true),
100 );
assert_eq!(
crate::foerderdauer::verguetungszeitraum_verlaengerung_qh(201, true),
101 );
}
#[test]
fn sect51a_wind_uses_full_factor() {
assert_eq!(
crate::foerderdauer::verguetungszeitraum_verlaengerung_qh(200, false),
200 );
}
}