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.
Files changed (44) hide show
  1. brynq_sdk_alight/__init__.py +1058 -0
  2. brynq_sdk_alight/address.py +72 -0
  3. brynq_sdk_alight/archive/flat_wrapper.py +139 -0
  4. brynq_sdk_alight/archive/hrxml_generator.py +280 -0
  5. brynq_sdk_alight/archive/managers.py +132 -0
  6. brynq_sdk_alight/archive/managers_generic.py +114 -0
  7. brynq_sdk_alight/archive/managers_old_complex.py +294 -0
  8. brynq_sdk_alight/archive/managers_simple.py +229 -0
  9. brynq_sdk_alight/employee.py +81 -0
  10. brynq_sdk_alight/job.py +89 -0
  11. brynq_sdk_alight/leave.py +97 -0
  12. brynq_sdk_alight/pay_elements.py +97 -0
  13. brynq_sdk_alight/salary.py +89 -0
  14. brynq_sdk_alight/schemas/__init__.py +26 -0
  15. brynq_sdk_alight/schemas/absence.py +83 -0
  16. brynq_sdk_alight/schemas/address.py +113 -0
  17. brynq_sdk_alight/schemas/employee.py +656 -0
  18. brynq_sdk_alight/schemas/generated_envelope_xsd_schema/__init__.py +38683 -0
  19. brynq_sdk_alight/schemas/generated_envelope_xsd_schema/process_pay_serv_emp.py +622264 -0
  20. brynq_sdk_alight/schemas/generated_xsd_schemas/__init__.py +10965 -0
  21. brynq_sdk_alight/schemas/generated_xsd_schemas/csec_person.py +39808 -0
  22. brynq_sdk_alight/schemas/generated_xsd_schemas/hrxml_indicative_data.py +90318 -0
  23. brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_bod.py +33869 -0
  24. brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_list_currency_code_iso_7_04.py +365 -0
  25. brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_list_language_code_iso_7_04.py +16 -0
  26. brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_list_mimemedia_type_code_iana_7_04.py +16 -0
  27. brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_list_unit_code_unece_7_04.py +14 -0
  28. brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_code_lists.py +535 -0
  29. brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_qualified_data_types.py +84 -0
  30. brynq_sdk_alight/schemas/generated_xsd_schemas/openapplications_unqualified_data_types.py +1449 -0
  31. brynq_sdk_alight/schemas/job.py +145 -0
  32. brynq_sdk_alight/schemas/leave.py +58 -0
  33. brynq_sdk_alight/schemas/payments.py +207 -0
  34. brynq_sdk_alight/schemas/salary.py +67 -0
  35. brynq_sdk_alight/schemas/termination.py +48 -0
  36. brynq_sdk_alight/schemas/timequota.py +66 -0
  37. brynq_sdk_alight/schemas/utils.py +452 -0
  38. brynq_sdk_alight/termination.py +103 -0
  39. brynq_sdk_alight/time_elements.py +121 -0
  40. brynq_sdk_alight/time_quotas.py +114 -0
  41. brynq_sdk_alight-1.0.0.dev0.dist-info/METADATA +20 -0
  42. brynq_sdk_alight-1.0.0.dev0.dist-info/RECORD +44 -0
  43. brynq_sdk_alight-1.0.0.dev0.dist-info/WHEEL +5 -0
  44. brynq_sdk_alight-1.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,656 @@
