44from homeassistant import config_entries
55from homeassistant .data_entry_flow import FlowResult
66
7- from .const import CONF_NET_METERING , CONF_SELECTED_RATE , DOMAIN
7+ from .const import (
8+ CONF_INCLUDE_PSCR ,
9+ CONF_NET_METERING ,
10+ CONF_SELECTED_RATE ,
11+ CONF_TAX_RATE ,
12+ DEFAULT_INCLUDE_PSCR ,
13+ DEFAULT_TAX_RATE_PERCENT ,
14+ DOMAIN ,
15+ )
816from .coordinator import DteRateCoordinator
17+ from .settings import normalize_tax_rate_percent
918
1019
1120class DteRatesConfigFlow (config_entries .ConfigFlow , domain = DOMAIN ):
1221 VERSION = 1
1322
23+ @staticmethod
24+ def async_get_options_flow (config_entry ) -> config_entries .OptionsFlow :
25+ return DteRatesOptionsFlow (config_entry )
26+
1427 async def async_step_user (self , user_input : dict | None = None ) -> FlowResult :
1528 coordinator = DteRateCoordinator (self .hass )
1629 await coordinator .async_refresh ()
@@ -22,31 +35,105 @@ async def async_step_user(self, user_input: dict | None = None) -> FlowResult:
2235 for code , rate in coordinator .data .rates .items ()
2336 }
2437
38+ errors : dict [str , str ] = {}
2539 if user_input is not None :
26- selected_rate = user_input [ CONF_SELECTED_RATE ]
27- return self . async_create_entry (
28- title = rate_options [ selected_rate ],
29- data = user_input ,
30- )
31-
32- schema = vol . Schema (
33- {
34- vol . Required ( CONF_SELECTED_RATE ): vol . In ( rate_options ) ,
35- vol . Optional ( CONF_NET_METERING , default = False ): bool ,
36- }
37- )
40+ try :
41+ data = _normalize_config_input ( user_input )
42+ except ValueError :
43+ errors [ "base" ] = "invalid_tax_rate"
44+ else :
45+ selected_rate = data [ CONF_SELECTED_RATE ]
46+ return self . async_create_entry (
47+ title = rate_options [ selected_rate ],
48+ data = data ,
49+ )
50+
51+ schema = _user_schema ( rate_options )
3852 return self .async_show_form (
3953 step_id = "user" ,
4054 data_schema = schema ,
55+ errors = errors ,
4156 description_placeholders = {
4257 "rider18_status" : _rider18_status (coordinator .data ),
58+ "tax_note" : _tax_note (),
4359 },
4460 )
4561
4662
63+ class DteRatesOptionsFlow (config_entries .OptionsFlow ):
64+ def __init__ (self , config_entry ) -> None :
65+ self .config_entry = config_entry
66+
67+ async def async_step_init (self , user_input : dict | None = None ) -> FlowResult :
68+ errors : dict [str , str ] = {}
69+ if user_input is not None :
70+ try :
71+ data = _normalize_options_input (user_input )
72+ except ValueError :
73+ errors ["base" ] = "invalid_tax_rate"
74+ else :
75+ return self .async_create_entry (title = "" , data = data )
76+
77+ return self .async_show_form (
78+ step_id = "init" ,
79+ data_schema = _options_schema (self .config_entry ),
80+ errors = errors ,
81+ description_placeholders = {"tax_note" : _tax_note ()},
82+ )
83+
84+
85+ def _normalize_config_input (user_input : dict ) -> dict :
86+ data = dict (user_input )
87+ data .setdefault (CONF_INCLUDE_PSCR , DEFAULT_INCLUDE_PSCR )
88+ data .setdefault (CONF_TAX_RATE , DEFAULT_TAX_RATE_PERCENT )
89+ data [CONF_INCLUDE_PSCR ] = bool (data [CONF_INCLUDE_PSCR ])
90+ data [CONF_TAX_RATE ] = normalize_tax_rate_percent (data [CONF_TAX_RATE ])
91+ return data
92+
93+
94+ def _normalize_options_input (user_input : dict ) -> dict :
95+ data = {
96+ CONF_INCLUDE_PSCR : bool (user_input .get (CONF_INCLUDE_PSCR , DEFAULT_INCLUDE_PSCR )),
97+ CONF_TAX_RATE : normalize_tax_rate_percent (user_input .get (CONF_TAX_RATE , DEFAULT_TAX_RATE_PERCENT )),
98+ }
99+ return data
100+
101+
102+ def _user_schema (rate_options : dict [str , str ]) -> vol .Schema :
103+ return vol .Schema (
104+ {
105+ vol .Required (CONF_SELECTED_RATE ): vol .In (rate_options ),
106+ vol .Optional (CONF_NET_METERING , default = False ): bool ,
107+ vol .Optional (CONF_INCLUDE_PSCR , default = DEFAULT_INCLUDE_PSCR ): bool ,
108+ vol .Optional (CONF_TAX_RATE , default = DEFAULT_TAX_RATE_PERCENT ): str ,
109+ }
110+ )
111+
112+
113+ def _options_schema (config_entry ) -> vol .Schema :
114+ options = getattr (config_entry , "options" , None ) or {}
115+ data = getattr (config_entry , "data" , None ) or {}
116+ include_pscr = options .get (CONF_INCLUDE_PSCR , data .get (CONF_INCLUDE_PSCR , DEFAULT_INCLUDE_PSCR ))
117+ tax_rate = options .get (CONF_TAX_RATE , data .get (CONF_TAX_RATE , DEFAULT_TAX_RATE_PERCENT ))
118+ return vol .Schema (
119+ {
120+ vol .Optional (CONF_INCLUDE_PSCR , default = include_pscr ): bool ,
121+ vol .Optional (CONF_TAX_RATE , default = tax_rate ): str ,
122+ }
123+ )
124+
125+
47126def _rider18_status (rate_card ) -> str :
48127 count = len (getattr (rate_card , "pscr_rates" , {}))
49128 if count :
50129 suffix = "tariff" if count == 1 else "tariffs"
51130 return f"Rider 18 export credits use parsed generation rates plus MPSC PSCR factors loaded for { count } { suffix } ."
52131 return "Rider 18 export credits use parsed generation rates plus MPSC PSCR when the selected tariff has a PSCR factor."
132+
133+
134+ def _tax_note () -> str :
135+ return (
136+ "Default tax is Michigan's 4.0% residential electric sales tax. "
137+ "Detroit residents may incur an additional 5.0% City Utility Users' Tax. "
138+ "Set tax rate to 0 to omit taxes from rate calculations."
139+ )
0 commit comments