Skip to content

Commit e9e3869

Browse files
authoredMar 3, 2022
merge: create release 0.5.2-beta (#43)
feat: create release 0.5.2-beta
2 parents 00a9568 + 351bcc8 commit e9e3869

File tree

9 files changed

+152
-27
lines changed

9 files changed

+152
-27
lines changed
 

‎README.md

+32-2
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,35 @@
88

99
[Dijnet](https://www.dijnet.hu/) integration for [Home Assistant](https://www.home-assistant.io/)
1010

11-
## Usage:
12-
Install integration from HACS and add integration from UI.
11+
## Installation
12+
13+
You can install this integration via [HACS](#hacs) or [manually](#manual).
14+
15+
### HACS
16+
17+
This integration is included in HACS. Search for the `Dijnet` integration and choose install. Reboot Home Assistant and configure the 'Dijnet' integration via the integrations page or press the blue button below.
18+
19+
[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=dijnet)
20+
21+
### Manual
22+
23+
Copy the `custom_components/dijnet` to your `custom_components` folder. Reboot Home Assistant and configure the 'Dijnet' integration via the integrations page or press the blue button below.
24+
25+
[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=dijnet)
26+
27+
## Features
28+
29+
- The integration provides services for every invoice issuer. Every invoice issuer could have multiple providers. For example DBH Zrt. invoice issuer handles invoices for FV Zrt. and FCSM Zrt. In that case the integration creates separate sensors for these providers.
30+
- For all providers an invoice amount sensor is created. It contains the sum of unpaid amount for a provider. The details of the unpaid invoices can be read out from `unpaid_invoices` attribute of the sensor.
31+
<!-- - For all providers a calendar entity is created. These entities are disabled by default. You can enable them by selecting 'Enable entity' toggle. The calendar entity registers an event for every incoming invoice. The event start date is the issuance date of the invoice. The event end date is the deadline of the invoice. If the invoices is paid before deadline, the end date of the event became the payment date. If the invoice is not paid until deadline the event end date will be today. -->
32+
33+
## Enable debug logging
34+
35+
The [logger](https://www.home-assistant.io/integrations/logger/) integration lets you define the level of logging activities in Home Assistant. Turning on debug mode will show more information about the running of the integration in the homeassistant.log file.
36+
37+
```yaml
38+
logger:
39+
default: error
40+
logs:
41+
custom_components.dijnet: debug
42+
```

‎custom_components/dijnet/__init__.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
'''Dijnet component.'''
22

3+
import logging
4+
35
from homeassistant.config_entries import ConfigEntry
46
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
57
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
68

7-
from .const import CONF_DOWNLOAD_DIR, DATA_CONTROLLER, DOMAIN
8-
from .controller import is_controller_exists, set_controller, DijnetController
9+
from .const import (CONF_DOWNLOAD_DIR,
10+
CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE,
11+
DATA_CONTROLLER, DOMAIN)
12+
from .controller import DijnetController, is_controller_exists, set_controller
913

14+
_LOGGER = logging.getLogger(__name__)
1015

1116
# pylint: disable=unused-argument
17+
18+
1219
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
1320
'''
1421
Set up the Dijnet component.
@@ -53,7 +60,8 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry)
5360
DijnetController(
5461
config_entry.data[CONF_USERNAME],
5562
config_entry.data[CONF_PASSWORD],
56-
config_entry.data[CONF_DOWNLOAD_DIR]
63+
config_entry.data[CONF_DOWNLOAD_DIR],
64+
config_entry.data[CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE]
5765
)
5866
)
5967

@@ -62,3 +70,19 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry)
6270
)
6371

6472
return True
73+
74+
75+
async def async_migrate_entry(hass: HomeAssistantType, config_entry: ConfigEntry):
76+
"""Migrate old entry."""
77+
_LOGGER.debug("Migrating from version %s", config_entry.version)
78+
79+
if config_entry.version == 1:
80+
new_config_entry = {**config_entry.data}
81+
new_config_entry[CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE] = False
82+
83+
config_entry.version = 2
84+
hass.config_entries.async_update_entry(config_entry, data=new_config_entry)
85+
86+
_LOGGER.info("Migration to version %s successful", config_entry.version)
87+
88+
return True

‎custom_components/dijnet/config_flow.py

+17-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from homeassistant.core import callback
1313
from homeassistant.data_entry_flow import FlowResult
1414

15-
from .const import CONF_DOWNLOAD_DIR, DOMAIN
15+
from .const import CONF_DOWNLOAD_DIR, CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE, DOMAIN
1616
from .dijnet_session import DijnetSession
1717

1818
_LOGGER = logging.getLogger(__name__)
@@ -56,7 +56,11 @@ async def async_step_init(
5656
vol.Optional(
5757
CONF_DOWNLOAD_DIR,
5858
default=self.config_entry.data.get(CONF_DOWNLOAD_DIR)
59-
): str
59+
): str,
60+
vol.Required(
61+
CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE,
62+
default=self.config_entry.data[CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE]
63+
): bool
6064
})
6165

6266
if user_input is not None:
@@ -85,7 +89,7 @@ class DijnetConfigFlow(ConfigFlow, domain=DOMAIN):
8589
'''
8690
Configuration flow handler for Dijnet integration.
8791
'''
88-
VERSION = 1
92+
VERSION = 2
8993

9094
@staticmethod
9195
@callback
@@ -112,12 +116,16 @@ async def async_step_user(self, user_input: Dict[str, Any]) -> FlowResult:
112116
data_schema = vol.Schema({
113117
vol.Required(CONF_USERNAME): str,
114118
vol.Required(CONF_PASSWORD): str,
115-
vol.Optional(CONF_DOWNLOAD_DIR): str
119+
vol.Optional(CONF_DOWNLOAD_DIR): str,
120+
vol.Required(CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE): bool
116121
})
117122

118123
if user_input is not None:
119124
async with DijnetSession() as session:
120-
if not await session.post_login(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]):
125+
if not await session.post_login(
126+
user_input[CONF_USERNAME],
127+
user_input[CONF_PASSWORD]
128+
):
121129
return self.async_show_form(
122130
step_id='user',
123131
data_schema=data_schema,
@@ -130,7 +138,10 @@ async def async_step_user(self, user_input: Dict[str, Any]) -> FlowResult:
130138
data = {
131139
CONF_USERNAME: user_input[CONF_USERNAME],
132140
CONF_PASSWORD: user_input[CONF_PASSWORD],
133-
CONF_DOWNLOAD_DIR: user_input.get(CONF_DOWNLOAD_DIR, '')
141+
CONF_DOWNLOAD_DIR: user_input.get(CONF_DOWNLOAD_DIR, ''),
142+
CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE: user_input[
143+
CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE
144+
]
134145
}
135146

136147
return self.async_create_entry(

‎custom_components/dijnet/const.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
DOMAIN = 'dijnet'
55

66
CONF_DOWNLOAD_DIR = 'download_dir'
7+
CONF_ENCASHMENT_REPORTED_AS_PAID_AFTER_DEADLINE = 'encashment_reported_as_paid_after_deadline'
78

8-
DATA_CONTROLLER = 'controller'
9+
DATA_CONTROLLER = 'controller'

‎custom_components/dijnet/controller.py

+63-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import re
88
from datetime import datetime, timedelta
99
from os import makedirs, path
10-
from typing import Any, Dict, List
10+
from typing import Any, Dict, List, Optional
1111

1212
import yaml
1313
from homeassistant.helpers.typing import HomeAssistantType
@@ -298,7 +298,13 @@ class DijnetController:
298298
Responsible for providing data from Dijnet website.
299299
'''
300300

301-
def __init__(self, username: str, password: str, download_dir: str = None):
301+
def __init__(
302+
self,
303+
username: str,
304+
password: str,
305+
download_dir: str = None,
306+
encashment_reported_as_paid_after_deadline: bool = False
307+
):
302308
'''
303309
Initialize a new instance of DijnetController class.
304310
@@ -311,10 +317,15 @@ def __init__(self, username: str, password: str, download_dir: str = None):
311317
download_dir: str
312318
Optional download directory. If set then the invoice
313319
files are downloaded to that location.
320+
encashment_reported_as_paid_after_deadline: bool
321+
The value indicates whether the encashment
322+
should be reported as paid after deadline
314323
'''
315324
self._username = username
316325
self._password = password
317326
self._download_dir = download_dir
327+
self._encashment_reported_as_paid_after_deadline = \
328+
encashment_reported_as_paid_after_deadline
318329
self._registry: Dict[str, str] = None
319330
self._unpaid_invoices: List[Invoice] = []
320331
self._paid_invoices: List[Invoice] = []
@@ -376,7 +387,7 @@ async def update_registered_issuers(self):
376387
search_page = await session.get_invoice_search_page()
377388

378389
providers_json = re.search(
379-
r'var ropts = (.*);', search_page.decode("windows-1252")
390+
r'var ropts = (.*);', search_page.decode("iso-8859-2")
380391
).groups(1)[0]
381392

382393
raw_providers: List[Any] = json.loads(providers_json)
@@ -391,7 +402,10 @@ async def update_registered_issuers(self):
391402
issuer_id = row.children("td:nth-child(2)").text()
392403
display_name = row.children("td:nth-child(3)").text()
393404
providers = [
394-
raw_provider['szlaszolgnev'] for raw_provider in raw_providers if raw_provider['alias'] == display_name
405+
raw_provider['szlaszolgnev'] for
406+
raw_provider in
407+
raw_providers if
408+
raw_provider['alias'] == display_name
395409
]
396410
issuer = InvoiceIssuer(issuer_id, issuer_name, display_name, providers)
397411
issuers.append(issuer)
@@ -430,7 +444,12 @@ async def update_invoices(self):
430444
index = 0
431445
for row in invoices_pyquery.find('.szamla_table > tbody > tr').items():
432446
invoice: Invoice = None
433-
if self._is_invoice_paid(row):
447+
is_paid: Optional[bool] = self._is_invoice_paid(row)
448+
if is_paid is None:
449+
_LOGGER.error('Failed to determine invoice state. State column text: %s',
450+
row.children('td:nth-child(8)').text())
451+
continue
452+
elif self._is_invoice_paid(row):
434453
await session.get_invoice_page(index)
435454
invoice_history_page = await session.get_invoice_history_page()
436455
invoice_history_page_response_pyquery = pq(invoice_history_page)
@@ -443,8 +462,22 @@ async def update_invoices(self):
443462
invoice = self._create_invoice_from_row(row, paid_at)
444463
possible_new_paid_invoices.append(invoice)
445464
else:
446-
# not paid?
447-
invoice = self._create_invoice_from_row(row)
465+
# payment info not found, but invoice paid
466+
paid_at = datetime.strptime(row.children(
467+
'td:nth-child(6)').text(), DATE_FORMAT
468+
).replace(tzinfo=None).date().isoformat()
469+
invoice = self._create_invoice_from_row(row, paid_at)
470+
possible_new_paid_invoices.append(invoice)
471+
472+
if invoice is None:
473+
_LOGGER.warning(
474+
'History table rows not found. Setting paid_at value to deadline'
475+
)
476+
_LOGGER.debug(invoice_history_page.decode("iso-8859-2"))
477+
paid_at = datetime.strptime(row.children(
478+
'td:nth-child(6)').text(), DATE_FORMAT
479+
).replace(tzinfo=None).date().isoformat()
480+
invoice = self._create_invoice_from_row(row, paid_at)
448481
else:
449482
invoice = self._create_invoice_from_row(row)
450483
possible_new_unpaid_invoices.append(invoice)
@@ -576,8 +609,29 @@ def _create_invoice_from_row(self, row: PyQuery, paid_at: datetime = None) -> In
576609

577610
return invoice
578611

579-
def _is_invoice_paid(self, row: PyQuery) -> bool:
580-
return 'Rendezetlen' not in row.children('td:nth-child(8)').text()
612+
def _is_invoice_paid(self, row: PyQuery) -> Optional[bool]:
613+
paid: bool = 'Rendezett' in row.children('td:nth-child(8)').text()
614+
if paid:
615+
return True
616+
617+
paid = 'Fizetve' in row.children('td:nth-child(8)').text()
618+
if paid:
619+
return True
620+
621+
not_paid: bool = 'Rendezetlen' in row.children('td:nth-child(8)').text()
622+
if not_paid:
623+
return False
624+
625+
collection: bool = 'Csoportos beszedés' in row.children('td:nth-child(8)').text()
626+
if collection:
627+
if self._encashment_reported_as_paid_after_deadline:
628+
deadline = datetime.strptime(row.children(
629+
'td:nth-child(6)').text(), DATE_FORMAT).replace(tzinfo=None).date()
630+
return deadline < datetime.now().date()
631+
else:
632+
return False
633+
634+
return None
581635

582636
def _initialize_registry_and_unpaid_invoices(self):
583637
paid_invoices = None

‎custom_components/dijnet/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"issue_tracker": "https://github.com/laszlojakab/homeassistant-dijnet/issues",
77
"dependencies": [],
88
"config_flow": true,
9-
"version": "0.4.3-beta",
9+
"version": "0.5.2-beta",
1010
"codeowners": ["@laszlojakab"],
1111
"requirements": ["PyQuery==1.4.3"]
1212
}

‎custom_components/dijnet/strings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"data": {
66
"username": "Username",
77
"password": "Password",
8-
"download_dir": "Download directory"
8+
"download_dir": "Download directory",
9+
"encashment_reported_as_paid_after_deadline": "Encashment payments reported as paid after deadline"
910
},
1011
"description": "Set username and password. Optionally set download directory where downloaded invoices should be stored.",
1112
"title": "Configure Dijnet integration."

‎custom_components/dijnet/translations/en.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"data": {
66
"username": "Username",
77
"password": "Password",
8-
"download_dir": "Download directory"
8+
"download_dir": "Download directory",
9+
"encashment_reported_as_paid_after_deadline": "Encashment payments reported as paid after deadline"
910
},
1011
"description": "Set username and password. Optionally set download directory where downloaded invoices should be stored.",
1112
"title": "Configure Dijnet integration."
@@ -23,7 +24,8 @@
2324
"init": {
2425
"data": {
2526
"password": "Password",
26-
"download_dir": "Download directory"
27+
"download_dir": "Download directory",
28+
"encashment_reported_as_paid_after_deadline": "Encashment payments reported as paid after deadline"
2729
},
2830
"description": "Set password. Optionally set download directory where downloaded invoices should be stored. To set username remove the integration and readd.",
2931
"title": "Reconfigure Dijnet integration."

‎custom_components/dijnet/translations/hu.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"data": {
66
"username": "Felhasználó név",
77
"password": "Jelszó",
8-
"download_dir": "Letöltési könyvtár"
8+
"download_dir": "Letöltési könyvtár",
9+
"encashment_reported_as_paid_after_deadline": "Csoportos beszedéseket fizetettnek tekint számla lejártakor"
910
},
1011
"description": "Állítsd be a Dijnet-es felhasználónevedet és jelszavadat. Ha szeretnéd, hogy a számlák letöltésre kerüljenek adj meg egy könyvtárnevet is a letöltés helyének!",
1112
"title": "Dijnet integráció beállítása."
@@ -23,7 +24,8 @@
2324
"init": {
2425
"data": {
2526
"password": "Jelszó",
26-
"download_dir": "Letöltési könyvtár"
27+
"download_dir": "Letöltési könyvtár",
28+
"encashment_reported_as_paid_after_deadline": "Csoportos beszedéseket fizetettnek tekint számla lejártakor"
2729
},
2830
"description": "Állítsd be a Dijnet-es jelszavadat. Ha szeretnéd, hogy a számlák letöltésre kerüljenek adj meg egy könyvtárnevet is a letöltés helyének! Ha felhasználó nevet szeretnél váltani, akkor távolítsd el az integrációt, majd add hozzá újra!",
2931
"title": "Dijnet integráció beállítása."

0 commit comments

Comments
 (0)
Please sign in to comment.