1
+ """
2
+ Flat, user-friendly employee model for Alight SDK.
3
+ Automatically converts to complex nested HR-XML structure using schema introspection.
4
+ """
5
+
6
+ import datetime
7
+ from typing import Optional, Dict, Any, List, Union
8
+ from pydantic import Field, model_validator, BaseModel
9
+
10
+ from .utils import add_to_nested_path, post_process_nested_data, construct_model
11
+ from .salary import Salary # Import related models
12
+ from .address import Address
13
+ from .job import Job
14
+ from .leave import Leave
15
+ from .termination import Termination
16
+ from .generated_xsd_schemas.hrxml_indicative_data import IndicativeDataType
17
+
18
+
19
+ class EmployeeCreate(BaseModel):
20
+ """
21
+ Comprehensive employee model for Alight integration.
22
+ Automatically converts to complex nested HR-XML structure using pure schema introspection.
23
+ """
24
+ model_config = {
25
+ "populate_by_name": True # Allow populating by field name in addition to alias
26
+ }
27
+
28
+ # Document Level Fields - Use aliases to map directly to XSD fields
29
+ document_id: Optional[str] = Field(
30
+ default=None,
31
+ description="Document identifier",
32
+ alias="document_id"
33
+ )
34
+ document_sequence: Optional[str] = Field(
35
+ default=None,
36
+ description="Document sequence number",
37
+ alias="document_sequence"
38
+ )
39
+ alternate_document_id: Optional[List[str]] = Field(
40
+ default=None,
41
+ description="Alternate document identifier (list in XSD)",
42
+ alias="alternate_document_id"
43
+ )
44
+
45
+ # Core Identity - Use aliases to map directly to XSD fields
46
+ person_id: str = Field(
47
+ description="Unique person identifier",
48
+ alias="indicative_person_dossier.indicative_person.person_id"
49
+ )
50
+ person_legal_id: Optional[str] = Field(
51
+ default=None,
52
+ description="Legal ID (SSN/NI number)",
53
+ alias="indicative_person_dossier.indicative_person.person_legal_id"
54
+ )
55
+ person_legal_id_scheme_name: Optional[str] = Field(
56
+ default=None,
57
+ description="Legal ID scheme (GB-NI/US-SSN)",
58
+ alias="indicative_person_dossier.indicative_person.person_legal_id.scheme_name"
59
+ )
60
+ employee_id: Optional[str] = Field(
61
+ default=None,
62
+ description="Employee ID (defaults to person_id)",
63
+ alias="indicative_person_dossier.indicative_employee.employee_id"
64
+ )
65
+ employee_group_code: Optional[str] = Field(
66
+ default=None,
67
+ description="Employee group classification",
68
+ alias="indicative_person_dossier.indicative_employee.employee_group_code"
69
+ )
70
+
71
+ # Name Information - Use aliases to map directly to XSD fields
72
+ given_name: Optional[str] = Field(
73
+ default=None,
74
+ description="First name",
75
+ alias="indicative_person_dossier.indicative_person.person_name.given_name"
76
+ )
77
+ middle_name: Optional[str] = Field(
78
+ default=None,
79
+ description="Middle name",
80
+ alias="indicative_person_dossier.indicative_person.person_name.middle_name"
81
+ )
82
+ preferred_name: Optional[str] = Field(
83
+ default=None,
84
+ description="Preferred or nick name",
85
+ alias="indicative_person_dossier.indicative_person.person_name.preferred_name"
86
+ )
87
+ family_name: Optional[str] = Field(
88
+ default=None,
89
+ description="Last name/surname",
90
+ alias="indicative_person_dossier.indicative_person.person_name.family_name"
91
+ )
92
+ former_family_name: Optional[str] = Field(
93
+ default=None,
94
+ description="Maiden name",
95
+ alias="indicative_person_dossier.indicative_person.person_name.former_family_name"
96
+ )
97
+ preferred_salutation_code: Optional[str] = Field(
98
+ default=None,
99
+ description="Mr/Ms/Dr etc.",
100
+ alias="indicative_person_dossier.indicative_person.person_name.preferred_salutation_code"
101
+ )
102
+ generation_affix_code: Optional[str] = Field(
103
+ default=None,
104
+ description="Generation suffix (Sr., Jr., III, etc.)",
105
+ alias="indicative_person_dossier.indicative_person.person_name.generation_affix_code"
106
+ )
107
+ qualification_affix_code: Optional[str] = Field(
108
+ default=None,
109
+ description="Qualification code (e.g., MBA, PhD)",
110
+ alias="indicative_person_dossier.indicative_person.person_name.qualification_affix_code"
111
+ )
112
+ title_affix_code: Optional[List[str]] = Field(
113
+ default=None,
114
+ description="Title affix codes (e.g., Lord, Sir)",
115
+ alias="indicative_person_dossier.indicative_person.person_name.title_affix_code"
116
+ )
117
+ person_name_initials: Optional[str] = Field(
118
+ default=None,
119
+ description="Initials derived from person name",
120
+ alias="indicative_person_dossier.indicative_person.person_name.person_name_initials"
121
+ )
122
+ formatted_name: Optional[str] = Field(
123
+ default=None,
124
+ description="Formatted display name",
125
+ alias="indicative_person_dossier.indicative_person.person_name.formatted_name"
126
+ )
127
+
128
+ # Personal Details - Use aliases to map directly to XSD fields
129
+ birth_date: Optional[datetime.date] = Field(
130
+ default=None,
131
+ description="Birth date",
132
+ alias="indicative_person_dossier.indicative_person.birth_date"
133
+ )
134
+ gender_code: Optional[str] = Field(
135
+ default=None,
136
+ description="Male/Female/Other",
137
+ alias="indicative_person_dossier.indicative_person.gender_code"
138
+ )
139
+ # Birth Place - nested under indicative_person.birth_place
140
+ birth_place_city_name: Optional[str] = Field(
141
+ default=None,
142
+ description="City of birth",
143
+ alias="indicative_person_dossier.indicative_person.birth_place.city_name"
144
+ )
145
+ birth_place_country_sub_division_code: Optional[str] = Field(
146
+ default=None,
147
+ description="State/region code of birth",
148
+ alias="indicative_person_dossier.indicative_person.birth_place.country_sub_division_code"
149
+ )
150
+ birth_place_country_code: Optional[str] = Field(
151
+ default=None,
152
+ description="Country code of birth",
153
+ alias="indicative_person_dossier.indicative_person.birth_place.country_code"
154
+ )
155
+ marital_status_code: Optional[str] = Field(
156
+ default=None,
157
+ description="Married/Unmarried/etc.",
158
+ alias="indicative_person_dossier.indicative_person.certified_marital_status.marital_status_code"
159
+ )
160
+ marital_status_certified_date: Optional[datetime.date] = Field(
161
+ default=None,
162
+ description="Certified marital status date",
163
+ alias="indicative_person_dossier.indicative_person.certified_marital_status.certified_date"
164
+ )
165
+ primary_language_code: Optional[str] = Field(
166
+ default=None,
167
+ description="Primary language code",
168
+ alias="indicative_person_dossier.indicative_person.primary_language_code"
169
+ )
170
+ citizenship_country_code: Optional[str] = Field(
171
+ default=None,
172
+ description="Citizenship country code",
173
+ alias="indicative_person_dossier.indicative_person.citizenship_country_code"
174
+ )
175
+
176
+ # Employment Status - Use aliases to map directly to XSD fields
177
+ employed_indicator: Optional[bool] = Field(
178
+ default=None,
179
+ description="Employment status indicator",
180
+ alias="indicative_person_dossier.indicative_employment.employed_indicator"
181
+ )
182
+ expected_duty_entry_date: Optional[datetime.date] = Field(
183
+ default=None,
184
+ description="Expected start date",
185
+ alias="indicative_person_dossier.indicative_employment.proposed_hire.expected_duty_entry_date"
186
+ )
187
+ hire_type_code: Optional[str] = Field(
188
+ default=None,
189
+ description="NewHire/Rehire/Transfer",
190
+ alias="indicative_person_dossier.indicative_employment.employment_lifecycle.hire.hire_type_code"
191
+ )
192
+ hire_date: Optional[datetime.date] = Field(
193
+ default=None,
194
+ description="Hire date",
195
+ alias="indicative_person_dossier.indicative_employment.employment_lifecycle.hire.hire_date"
196
+ )
197
+ original_hire_date: Optional[datetime.date] = Field(
198
+ default=None,
199
+ description="Original hire date",
200
+ alias="indicative_person_dossier.indicative_employment.employment_lifecycle.hire.original_hire_date"
201
+ )
202
+
203
+ # Contact Information - Use index-aware aliases to map items into communication list
204
+ mobile_phone: Optional[str] = Field(
205
+ default=None,
206
+ description="Mobile phone number",
207
+ alias="indicative_person_dossier.indicative_person.communication[0].dial_number"
208
+ )
209
+ mobile_phone_use_code: Optional[str] = Field(
210
+ default=None,
211
+ description="Mobile phone use context",
212
+ alias="indicative_person_dossier.indicative_person.communication[0].use_code"
213
+ )
214
+ mobile_phone_channel_code: Optional[str] = Field(
215
+ default=None,
216
+ description="Communication channel code for phone",
217
+ alias="indicative_person_dossier.indicative_person.communication[0].channel_code"
218
+ )
219
+ country_dialing: Optional[str] = Field(
220
+ default=None,
221
+ description="Country dialing code for phone",
222
+ alias="indicative_person_dossier.indicative_person.communication[0].country_dialing"
223
+ )
224
+ area_dialing: Optional[str] = Field(
225
+ default=None,
226
+ description="Area/region dialing code for phone",
227
+ alias="indicative_person_dossier.indicative_person.communication[0].area_dialing"
228
+ )
229
+ dial_number: Optional[str] = Field(
230
+ default=None,
231
+ description="Dialable phone number",
232
+ alias="indicative_person_dossier.indicative_person.communication[0].dial_number"
233
+ )
234
+ extension: Optional[str] = Field(
235
+ default=None,
236
+ description="Phone extension",
237
+ alias="indicative_person_dossier.indicative_person.communication[0].extension"
238
+ )
239
+ access: Optional[str] = Field(
240
+ default=None,
241
+ description="Phone access code",
242
+ alias="indicative_person_dossier.indicative_person.communication[0].access"
243
+ )
244
+
245
+ email: Optional[str] = Field(
246
+ default=None,
247
+ description="Email address",
248
+ alias="indicative_person_dossier.indicative_person.communication[1].uri"
249
+ )
250
+ email_use_code: Optional[str] = Field(
251
+ default=None,
252
+ description="Email use context",
253
+ alias="indicative_person_dossier.indicative_person.communication[1].use_code"
254
+ )
255
+ email_channel_code: Optional[str] = Field(
256
+ default=None,
257
+ description="Communication channel code for email",
258
+ alias="indicative_person_dossier.indicative_person.communication[1].channel_code"
259
+ )
260
+
261
+ # Relation to Address model
262
+ address: Optional[Address] = Field(default=None, description="Employee's home address")
263
+ address_valid_from: Optional[datetime.date] = Field(
264
+ default=None,
265
+ description="Valid from date applied to primary home address",
266
+ alias="indicative_person_dossier.indicative_person.communication[2].address.valid_from"
267
+ )
268
+ address_use_code: Optional[str] = Field(
269
+ default=None,
270
+ description="Address use context (HOME/WORK)",
271
+ alias="indicative_person_dossier.indicative_person.communication[2].use_code"
272
+ )
273
+ address_type: Optional[str] = Field(
274
+ default=None,
275
+ description="Address type attribute",
276
+ alias="indicative_person_dossier.indicative_person.communication[2].address.type_value"
277
+ )
278
+ # Flat person address fields (HOME address in communication[2])
279
+ line_1: Optional[str] = Field(
280
+ default=None,
281
+ description="Primary address line",
282
+ alias="indicative_person_dossier.indicative_person.communication[2].address.line_one"
283
+ )
284
+ city: Optional[str] = Field(
285
+ default=None,
286
+ description="City name",
287
+ alias="indicative_person_dossier.indicative_person.communication[2].address.city_name"
288
+ )
289
+ state_province: Optional[str] = Field(
290
+ default=None,
291
+ description="State/Province code",
292
+ alias="indicative_person_dossier.indicative_person.communication[2].address.country_sub_division_code"
293
+ )
294
+ postal_code: Optional[str] = Field(
295
+ default=None,
296
+ description="Postal code",
297
+ alias="indicative_person_dossier.indicative_person.communication[2].address.postal_code"
298
+ )
299
+ country: Optional[str] = Field(
300
+ default=None,
301
+ description="Country code",
302
+ alias="indicative_person_dossier.indicative_person.communication[2].address.country_code"
303
+ )
304
+ person_valid_from: Optional[datetime.date] = Field(
305
+ default=None,
306
+ description="Effective date for indicative person",
307
+ alias="indicative_person_dossier.indicative_person.valid_from"
308
+ )
309
+ employee_valid_from: Optional[datetime.date] = Field(
310
+ default=None,
311
+ description="Effective date for indicative employee",
312
+ alias="indicative_person_dossier.indicative_employee.valid_from"
313
+ )
314
+ employment_valid_from: Optional[datetime.date] = Field(
315
+ default=None,
316
+ description="Effective date for indicative employment",
317
+ alias="indicative_person_dossier.indicative_employment.valid_from"
318
+ )
319
+ employment_lifecycle_valid_from: Optional[datetime.date] = Field(
320
+ default=None,
321
+ description="Effective date for employment lifecycle",
322
+ alias="indicative_person_dossier.indicative_employment.employment_lifecycle.valid_from"
323
+ )
324
+ hire_valid_from: Optional[datetime.date] = Field(
325
+ default=None,
326
+ description="Effective date for hire node",
327
+ alias="indicative_person_dossier.indicative_employment.employment_lifecycle.hire.valid_from"
328
+ )
329
+ deployment_valid_from: Optional[datetime.date] = Field(
330
+ default=None,
331
+ description="Effective date for indicative deployment",
332
+ alias="indicative_person_dossier.indicative_deployment.valid_from"
333
+ )
334
+ pay_cycle_remuneration_valid_from: Optional[datetime.date] = Field(
335
+ default=None,
336
+ description="Effective date for pay cycle remuneration",
337
+ alias="indicative_person_dossier.pay_cycle_remuneration.valid_from"
338
+ )
339
+
340
+ # Employer/Organization Information - Use aliases to map directly to XSD fields
341
+ employer_organization_id: Optional[str] = Field(
342
+ default=None,
343
+ description="Employer organization ID (flat input; coerced to XSD list)",
344
+ alias="employer_identifiers.organization_id"
345
+ )
346
+ employer_organization_name: Optional[str] = Field(
347
+ default=None,
348
+ description="Employer organization name",
349
+ alias="employer_identifiers.organization_name"
350
+ )
351
+ employer_organization_tax_id: Optional[List[str]] = Field(
352
+ default=None,
353
+ description="Employer organization tax ID (list in XSD)",
354
+ alias="employer_identifiers.organization_tax_id"
355
+ )
356
+ employer_organization_legal_id: Optional[List[str]] = Field(
357
+ default=None,
358
+ description="Employer organization legal ID (list in XSD)",
359
+ alias="employer_identifiers.organization_legal_id"
360
+ )
361
+
362
+ # Deployment Organization - Use aliases to map directly to XSD fields
363
+ deployment_organization_id: Optional[str] = Field(
364
+ default=None,
365
+ description="Deployment organization ID",
366
+ alias="indicative_person_dossier.indicative_deployment.deployment_organization.organization_identifiers.organization_id"
367
+ )
368
+ deployment_organization_name: Optional[str] = Field(
369
+ default=None,
370
+ description="Deployment organization name",
371
+ alias="indicative_person_dossier.indicative_deployment.deployment_organization.organization_identifiers.organization_name"
372
+ )
373
+ deployment_organization_valid_from: Optional[datetime.date] = Field(
374
+ default=None,
375
+ description="Effective date for deployment organization",
376
+ alias="indicative_person_dossier.indicative_deployment.deployment_organization.valid_from"
377
+ )
378
+
379
+ # Work Location (flat) -> IndicativeDeployment.WorkLocation.Address
380
+ work_location_city_name: Optional[str] = Field(
381
+ default=None,
382
+ description="Work location city",
383
+ alias="indicative_person_dossier.indicative_deployment.work_location.address.city_name"
384
+ )
385
+ work_location_country_sub_division_code: Optional[str] = Field(
386
+ default=None,
387
+ description="Work location subdivision/state",
388
+ alias="indicative_person_dossier.indicative_deployment.work_location.address.country_sub_division_code"
389
+ )
390
+ work_location_postal_code: Optional[str] = Field(
391
+ default=None,
392
+ description="Work location postal code",
393
+ alias="indicative_person_dossier.indicative_deployment.work_location.address.postal_code"
394
+ )
395
+
396
+ # Legacy job fields (maintain backwards compatibility with flat payloads)
397
+ title: Optional[str] = Field(
398
+ default=None,
399
+ description="Job title",
400
+ alias="indicative_person_dossier.indicative_deployment.job.job_title"
401
+ )
402
+ department: Optional[str] = Field(
403
+ default=None,
404
+ description="Department name",
405
+ alias="indicative_person_dossier.indicative_deployment.department_name"
406
+ )
407
+ position_id: Optional[str] = Field(
408
+ default=None,
409
+ description="Position identifier",
410
+ alias="indicative_person_dossier.indicative_deployment.position_id"
411
+ )
412
+ position_title: Optional[str] = Field(
413
+ default=None,
414
+ description="Position title",
415
+ alias="indicative_person_dossier.indicative_deployment.position_title"
416
+ )
417
+ location_code: Optional[str] = Field(
418
+ default=None,
419
+ description="Work location identifier",
420
+ alias="indicative_person_dossier.indicative_deployment.work_location.location_id"
421
+ )
422
+ manager_id: Optional[str] = Field(
423
+ default=None,
424
+ description="Manager employee ID",
425
+ alias="indicative_person_dossier.indicative_deployment.manager_id.id"
426
+ )
427
+ weekly_hours: Optional[float] = Field(
428
+ default=None,
429
+ description="Weekly working hours",
430
+ alias="indicative_person_dossier.indicative_deployment.schedule.scheduled_hours[0].value"
431
+ )
432
+ weekly_hours_basis: Optional[str] = Field(
433
+ default="Week",
434
+ description="Schedule basis for weekly hours",
435
+ alias="indicative_person_dossier.indicative_deployment.schedule.scheduled_hours[0].schedule_basis"
436
+ )
437
+ pay_cycle_hours: Optional[float] = Field(
438
+ default=None,
439
+ description="Hours per pay cycle",
440
+ alias="indicative_person_dossier.indicative_deployment.schedule.scheduled_hours[1].value"
441
+ )
442
+ pay_cycle_hours_basis: Optional[str] = Field(
443
+ default=None,
444
+ description="Schedule basis for pay cycle hours",
445
+ alias="indicative_person_dossier.indicative_deployment.schedule.scheduled_hours[1].schedule_basis"
446
+ )
447
+ scheduled_days_per_week: Optional[float] = Field(
448
+ default=None,
449
+ description="Scheduled days per week",
450
+ alias="indicative_person_dossier.indicative_deployment.schedule.scheduled_days[0].value"
451
+ )
452
+ scheduled_days_basis: Optional[str] = Field(
453
+ default=None,
454
+ description="Schedule basis for scheduled days",
455
+ alias="indicative_person_dossier.indicative_deployment.schedule.scheduled_days[0].schedule_basis"
456
+ )
457
+ day_schedule_id: Optional[str] = Field(
458
+ default=None,
459
+ description="Day schedule ID",
460
+ alias="indicative_person_dossier.indicative_deployment.schedule.day_schedule[0].id"
461
+ )
462
+ fte_ratio: Optional[str] = Field(
463
+ default=None,
464
+ description="Full-time equivalent ratio",
465
+ alias="indicative_person_dossier.indicative_deployment.full_time_equivalent_ratio"
466
+ )
467
+ work_level_code: Optional[str] = Field(
468
+ default="FullTime",
469
+ description="Work level code",
470
+ alias="indicative_person_dossier.indicative_deployment.work_level_code"
471
+ )
472
+
473
+ # Work Location and Job Information fields removed - these belong in Job model
474
+ # Access these through the job relation
475
+
476
+ # Relation to Job model (replaces job fields)
477
+ job: Optional[Job] = Field(default=None, description="Employee's job information")
478
+ leave: Optional[Leave] = Field(default=None, description="Employee's leave information")
479
+ termination: Optional[Termination] = Field(default=None, description="Employee's termination information")
480
+
481
+ # Schedule Information and Work Level fields removed - these belong in Job model
482
+ # Access these through the job relation
483
+
484
+ # Pay Information - Use aliases to map directly to XSD fields
485
+ pay_group_code: Optional[str] = Field(
486
+ default=None,
487
+ description="Pay group classification",
488
+ alias="indicative_person_dossier.pay_cycle_remuneration.pay_group_code"
489
+ )
490
+
491
+ # Payment Instructions - Use aliases to map directly to XSD fields
492
+ payment_type: Optional[str] = Field(
493
+ default=None,
494
+ description="Payment type (MAIN/etc.)",
495
+ alias="payment_instruction.payment_type"
496
+ )
497
+ payment_method: Optional[str] = Field(
498
+ default=None,
499
+ description="Payment method (BANK_DOM/etc.)",
500
+ alias="payment_instruction.payment_method"
501
+ )
502
+ account_type_code: Optional[str] = Field(
503
+ default=None,
504
+ description="Account type (DDA/etc.)",
505
+ alias="payment_instruction.account_type_code"
506
+ )
507
+ name_on_account: Optional[str] = Field(
508
+ default=None,
509
+ description="Name on bank account",
510
+ alias="payment_instruction.name_on_account"
511
+ )
512
+ bank_routing_id: Optional[str] = Field(
513
+ default=None,
514
+ description="Bank routing number",
515
+ alias="payment_instruction.bank_routing_id"
516
+ )
517
+ account_id: Optional[str] = Field(
518
+ default=None,
519
+ description="Bank account number",
520
+ alias="payment_instruction.account_id"
521
+ )
522
+ iban: Optional[str] = Field(
523
+ default=None,
524
+ description="IBAN",
525
+ alias="payment_instruction.iban"
526
+ )
527
+ bank_country_code: Optional[str] = Field(
528
+ default=None,
529
+ description="Bank country code",
530
+ alias="payment_instruction.bank_country_code"
531
+ )
532
+
533
+ # Relation to Salary model
534
+ salary: Optional[Salary] = Field(default=None, description="Employee's salary information")
535
+
536
+ # Legacy Pay Elements - Use aliases to map directly to XSD fields
537
+ pay_element_id: Optional[str] = Field(
538
+ default=None,
539
+ description="Pay element ID",
540
+ alias="indicative_person_dossier.pay_cycle_remuneration.remuneration.pay_element.id"
541
+ )
542
+ pay_element_type: Optional[str] = Field(
543
+ default=None,
544
+ description="Pay element type",
545
+ alias="indicative_person_dossier.pay_cycle_remuneration.remuneration.pay_element.type_code"
546
+ )
547
+ pay_amount: Optional[str] = Field(
548
+ default=None,
549
+ description="Pay amount",
550
+ alias="indicative_person_dossier.pay_cycle_remuneration.remuneration.amount.value"
551
+ )
552
+ pay_currency_code: Optional[str] = Field(
553
+ default=None,
554
+ description="Pay currency code",
555
+ alias="indicative_person_dossier.pay_cycle_remuneration.remuneration.amount.currency_code"
556
+ )
557
+
558
+ # Common Dates - Use aliases to map to specific XSD fields
559
+ valid_from: Optional[datetime.date] = Field(
560
+ default=None,
561
+ description="Valid from date",
562
+ # Map to specific fields in the XSD schema
563
+ alias="indicative_person_dossier.valid_from"
564
+ )
565
+
566
+ # Additional fields - Use aliases to map directly to XSD fields
567
+ compensation_change_reason: Optional[str] = Field(
568
+ default=None,
569
+ description="Reason for compensation change",
570
+ alias="indicative_person_dossier.pay_cycle_remuneration.remuneration.compensation_change_reason"
571
+ )
572
+ effective_date: Optional[datetime.date] = Field(
573
+ default=None,
574
+ description="Effective date for changes",
575
+ alias="indicative_person_dossier.indicative_deployment.effective_date"
576
+ )
577
+
578
+ def to_nested_dict(self) -> Dict[str, Any]:
579
+ """
580
+ Convert flat employee data to nested HR-XML structure using PURE schema-driven conversion.
581
+ Uses alias dot-paths expanded to nested dict to align with XSD structure.
582
+ """
583
+ if IndicativeDataType is None:
584
+ raise ImportError("IndicativeDataType not available - cannot perform schema-driven conversion")
585
+
586
+ # Dump current model using aliases to get dot-path keys
587
+ flat_alias: Dict[str, Any] = self.model_dump(
588
+ exclude={"salary", "address", "job"}, exclude_none=True, by_alias=True
589
+ )
590
+ # print("[EMP] flat_alias keys:", list(flat_alias.keys())[:40])
591
+
592
+ # Clean up orphan schedule_basis fields when corresponding value fields are None
593
+ # This prevents malformed ScheduledHours objects missing required attributes
594
+ schedule_paths = [
595
+ ('indicative_person_dossier.indicative_deployment.schedule.scheduled_hours[0].value',
596
+ 'indicative_person_dossier.indicative_deployment.schedule.scheduled_hours[0].schedule_basis'),
597
+ ('indicative_person_dossier.indicative_deployment.schedule.scheduled_hours[1].value',
598
+ 'indicative_person_dossier.indicative_deployment.schedule.scheduled_hours[1].schedule_basis'),
599
+ ('indicative_person_dossier.indicative_deployment.schedule.scheduled_days[0].value',
600
+ 'indicative_person_dossier.indicative_deployment.schedule.scheduled_days[0].schedule_basis'),
601
+ ]
602
+ for value_path, basis_path in schedule_paths:
603
+ # If value is missing but basis exists, remove the basis to avoid malformed objects
604
+ if value_path not in flat_alias and basis_path in flat_alias:
605
+ del flat_alias[basis_path]
606
+
607
+ # Minimal business logic
608
+ if not flat_alias.get('indicative_person_dossier.indicative_employee.employee_id') and self.person_id:
609
+ # Mirror employee_id default to person_id if missing
610
+ flat_alias['indicative_person_dossier.indicative_employee.employee_id'] = self.person_id
611
+
612
+ # Merge related models (they already dump with by_alias in their to_nested_dict)
613
+ if self.salary is not None:
614
+ for k, v in (self.salary.to_nested_dict() or {}).items():
615
+ flat_alias[k] = v
616
+ if self.address is not None:
617
+ for k, v in (self.address.to_nested_dict() or {}).items():
618
+ flat_alias[k] = v
619
+ if self.job is not None:
620
+ job_data = self.job.to_nested_dict() or {}
621
+ for k, v in job_data.items():
622
+ flat_alias[k] = v
623
+ if self.leave is not None:
624
+ for k, v in (self.leave.to_nested_dict() or {}).items():
625
+ flat_alias[k] = v
626
+ if self.termination is not None:
627
+ for k, v in (self.termination.to_nested_dict() or {}).items():
628
+ flat_alias[k] = v
629
+
630
+ # No ad-hoc communication aggregation; indices in aliases will build lists
631
+ # Delegate to generic converter (now dot-path aware)
632
+ # Build nested structure from dot-path aliases generically
633
+ nested: Dict[str, Any] = {}
634
+ for k, v in flat_alias.items():
635
+ add_to_nested_path(nested, k, v)
636
+
637
+ post_process_nested_data(nested, IndicativeDataType)
638
+ # print("[EMP] nested top-level keys:", list(nested.keys()))
639
+ return nested
640
+
641
+ def to_model(self) -> IndicativeDataType:
642
+ """
643
+ Convert flat employee data directly to validated IndicativeDataType model.
644
+ Uses dot-separated aliases to map fields directly to the right places in the schema.
645
+
646
+ Returns:
647
+ IndicativeDataType: A validated model ready for XML serialization
648
+ """
649
+ # Get the nested structure with only non-empty fields
650
+ nested_data = self.to_nested_dict()
651
+
652
+ try:
653
+ return construct_model(IndicativeDataType, nested_data)
654
+ except Exception:
655
+ # Fallback to full validation if fast-path construction fails
656
+ return IndicativeDataType.model_validate(nested_data)