300 lines
9.8 KiB
Python
300 lines
9.8 KiB
Python
"""Adds config flow for Workday integration."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from holidays import country_holidays, list_supported_countries
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.config_entries import (
|
|
ConfigEntry,
|
|
ConfigFlow,
|
|
OptionsFlowWithConfigEntry,
|
|
)
|
|
from homeassistant.const import CONF_NAME
|
|
from homeassistant.core import callback
|
|
from homeassistant.data_entry_flow import AbortFlow, FlowResult
|
|
from homeassistant.exceptions import HomeAssistantError
|
|
from homeassistant.helpers.selector import (
|
|
NumberSelector,
|
|
NumberSelectorConfig,
|
|
NumberSelectorMode,
|
|
SelectSelector,
|
|
SelectSelectorConfig,
|
|
SelectSelectorMode,
|
|
TextSelector,
|
|
)
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
from .const import (
|
|
ALLOWED_DAYS,
|
|
CONF_ADD_HOLIDAYS,
|
|
CONF_COUNTRY,
|
|
CONF_EXCLUDES,
|
|
CONF_OFFSET,
|
|
CONF_PROVINCE,
|
|
CONF_REMOVE_HOLIDAYS,
|
|
CONF_WORKDAYS,
|
|
DEFAULT_EXCLUDES,
|
|
DEFAULT_NAME,
|
|
DEFAULT_OFFSET,
|
|
DEFAULT_WORKDAYS,
|
|
DOMAIN,
|
|
LOGGER,
|
|
)
|
|
|
|
NONE_SENTINEL = "none"
|
|
|
|
|
|
def add_province_to_schema(
|
|
schema: vol.Schema,
|
|
country: str,
|
|
) -> vol.Schema:
|
|
"""Update schema with province from country."""
|
|
all_countries = list_supported_countries()
|
|
if not all_countries[country]:
|
|
return schema
|
|
|
|
province_list = [NONE_SENTINEL, *all_countries[country]]
|
|
add_schema = {
|
|
vol.Optional(CONF_PROVINCE, default=NONE_SENTINEL): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=province_list,
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
translation_key=CONF_PROVINCE,
|
|
)
|
|
),
|
|
}
|
|
|
|
return vol.Schema({**DATA_SCHEMA_OPT.schema, **add_schema})
|
|
|
|
|
|
def validate_custom_dates(user_input: dict[str, Any]) -> None:
|
|
"""Validate custom dates for add/remove holidays."""
|
|
|
|
for add_date in user_input[CONF_ADD_HOLIDAYS]:
|
|
if dt_util.parse_date(add_date) is None:
|
|
raise AddDatesError("Incorrect date")
|
|
|
|
year: int = dt_util.now().year
|
|
obj_holidays = country_holidays(
|
|
user_input[CONF_COUNTRY], user_input.get(CONF_PROVINCE), year
|
|
)
|
|
|
|
for remove_date in user_input[CONF_REMOVE_HOLIDAYS]:
|
|
if dt_util.parse_date(remove_date) is None:
|
|
if obj_holidays.get_named(remove_date) == []:
|
|
raise RemoveDatesError("Incorrect date or name")
|
|
|
|
|
|
DATA_SCHEMA_SETUP = vol.Schema(
|
|
{
|
|
vol.Required(CONF_NAME, default=DEFAULT_NAME): TextSelector(),
|
|
vol.Required(CONF_COUNTRY): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=list(list_supported_countries()),
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
)
|
|
),
|
|
}
|
|
)
|
|
|
|
DATA_SCHEMA_OPT = vol.Schema(
|
|
{
|
|
vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=ALLOWED_DAYS,
|
|
multiple=True,
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
translation_key="days",
|
|
)
|
|
),
|
|
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): NumberSelector(
|
|
NumberSelectorConfig(min=-10, max=10, step=1, mode=NumberSelectorMode.BOX)
|
|
),
|
|
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=ALLOWED_DAYS,
|
|
multiple=True,
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
translation_key="days",
|
|
)
|
|
),
|
|
vol.Optional(CONF_ADD_HOLIDAYS, default=[]): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=[],
|
|
multiple=True,
|
|
custom_value=True,
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
)
|
|
),
|
|
vol.Optional(CONF_REMOVE_HOLIDAYS, default=[]): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=[],
|
|
multiple=True,
|
|
custom_value=True,
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
)
|
|
),
|
|
}
|
|
)
|
|
|
|
|
|
class WorkdayConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
"""Handle a config flow for Workday integration."""
|
|
|
|
VERSION = 1
|
|
|
|
data: dict[str, Any] = {}
|
|
|
|
@staticmethod
|
|
@callback
|
|
def async_get_options_flow(
|
|
config_entry: ConfigEntry,
|
|
) -> WorkdayOptionsFlowHandler:
|
|
"""Get the options flow for this handler."""
|
|
return WorkdayOptionsFlowHandler(config_entry)
|
|
|
|
async def async_step_user(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> FlowResult:
|
|
"""Handle the user initial step."""
|
|
errors: dict[str, str] = {}
|
|
|
|
if user_input is not None:
|
|
self.data = user_input
|
|
return await self.async_step_options()
|
|
return self.async_show_form(
|
|
step_id="user",
|
|
data_schema=DATA_SCHEMA_SETUP,
|
|
errors=errors,
|
|
)
|
|
|
|
async def async_step_options(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> FlowResult:
|
|
"""Handle remaining flow."""
|
|
errors: dict[str, str] = {}
|
|
if user_input is not None:
|
|
combined_input: dict[str, Any] = {**self.data, **user_input}
|
|
if combined_input.get(CONF_PROVINCE, NONE_SENTINEL) == NONE_SENTINEL:
|
|
combined_input[CONF_PROVINCE] = None
|
|
|
|
try:
|
|
await self.hass.async_add_executor_job(
|
|
validate_custom_dates, combined_input
|
|
)
|
|
except AddDatesError:
|
|
errors["add_holidays"] = "add_holiday_error"
|
|
except RemoveDatesError:
|
|
errors["remove_holidays"] = "remove_holiday_error"
|
|
except NotImplementedError:
|
|
self.async_abort(reason="incorrect_province")
|
|
|
|
abort_match = {
|
|
CONF_COUNTRY: combined_input[CONF_COUNTRY],
|
|
CONF_EXCLUDES: combined_input[CONF_EXCLUDES],
|
|
CONF_OFFSET: combined_input[CONF_OFFSET],
|
|
CONF_WORKDAYS: combined_input[CONF_WORKDAYS],
|
|
CONF_ADD_HOLIDAYS: combined_input[CONF_ADD_HOLIDAYS],
|
|
CONF_REMOVE_HOLIDAYS: combined_input[CONF_REMOVE_HOLIDAYS],
|
|
CONF_PROVINCE: combined_input[CONF_PROVINCE],
|
|
}
|
|
LOGGER.debug("abort_check in options with %s", combined_input)
|
|
self._async_abort_entries_match(abort_match)
|
|
|
|
LOGGER.debug("Errors have occurred %s", errors)
|
|
if not errors:
|
|
LOGGER.debug("No duplicate, no errors, creating entry")
|
|
return self.async_create_entry(
|
|
title=combined_input[CONF_NAME],
|
|
data={},
|
|
options=combined_input,
|
|
)
|
|
|
|
schema = await self.hass.async_add_executor_job(
|
|
add_province_to_schema, DATA_SCHEMA_OPT, self.data[CONF_COUNTRY]
|
|
)
|
|
new_schema = self.add_suggested_values_to_schema(schema, user_input)
|
|
return self.async_show_form(
|
|
step_id="options",
|
|
data_schema=new_schema,
|
|
errors=errors,
|
|
description_placeholders={
|
|
"name": self.data[CONF_NAME],
|
|
"country": self.data[CONF_COUNTRY],
|
|
},
|
|
)
|
|
|
|
|
|
class WorkdayOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
|
"""Handle Workday options."""
|
|
|
|
async def async_step_init(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> FlowResult:
|
|
"""Manage Workday options."""
|
|
errors: dict[str, str] = {}
|
|
|
|
if user_input is not None:
|
|
combined_input: dict[str, Any] = {**self.options, **user_input}
|
|
if combined_input.get(CONF_PROVINCE, NONE_SENTINEL) == NONE_SENTINEL:
|
|
combined_input[CONF_PROVINCE] = None
|
|
|
|
try:
|
|
await self.hass.async_add_executor_job(
|
|
validate_custom_dates, combined_input
|
|
)
|
|
except AddDatesError:
|
|
errors["add_holidays"] = "add_holiday_error"
|
|
except RemoveDatesError:
|
|
errors["remove_holidays"] = "remove_holiday_error"
|
|
else:
|
|
LOGGER.debug("abort_check in options with %s", combined_input)
|
|
try:
|
|
self._async_abort_entries_match(
|
|
{
|
|
CONF_COUNTRY: self._config_entry.options[CONF_COUNTRY],
|
|
CONF_EXCLUDES: combined_input[CONF_EXCLUDES],
|
|
CONF_OFFSET: combined_input[CONF_OFFSET],
|
|
CONF_WORKDAYS: combined_input[CONF_WORKDAYS],
|
|
CONF_ADD_HOLIDAYS: combined_input[CONF_ADD_HOLIDAYS],
|
|
CONF_REMOVE_HOLIDAYS: combined_input[CONF_REMOVE_HOLIDAYS],
|
|
CONF_PROVINCE: combined_input[CONF_PROVINCE],
|
|
}
|
|
)
|
|
except AbortFlow as err:
|
|
errors = {"base": err.reason}
|
|
else:
|
|
return self.async_create_entry(data=combined_input)
|
|
|
|
schema: vol.Schema = await self.hass.async_add_executor_job(
|
|
add_province_to_schema, DATA_SCHEMA_OPT, self.options[CONF_COUNTRY]
|
|
)
|
|
|
|
new_schema = self.add_suggested_values_to_schema(
|
|
schema, user_input or self.options
|
|
)
|
|
LOGGER.debug("Errors have occurred in options %s", errors)
|
|
return self.async_show_form(
|
|
step_id="init",
|
|
data_schema=new_schema,
|
|
errors=errors,
|
|
description_placeholders={
|
|
"name": self.options[CONF_NAME],
|
|
"country": self.options[CONF_COUNTRY],
|
|
},
|
|
)
|
|
|
|
|
|
class AddDatesError(HomeAssistantError):
|
|
"""Exception for error adding dates."""
|
|
|
|
|
|
class RemoveDatesError(HomeAssistantError):
|
|
"""Exception for error removing dates."""
|
|
|
|
|
|
class CountryNotExist(HomeAssistantError):
|
|
"""Exception country does not exist error."""
|