brynq-sdk-alight 1.0.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brynq_sdk_alight/__init__.py +1058 -0
- brynq_sdk_alight/address.py +72 -0
- brynq_sdk_alight/archive/flat_wrapper.py +139 -0
- brynq_sdk_alight/archive/hrxml_generator.py +280 -0
- brynq_sdk_alight/archive/managers.py +132 -0
- brynq_sdk_alight/archive/managers_generic.py +114 -0
- brynq_sdk_alight/archive/managers_old_complex.py +294 -0
- brynq_sdk_alight/archive/managers_simple.py +229 -0
- brynq_sdk_alight/employee.py +81 -0
- brynq_sdk_alight/job.py +89 -0
- brynq_sdk_alight/leave.py +97 -0
- brynq_sdk_alight/pay_elements.py +97 -0
- brynq_sdk_alight/salary.py +89 -0
- brynq_sdk_alight/schemas/__init__.py +26 -0
- brynq_sdk_alight/schemas/absence.py +83 -0
- brynq_sdk_alight/schemas/address.py +113 -0
- brynq_sdk_alight/schemas/employee.py +656 -0
- brynq_sdk_alight/schemas/generated_envelope_xsd_schema/__init__.py +38683 -0
- brynq_sdk_alight/schemas/generated_envelope_xsd_schema/process_pay_serv_emp.py +622264 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/__init__.py +10965 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/csec_person.py +39808 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/hrxml_indicative_data.py +90318 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_bod.py +33869 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_list_currency_code_iso_7_04.py +365 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_list_language_code_iso_7_04.py +16 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_list_mimemedia_type_code_iana_7_04.py +16 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_list_unit_code_unece_7_04.py +14 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_lists.py +535 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_qualified_data_types.py +84 -0
- brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_unqualified_data_types.py +1449 -0
- brynq_sdk_alight/schemas/job.py +145 -0
- brynq_sdk_alight/schemas/leave.py +58 -0
- brynq_sdk_alight/schemas/payments.py +207 -0
- brynq_sdk_alight/schemas/salary.py +67 -0
- brynq_sdk_alight/schemas/termination.py +48 -0
- brynq_sdk_alight/schemas/timequota.py +66 -0
- brynq_sdk_alight/schemas/utils.py +452 -0
- brynq_sdk_alight/termination.py +103 -0
- brynq_sdk_alight/time_elements.py +121 -0
- brynq_sdk_alight/time_quotas.py +114 -0
- brynq_sdk_alight-1.0.0.dev0.dist-info/METADATA +20 -0
- brynq_sdk_alight-1.0.0.dev0.dist-info/RECORD +44 -0
- brynq_sdk_alight-1.0.0.dev0.dist-info/WHEEL +5 -0
- brynq_sdk_alight-1.0.0.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Employee:
|
|
5
|
+
"""
|
|
6
|
+
High-level Employee API (no manager layer).
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
alight = Alight()
|
|
10
|
+
xml = alight.employee.create({...})
|
|
11
|
+
xml = alight.employee.update(EmployeeModel(...))
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, client: Any):
|
|
15
|
+
self._client = client
|
|
16
|
+
|
|
17
|
+
def create(
|
|
18
|
+
self,
|
|
19
|
+
data: Dict[str, Any],
|
|
20
|
+
*,
|
|
21
|
+
logical_id: Optional[str] = None,
|
|
22
|
+
extension_data: Optional[Dict[str, Any]] = None,
|
|
23
|
+
pay_elements_data: Optional[Dict[str, Any]] = None,
|
|
24
|
+
envelope_options: Optional[Dict[str, Any]] = None,
|
|
25
|
+
pretty_print: bool = True,
|
|
26
|
+
) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Create a New Hire XML document (actionCode=ADD).
|
|
29
|
+
Returns XML string.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> alight.employee.create(
|
|
33
|
+
... {
|
|
34
|
+
... "person_id": "35561",
|
|
35
|
+
... "employee_id": "35561ZZGB",
|
|
36
|
+
... "given_name": "Alex",
|
|
37
|
+
... "family_name": "Mason",
|
|
38
|
+
... },
|
|
39
|
+
... extension_data={"bank_accounts": [{"iban": "GB00BARC20201530093459"}]},
|
|
40
|
+
... )
|
|
41
|
+
"""
|
|
42
|
+
return self._client.generate_employee_xml(
|
|
43
|
+
employee=data,
|
|
44
|
+
action_code="ADD",
|
|
45
|
+
logical_id=logical_id,
|
|
46
|
+
extension_data=extension_data,
|
|
47
|
+
pay_elements_data=pay_elements_data,
|
|
48
|
+
envelope_options=envelope_options,
|
|
49
|
+
pretty_print=pretty_print,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def update(
|
|
53
|
+
self,
|
|
54
|
+
data: Dict[str, Any],
|
|
55
|
+
*,
|
|
56
|
+
logical_id: Optional[str] = None,
|
|
57
|
+
extension_data: Optional[Dict[str, Any]] = None,
|
|
58
|
+
pay_elements_data: Optional[Dict[str, Any]] = None,
|
|
59
|
+
envelope_options: Optional[Dict[str, Any]] = None,
|
|
60
|
+
pretty_print: bool = True,
|
|
61
|
+
) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Create an Employee Change XML document (actionCode=CHANGE).
|
|
64
|
+
Returns XML string.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> alight.employee.update(
|
|
68
|
+
... {"person_id": "35561", "employee_id": "35561ZZGB", "email": "alex@example.com"},
|
|
69
|
+
... extension_data={"bank_accounts": [{"iban": "GB00BARC20201530093459"}]},
|
|
70
|
+
... pay_elements_data={"pay_element": [{"id": [{"value": "0010"}], "amount": {"value": "45000"}}]},
|
|
71
|
+
... )
|
|
72
|
+
"""
|
|
73
|
+
return self._client.generate_employee_xml(
|
|
74
|
+
employee=data,
|
|
75
|
+
action_code="CHANGE",
|
|
76
|
+
logical_id=logical_id,
|
|
77
|
+
extension_data=extension_data,
|
|
78
|
+
pay_elements_data=pay_elements_data,
|
|
79
|
+
envelope_options=envelope_options,
|
|
80
|
+
pretty_print=pretty_print,
|
|
81
|
+
)
|
brynq_sdk_alight/job.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, Mapping
|
|
2
|
+
|
|
3
|
+
from .schemas import Job as JobModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Job:
|
|
7
|
+
"""
|
|
8
|
+
High-level Job/Deployment API.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
alight = Alight()
|
|
12
|
+
xml = alight.job.create(data={...}, person_id="35561", employee_id="35561ZZGB")
|
|
13
|
+
xml = alight.job.update(data={...}, person_id="35561", employee_id="35561ZZGB")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, client: Any):
|
|
17
|
+
self._client = client
|
|
18
|
+
|
|
19
|
+
def create(
|
|
20
|
+
self,
|
|
21
|
+
data: Dict[str, Any],
|
|
22
|
+
*,
|
|
23
|
+
person_id: str,
|
|
24
|
+
employee_id: str,
|
|
25
|
+
logical_id: Optional[str] = None,
|
|
26
|
+
envelope_options: Optional[Dict[str, Any]] = None,
|
|
27
|
+
pretty_print: bool = True,
|
|
28
|
+
) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Create job/deployment data (actionCode=ADD). Returns XML string.
|
|
31
|
+
Requires both person_id and employee_id.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> alight.job.create(
|
|
35
|
+
... data={"job_code": "FIN-MGR", "department": "Finance"},
|
|
36
|
+
... person_id="35561",
|
|
37
|
+
... employee_id="35561ZZGB",
|
|
38
|
+
... )
|
|
39
|
+
"""
|
|
40
|
+
job_model = JobModel(**data)
|
|
41
|
+
payload = {
|
|
42
|
+
"person_id": person_id,
|
|
43
|
+
"employee_id": employee_id,
|
|
44
|
+
"job": job_model.model_dump(exclude_none=True, by_alias=True),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return self._client.generate_employee_xml(
|
|
48
|
+
employee=payload,
|
|
49
|
+
action_code="ADD",
|
|
50
|
+
logical_id=logical_id,
|
|
51
|
+
envelope_options=envelope_options,
|
|
52
|
+
pretty_print=pretty_print,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def update(
|
|
56
|
+
self,
|
|
57
|
+
data: Mapping[str, Any],
|
|
58
|
+
*,
|
|
59
|
+
person_id: str,
|
|
60
|
+
employee_id: str,
|
|
61
|
+
logical_id: Optional[str] = None,
|
|
62
|
+
envelope_options: Optional[dict[str, Any]] = None,
|
|
63
|
+
pretty_print: bool = True,
|
|
64
|
+
) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Update job/deployment data (actionCode=CHANGE). Returns XML string.
|
|
67
|
+
Requires both person_id and employee_id.
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> alight.job.update(
|
|
71
|
+
... data={"job": {"job_code": "FIN-DIR", "effective_date": "2024-04-01"}},
|
|
72
|
+
... person_id="35561",
|
|
73
|
+
... employee_id="35561ZZGB",
|
|
74
|
+
... )
|
|
75
|
+
"""
|
|
76
|
+
job_model = JobModel(**dict(data))
|
|
77
|
+
payload = {
|
|
78
|
+
"person_id": person_id,
|
|
79
|
+
"employee_id": employee_id,
|
|
80
|
+
"job": job_model.model_dump(exclude_none=True, by_alias=True),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return self._client.generate_employee_xml(
|
|
84
|
+
employee=payload,
|
|
85
|
+
action_code="CHANGE",
|
|
86
|
+
logical_id=logical_id,
|
|
87
|
+
envelope_options=envelope_options,
|
|
88
|
+
pretty_print=pretty_print,
|
|
89
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from .schemas import Leave as LeaveModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Leave:
|
|
7
|
+
"""
|
|
8
|
+
High-level Leave API.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
alight = Alight()
|
|
12
|
+
xml = alight.leave.create(data={...}, person_id="35561", employee_id="35561ZZGB")
|
|
13
|
+
xml = alight.leave.update(data={...}, person_id="35561", employee_id="35561ZZGB")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, client: Any):
|
|
17
|
+
self._client = client
|
|
18
|
+
|
|
19
|
+
def create(
|
|
20
|
+
self,
|
|
21
|
+
data: Dict[str, Any],
|
|
22
|
+
*,
|
|
23
|
+
person_id: str,
|
|
24
|
+
employee_id: str,
|
|
25
|
+
logical_id: Optional[str] = None,
|
|
26
|
+
envelope_options: Optional[Dict[str, Any]] = None,
|
|
27
|
+
pretty_print: bool = True,
|
|
28
|
+
) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Create leave entry (actionCode=ADD). Returns XML string.
|
|
31
|
+
Requires both person_id and employee_id.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> alight.leave.create(
|
|
35
|
+
... data={
|
|
36
|
+
... "leave_type": "PARENTAL",
|
|
37
|
+
... "start_date": "2024-02-01",
|
|
38
|
+
... "end_date": "2024-02-15",
|
|
39
|
+
... },
|
|
40
|
+
... person_id="35561",
|
|
41
|
+
... employee_id="35561ZZGB",
|
|
42
|
+
... )
|
|
43
|
+
"""
|
|
44
|
+
leave_model = LeaveModel(**data)
|
|
45
|
+
payload = {
|
|
46
|
+
"person_id": person_id,
|
|
47
|
+
"employee_id": employee_id,
|
|
48
|
+
"leave": leave_model.model_dump(exclude_none=True, by_alias=True),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return self._client.generate_employee_xml(
|
|
52
|
+
employee=payload,
|
|
53
|
+
action_code="ADD",
|
|
54
|
+
logical_id=logical_id,
|
|
55
|
+
envelope_options=envelope_options,
|
|
56
|
+
pretty_print=pretty_print,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def update(
|
|
60
|
+
self,
|
|
61
|
+
data: Dict[str, Any],
|
|
62
|
+
*,
|
|
63
|
+
person_id: str,
|
|
64
|
+
employee_id: str,
|
|
65
|
+
logical_id: Optional[str] = None,
|
|
66
|
+
envelope_options: Optional[dict[str, Any]] = None,
|
|
67
|
+
pretty_print: bool = True,
|
|
68
|
+
) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Update leave entry (actionCode=CHANGE). Returns XML string.
|
|
71
|
+
Requires both person_id and employee_id.
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
>>> alight.leave.update(
|
|
75
|
+
... data={"leave": {"leave_type": "SICK", "status": "Cancelled"}},
|
|
76
|
+
... person_id="35561",
|
|
77
|
+
... employee_id="35561ZZGB",
|
|
78
|
+
... )
|
|
79
|
+
"""
|
|
80
|
+
if 'leave' in data and data['leave'] is not None:
|
|
81
|
+
nested = data['leave']
|
|
82
|
+
leave_model = nested if isinstance(nested, LeaveModel) else LeaveModel(**nested)
|
|
83
|
+
else:
|
|
84
|
+
leave_model = LeaveModel(**data)
|
|
85
|
+
payload = {
|
|
86
|
+
"person_id": person_id,
|
|
87
|
+
"employee_id": employee_id,
|
|
88
|
+
"leave": leave_model.model_dump(exclude_none=True, by_alias=True),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return self._client.generate_employee_xml(
|
|
92
|
+
employee=payload,
|
|
93
|
+
action_code="CHANGE",
|
|
94
|
+
logical_id=logical_id,
|
|
95
|
+
envelope_options=envelope_options,
|
|
96
|
+
pretty_print=pretty_print,
|
|
97
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from .schemas.payments import PayElementCreate
|
|
4
|
+
from .schemas.generated_envelope_xsd_schema.process_pay_serv_emp import (
|
|
5
|
+
PayServEmpPayElements as XsdPayServEmpPayElements,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PayElements:
|
|
10
|
+
"""
|
|
11
|
+
High-level PayElements API. Builds PayServEmpPayElements from a list of PayElementCreate.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
alight = Alight()
|
|
15
|
+
xml = alight.pay_elements.create(elements=[{...}], person_id="35561", employee_id="35561ZZGB")
|
|
16
|
+
xml = alight.pay_elements.update(elements=[{...}], person_id="35561", employee_id="35561ZZGB")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: Any):
|
|
20
|
+
self._client = client
|
|
21
|
+
|
|
22
|
+
def _build_pay_elements(self, elements: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
Normalize incoming pay elements and validate them against the generated XSD model.
|
|
25
|
+
|
|
26
|
+
Accepts plain dictionaries and returns the nested payload expected by `generate_employee_xml`.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> self._build_pay_elements([
|
|
30
|
+
... {"element_code": "0010", "amount": 1200, "currency_code": "GBP"},
|
|
31
|
+
... ])
|
|
32
|
+
"""
|
|
33
|
+
normalized: List[Dict[str, Any]] = []
|
|
34
|
+
for item in elements:
|
|
35
|
+
model = item if isinstance(item, PayElementCreate) else PayElementCreate(**item)
|
|
36
|
+
normalized.append(model.to_nested_dict())
|
|
37
|
+
payload = {"pay_element": normalized}
|
|
38
|
+
XsdPayServEmpPayElements.model_validate(payload)
|
|
39
|
+
return payload
|
|
40
|
+
|
|
41
|
+
def create(
|
|
42
|
+
self,
|
|
43
|
+
*,
|
|
44
|
+
elements: List[Dict[str, Any]],
|
|
45
|
+
person_id: str,
|
|
46
|
+
employee_id: str,
|
|
47
|
+
logical_id: Optional[str] = None,
|
|
48
|
+
envelope_options: Optional[Dict[str, Any]] = None,
|
|
49
|
+
pretty_print: bool = True,
|
|
50
|
+
) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Generate an ADD envelope for pay elements using the minimal person identifiers.
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> alight.pay_elements.create(
|
|
56
|
+
... elements=[{"element_code": "0010", "amount": "1200", "currency_code": "GBP"}],
|
|
57
|
+
... person_id="35561",
|
|
58
|
+
... employee_id="35561ZZGB",
|
|
59
|
+
... )
|
|
60
|
+
"""
|
|
61
|
+
pay_elements_data = self._build_pay_elements(elements)
|
|
62
|
+
|
|
63
|
+
return self._client.generate_employee_xml(
|
|
64
|
+
employee={"person_id": person_id, "employee_id": employee_id},
|
|
65
|
+
action_code="ADD",
|
|
66
|
+
logical_id=logical_id,
|
|
67
|
+
pay_elements_data=pay_elements_data,
|
|
68
|
+
envelope_options=envelope_options,
|
|
69
|
+
pretty_print=pretty_print,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def update(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
elements: List[Dict[str, Any]],
|
|
76
|
+
person_id: str,
|
|
77
|
+
employee_id: str,
|
|
78
|
+
logical_id: Optional[str] = None,
|
|
79
|
+
envelope_options: Optional[Dict[str, Any]] = None,
|
|
80
|
+
pretty_print: bool = True,
|
|
81
|
+
) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Generate a CHANGE envelope for existing pay elements while keeping identifiers flat.
|
|
84
|
+
|
|
85
|
+
Mirrors `create` but reuses the CHANGE action code so downstream consumers can differ updates
|
|
86
|
+
from new allocations without rebuilding envelopes manually.
|
|
87
|
+
"""
|
|
88
|
+
pay_elements_data = self._build_pay_elements(elements)
|
|
89
|
+
|
|
90
|
+
return self._client.generate_employee_xml(
|
|
91
|
+
employee={"person_id": person_id, "employee_id": employee_id},
|
|
92
|
+
action_code="CHANGE",
|
|
93
|
+
logical_id=logical_id,
|
|
94
|
+
pay_elements_data=pay_elements_data,
|
|
95
|
+
envelope_options=envelope_options,
|
|
96
|
+
pretty_print=pretty_print,
|
|
97
|
+
)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, Union
|
|
2
|
+
from xsdata.models.datatype import XmlDate
|
|
3
|
+
|
|
4
|
+
from .schemas import Salary as SalaryModel, EmployeeCreate as EmployeeModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Salary:
|
|
8
|
+
"""
|
|
9
|
+
High-level Salary API (no manager layer).
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
alight = Alight()
|
|
13
|
+
xml = alight.salary.update(salary={...}, identifiers={"person_id": "35561", "employee_id": "35561ZZGB"})
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, client: Any):
|
|
17
|
+
self._client = client
|
|
18
|
+
|
|
19
|
+
def update(
|
|
20
|
+
self,
|
|
21
|
+
salary: Union[SalaryModel, Dict[str, Any]],
|
|
22
|
+
*,
|
|
23
|
+
identifiers: Optional[Dict[str, Any]] = None,
|
|
24
|
+
employee: Optional[Union[EmployeeModel, Dict[str, Any]]] = None,
|
|
25
|
+
logical_id: Optional[str] = None,
|
|
26
|
+
pretty_print: bool = True,
|
|
27
|
+
) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Create an Employee Change XML with salary change as PayElements.
|
|
30
|
+
Returns XML string.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
>>> alight.salary.update(
|
|
34
|
+
... salary={"base_salary": 42000, "currency_code": "GBP"},
|
|
35
|
+
... identifiers={"person_id": "35561", "employee_id": "35561ZZGB"},
|
|
36
|
+
... )
|
|
37
|
+
|
|
38
|
+
Passing `salary` as a plain dict is enough—the helper wraps it in the generated `Salary` schema
|
|
39
|
+
and derives the recurring pay element payload automatically.
|
|
40
|
+
"""
|
|
41
|
+
# Build or augment an EmployeeModel with identifiers and salary
|
|
42
|
+
if employee is None:
|
|
43
|
+
if not identifiers or "person_id" not in identifiers:
|
|
44
|
+
raise ValueError("identifiers must include at least 'person_id' when no employee is provided")
|
|
45
|
+
base: Dict[str, Any] = {
|
|
46
|
+
"person_id": identifiers.get("person_id"),
|
|
47
|
+
}
|
|
48
|
+
if identifiers.get("employee_id"):
|
|
49
|
+
base["employee_id"] = identifiers.get("employee_id")
|
|
50
|
+
employee_model = EmployeeModel(**base)
|
|
51
|
+
else:
|
|
52
|
+
employee_model = employee if isinstance(employee, EmployeeModel) else EmployeeModel(**employee)
|
|
53
|
+
|
|
54
|
+
# Normalize salary model
|
|
55
|
+
salary_model = salary if isinstance(salary, SalaryModel) else SalaryModel(**salary)
|
|
56
|
+
|
|
57
|
+
# Convert salary to alias-based nested dict compatible with to_model mapping
|
|
58
|
+
# Alight expects a default pay element code/type even if upstream sources omit them,
|
|
59
|
+
# so fall back to the recurring base salary identifiers that match standard setup.
|
|
60
|
+
pay_elements_data = {
|
|
61
|
+
"pay_element": [
|
|
62
|
+
{
|
|
63
|
+
"id": [{"value": salary_model.element_code or "0010"}],
|
|
64
|
+
"pay_element_type": {"value": salary_model.element_type or "RECURRING"},
|
|
65
|
+
"amount": {"value": f"{salary_model.base_salary:.2f}"},
|
|
66
|
+
"currency_code": {"value": salary_model.currency_code},
|
|
67
|
+
"valid_from": (
|
|
68
|
+
XmlDate.from_string(salary_model.valid_from.isoformat())
|
|
69
|
+
if salary_model.valid_from
|
|
70
|
+
else None
|
|
71
|
+
),
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Clean None values in pay element
|
|
77
|
+
for k in list(pay_elements_data["pay_element"][0].keys()):
|
|
78
|
+
if pay_elements_data["pay_element"][0][k] is None:
|
|
79
|
+
del pay_elements_data["pay_element"][0][k]
|
|
80
|
+
|
|
81
|
+
# Also mirror salary on the Employee flat fields so IndicativeData contains identifiers
|
|
82
|
+
# Employee.to_model handles wrapping; pay elements go via pay_elements_data
|
|
83
|
+
return self._client.generate_employee_xml(
|
|
84
|
+
employee=employee_model,
|
|
85
|
+
action_code="CHANGE",
|
|
86
|
+
logical_id=logical_id,
|
|
87
|
+
pay_elements_data=pay_elements_data,
|
|
88
|
+
pretty_print=pretty_print,
|
|
89
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Alight SDK schema models.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .employee import EmployeeCreate
|
|
6
|
+
from .salary import Salary
|
|
7
|
+
from .address import Address
|
|
8
|
+
from .job import Job
|
|
9
|
+
from .leave import Leave
|
|
10
|
+
from .termination import Termination
|
|
11
|
+
from .absence import Absence
|
|
12
|
+
from .timequota import TimeQuota
|
|
13
|
+
from .payments import PayServEmpExtensionCreate, PayElementCreate
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"EmployeeCreate",
|
|
17
|
+
"Salary",
|
|
18
|
+
"Address",
|
|
19
|
+
"Job",
|
|
20
|
+
"Leave",
|
|
21
|
+
"Termination",
|
|
22
|
+
"Absence",
|
|
23
|
+
"TimeQuota",
|
|
24
|
+
"PayServEmpExtensionCreate",
|
|
25
|
+
"PayElementCreate",
|
|
26
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flat, user-friendly absence (time element) model for Alight SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
from typing import Optional, Dict, Any, List
|
|
7
|
+
from pydantic import Field, BaseModel
|
|
8
|
+
|
|
9
|
+
from .utils import add_to_nested_path, convert_datetime_to_xml, post_process_nested_data
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Absence(BaseModel):
|
|
13
|
+
"""
|
|
14
|
+
Simplified time element mapping to PayServEmpTimeElements.time_element[0].
|
|
15
|
+
"""
|
|
16
|
+
model_config = {
|
|
17
|
+
"populate_by_name": True
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# Core identifiers and reason
|
|
21
|
+
id: Optional[str] = Field(default=None, alias="time_element[0].id[0]")
|
|
22
|
+
absence_reason: Optional[str] = Field(default=None, alias="time_element[0].absence_reason")
|
|
23
|
+
|
|
24
|
+
# Quantities
|
|
25
|
+
units: Optional[str] = Field(default=None, alias="time_element[0].units")
|
|
26
|
+
unit_type: Optional[str] = Field(default=None, alias="time_element[0].unit_type")
|
|
27
|
+
|
|
28
|
+
# Dates
|
|
29
|
+
valid_from: Optional[datetime.date] = Field(default=None, alias="time_element[0].valid_from")
|
|
30
|
+
valid_to: Optional[datetime.date] = Field(default=None, alias="time_element[0].valid_to")
|
|
31
|
+
|
|
32
|
+
# Easy additional fields
|
|
33
|
+
comments: Optional[str] = Field(default=None, alias="time_element[0].comments")
|
|
34
|
+
absence_post: Optional[str] = Field(default=None, alias="time_element[0].absence_post")
|
|
35
|
+
|
|
36
|
+
def to_nested_dict(self) -> Dict[str, Any]:
|
|
37
|
+
flat = self.model_dump(exclude_none=True, by_alias=True)
|
|
38
|
+
nested: Dict[str, Any] = {}
|
|
39
|
+
for k, v in flat.items():
|
|
40
|
+
add_to_nested_path(nested, k, v)
|
|
41
|
+
# Convert dates to XML compatible
|
|
42
|
+
for key in ("valid_from", "valid_to"):
|
|
43
|
+
if key in nested and isinstance(nested[key], (datetime.date, datetime.datetime)):
|
|
44
|
+
nested[key] = convert_datetime_to_xml(nested[key], None)
|
|
45
|
+
return nested
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Absences(BaseModel):
|
|
49
|
+
"""
|
|
50
|
+
Wrapper for multiple Absence items mapping to time_element[*].
|
|
51
|
+
"""
|
|
52
|
+
absences: Optional[List[Absence]] = Field(default=None, description="List of absence time elements")
|
|
53
|
+
|
|
54
|
+
def to_nested_dict(self) -> Dict[str, Any]:
|
|
55
|
+
nested: Dict[str, Any] = {}
|
|
56
|
+
if not self.absences:
|
|
57
|
+
return nested
|
|
58
|
+
# Merge each absence into time_element list
|
|
59
|
+
time_elements: List[Dict[str, Any]] = []
|
|
60
|
+
for idx, item in enumerate(self.absences):
|
|
61
|
+
data = item.to_nested_dict()
|
|
62
|
+
# Convert indexed paths from [0] to [idx]
|
|
63
|
+
remapped: Dict[str, Any] = {}
|
|
64
|
+
for k, v in data.items():
|
|
65
|
+
remapped[k.replace("time_element[0]", f"time_element[{idx}]")] = v
|
|
66
|
+
# Build nested for this item and then merge
|
|
67
|
+
item_nested: Dict[str, Any] = {}
|
|
68
|
+
for k, v in remapped.items():
|
|
69
|
+
add_to_nested_path(item_nested, k, v)
|
|
70
|
+
# Collect the single time_element dict
|
|
71
|
+
if "time_element" in item_nested and isinstance(item_nested["time_element"], list):
|
|
72
|
+
time_elements.extend(item_nested["time_element"])
|
|
73
|
+
if time_elements:
|
|
74
|
+
nested["time_element"] = time_elements
|
|
75
|
+
# Post-process using the XSD model to wrap units and convert dates
|
|
76
|
+
try:
|
|
77
|
+
from ..schemas.generated_envelope_xsd_schema.process_pay_serv_emp import (
|
|
78
|
+
PayServEmpTimeElements as XsdPayServEmpTimeElements,
|
|
79
|
+
)
|
|
80
|
+
post_process_nested_data(nested, XsdPayServEmpTimeElements)
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
return nested
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flat, user-friendly address model for Alight SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Dict, Any, List
|
|
6
|
+
from pydantic import Field, BaseModel
|
|
7
|
+
|
|
8
|
+
# Composition approach: plain Pydantic BaseModel; conversion handled by callers
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Address(BaseModel):
|
|
12
|
+
"""
|
|
13
|
+
Simplified address model.
|
|
14
|
+
Uses PURE schema-driven conversion - NO hardcoded structure mappings.
|
|
15
|
+
Uses aliases to match expected field names in Employee model.
|
|
16
|
+
"""
|
|
17
|
+
model_config = {
|
|
18
|
+
"populate_by_name": True # Allow populating by field name in addition to alias
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
line_1: str = Field(
|
|
22
|
+
description="Street address line 1",
|
|
23
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.line_one"
|
|
24
|
+
)
|
|
25
|
+
line_2: Optional[str] = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description="Street address line 2",
|
|
28
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.line_two"
|
|
29
|
+
)
|
|
30
|
+
line_3: Optional[str] = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="Street address line 3",
|
|
33
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.line_three"
|
|
34
|
+
)
|
|
35
|
+
line_4: Optional[str] = Field(
|
|
36
|
+
default=None,
|
|
37
|
+
description="Street address line 4",
|
|
38
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.line_four"
|
|
39
|
+
)
|
|
40
|
+
line_5: Optional[str] = Field(
|
|
41
|
+
default=None,
|
|
42
|
+
description="Street address line 5",
|
|
43
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.line_five"
|
|
44
|
+
)
|
|
45
|
+
building_number: Optional[str] = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
description="Building number",
|
|
48
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.building_number"
|
|
49
|
+
)
|
|
50
|
+
building_name: Optional[str] = Field(
|
|
51
|
+
default=None,
|
|
52
|
+
description="Building name",
|
|
53
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.building_name"
|
|
54
|
+
)
|
|
55
|
+
street_name: Optional[str] = Field(
|
|
56
|
+
default=None,
|
|
57
|
+
description="Street name",
|
|
58
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.street_name"
|
|
59
|
+
)
|
|
60
|
+
unit: Optional[str] = Field(
|
|
61
|
+
default=None,
|
|
62
|
+
description="Unit or apartment identifier",
|
|
63
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.unit"
|
|
64
|
+
)
|
|
65
|
+
floor: Optional[str] = Field(
|
|
66
|
+
default=None,
|
|
67
|
+
description="Floor number",
|
|
68
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.floor"
|
|
69
|
+
)
|
|
70
|
+
post_office_box: Optional[str] = Field(
|
|
71
|
+
default=None,
|
|
72
|
+
description="P.O. Box",
|
|
73
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.post_office_box"
|
|
74
|
+
)
|
|
75
|
+
city: str = Field(
|
|
76
|
+
description="City name",
|
|
77
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.city_name"
|
|
78
|
+
)
|
|
79
|
+
state_province: Optional[str] = Field(
|
|
80
|
+
default=None,
|
|
81
|
+
description="State/province",
|
|
82
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.country_sub_division_code"
|
|
83
|
+
)
|
|
84
|
+
delivery_point_code: Optional[List[str]] = Field(
|
|
85
|
+
default=None,
|
|
86
|
+
description="Delivery point code(s)",
|
|
87
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.delivery_point_code"
|
|
88
|
+
)
|
|
89
|
+
postal_code: str = Field(
|
|
90
|
+
description="ZIP/postal code",
|
|
91
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.postal_code"
|
|
92
|
+
)
|
|
93
|
+
country: str = Field(
|
|
94
|
+
description="Country code (GB/US/etc.)",
|
|
95
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.country_code"
|
|
96
|
+
)
|
|
97
|
+
city_sub_division_name: Optional[List[str]] = Field(
|
|
98
|
+
default=None,
|
|
99
|
+
description="Neighborhood or city subdivision name(s)",
|
|
100
|
+
alias="indicative_person_dossier.indicative_person.communication[2].address.city_sub_division_name"
|
|
101
|
+
)
|
|
102
|
+
use_code: Optional[str] = Field(
|
|
103
|
+
default=None,
|
|
104
|
+
description="Address use context (HOME/WORK)",
|
|
105
|
+
alias="indicative_person_dossier.indicative_person.communication[2].use_code"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def to_nested_dict(self) -> Dict[str, Any]:
|
|
109
|
+
"""
|
|
110
|
+
Convert to nested structure compatible with employee address fields.
|
|
111
|
+
"""
|
|
112
|
+
# Convert to dictionary with aliases - will directly map to employee fields
|
|
113
|
+
return self.model_dump(exclude_none=True, by_alias=True)
|