Skip to content

Commit 99b60d9

Browse files
committed
Add EX_BASE_02 branch details query
1 parent d990d6a commit 99b60d9

3 files changed

Lines changed: 181 additions & 1 deletion

File tree

ergani/client.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from ergani.auth import ErganiAuthentication
88
from ergani.exceptions import APIError, AuthenticationError
99
from ergani.models import (
10+
BusinessBranch,
1011
CompanyDailySchedule,
1112
CompanyOvertime,
1213
CompanyWeeklySchedule,
@@ -280,3 +281,28 @@ def get_services_list(self) -> Optional[Response]:
280281
"""
281282

282283
return self._request("GET", "/WebServices/ServicesList", None)
284+
285+
def get_branch_details(self) -> List[BusinessBranch]:
286+
"""
287+
Fetches the authenticated employer's branch details from the Ergani API.
288+
289+
Returns:
290+
List[BusinessBranch]: The parsed branch detail entries.
291+
292+
Raises:
293+
APIError: An error occurred while communicating with the Ergani API
294+
AuthenticationError: Raised if there is an authentication error with the Ergani API
295+
ValueError: The response payload could not be parsed as a branch list
296+
"""
297+
298+
response = self._execute_service("EX_BASE_02")
299+
300+
if response is None:
301+
return BusinessBranch.parse_many(None)
302+
303+
try:
304+
payload = response.json()
305+
except ValueError as error:
306+
raise ValueError("EX_BASE_02 returned a non-JSON response") from error
307+
308+
return BusinessBranch.parse_many(payload)

ergani/models.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from __future__ import annotations
2+
13
from dataclasses import dataclass, field
24
from datetime import date, datetime, time
3-
from typing import List, Literal, Optional, TypedDict
5+
from typing import Any, Dict, List, Literal, Optional, TypedDict
46

57
from ergani.typings import (
68
LateDeclarationJustificationType,
@@ -21,6 +23,78 @@
2123
)
2224

2325

26+
@dataclass
27+
class BusinessBranch:
28+
"""
29+
Represents a business branch returned by the Ergani query services.
30+
31+
Attributes:
32+
branch_number (Optional[int]): The verified branch identifier used by later
33+
query endpoints when present in the payload.
34+
address (Optional[str]): The branch address when returned by EX_BASE_02.
35+
raw_payload (Dict[str, Any]): The original response object returned by the API.
36+
"""
37+
38+
branch_number: Optional[int]
39+
address: Optional[str]
40+
raw_payload: Dict[str, Any]
41+
42+
@classmethod
43+
def parse_many(cls, payload: Any) -> List[BusinessBranch]:
44+
branch_payloads = cls._parse_payloads(payload)
45+
46+
return [cls._parse(payload) for payload in branch_payloads]
47+
48+
@classmethod
49+
def _parse(cls, payload: Any) -> BusinessBranch:
50+
if not isinstance(payload, dict):
51+
raise ValueError("Expected branch details item to be an object")
52+
53+
return cls(
54+
branch_number=cls._parse_branch_number(payload),
55+
address=payload.get("Address"),
56+
raw_payload=dict(payload),
57+
)
58+
59+
@classmethod
60+
def _parse_payloads(cls, payload: Any) -> List[Dict[str, Any]]:
61+
if payload is None:
62+
return []
63+
64+
if isinstance(payload, dict):
65+
if "EX_BASE_02" in payload:
66+
payload = payload["EX_BASE_02"]
67+
68+
if isinstance(payload, dict) and "Pararthma" in payload:
69+
payload = payload["Pararthma"]
70+
71+
if isinstance(payload, dict):
72+
return [payload]
73+
74+
if not isinstance(payload, list):
75+
raise ValueError("Expected branch details response to be a list")
76+
77+
return payload
78+
79+
@classmethod
80+
def _parse_branch_number(cls, payload: Dict[str, Any]) -> Optional[int]:
81+
for key in ("PararthmaAa", "Aa"):
82+
if key in payload:
83+
return _parse_int(payload[key])
84+
85+
return None
86+
87+
88+
def _parse_int(value: Any) -> Optional[int]:
89+
if value is None or value == "":
90+
return None
91+
92+
try:
93+
return int(value)
94+
except (TypeError, ValueError):
95+
return None
96+
97+
2498
@dataclass
2599
class WorkCard:
26100
"""

tests/test_branch_details.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from unittest import TestCase
2+
from unittest.mock import Mock, patch
3+
4+
from requests.models import Response
5+
6+
from ergani.client import ErganiClient
7+
from ergani.models import BusinessBranch
8+
9+
10+
class BusinessBranchTests(TestCase):
11+
def test_business_branch_parse_many_reads_wrapped_response(self):
12+
self.assertEqual(
13+
BusinessBranch.parse_many(
14+
{
15+
"EX_BASE_02": {
16+
"Pararthma": {
17+
"Aa": "0",
18+
"Address": "ΠΛΑΤΕΙΑ ΑΓ. ΘΕΟΔΩΡΩΝ 10561 ΑΘΗΝΑ",
19+
}
20+
}
21+
}
22+
),
23+
[
24+
BusinessBranch(
25+
branch_number=0,
26+
address="ΠΛΑΤΕΙΑ ΑΓ. ΘΕΟΔΩΡΩΝ 10561 ΑΘΗΝΑ",
27+
raw_payload={
28+
"Aa": "0",
29+
"Address": "ΠΛΑΤΕΙΑ ΑΓ. ΘΕΟΔΩΡΩΝ 10561 ΑΘΗΝΑ",
30+
},
31+
)
32+
],
33+
)
34+
35+
def test_business_branch_parse_many_requires_object_or_list_payload(self):
36+
with self.assertRaises(ValueError):
37+
BusinessBranch.parse_many("invalid")
38+
39+
def test_get_branch_details_uses_execute_service_and_parses_wrapped_response(self):
40+
client = ErganiClient("username", "password", "https://example.test")
41+
response = Mock(spec=Response)
42+
response.json.return_value = {
43+
"EX_BASE_02": {
44+
"Pararthma": {
45+
"Aa": "0",
46+
"Address": "ΠΛΑΤΕΙΑ ΑΓ. ΘΕΟΔΩΡΩΝ 10561 ΑΘΗΝΑ",
47+
}
48+
}
49+
}
50+
51+
with patch.object(
52+
client, "_execute_service", return_value=response
53+
) as execute_service_mock:
54+
result = client.get_branch_details()
55+
56+
self.assertEqual(
57+
result,
58+
[
59+
BusinessBranch(
60+
branch_number=0,
61+
address="ΠΛΑΤΕΙΑ ΑΓ. ΘΕΟΔΩΡΩΝ 10561 ΑΘΗΝΑ",
62+
raw_payload={
63+
"Aa": "0",
64+
"Address": "ΠΛΑΤΕΙΑ ΑΓ. ΘΕΟΔΩΡΩΝ 10561 ΑΘΗΝΑ",
65+
},
66+
)
67+
],
68+
)
69+
execute_service_mock.assert_called_once_with("EX_BASE_02")
70+
71+
def test_get_branch_details_returns_empty_list_for_no_content(self):
72+
client = ErganiClient("username", "password", "https://example.test")
73+
74+
with patch.object(
75+
client, "_execute_service", return_value=None
76+
) as execute_service_mock:
77+
result = client.get_branch_details()
78+
79+
self.assertEqual(result, [])
80+
execute_service_mock.assert_called_once_with("EX_BASE_02")

0 commit comments

Comments
 (0)