brynq-sdk-meta4 1.0.1__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_meta4/__init__.py +1 -0
- brynq_sdk_meta4/cost_centers.py +74 -0
- brynq_sdk_meta4/employees.py +121 -0
- brynq_sdk_meta4/jobs.py +120 -0
- brynq_sdk_meta4/meta4.py +165 -0
- brynq_sdk_meta4/schemas/__init__.py +9 -0
- brynq_sdk_meta4/schemas/cost_center.py +39 -0
- brynq_sdk_meta4/schemas/employee.py +225 -0
- brynq_sdk_meta4/schemas/job.py +59 -0
- brynq_sdk_meta4/schemas/reference_enums.py +624 -0
- brynq_sdk_meta4-1.0.1.dist-info/METADATA +17 -0
- brynq_sdk_meta4-1.0.1.dist-info/RECORD +14 -0
- brynq_sdk_meta4-1.0.1.dist-info/WHEEL +5 -0
- brynq_sdk_meta4-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
2
|
+
from typing import Optional, Literal
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from .reference_enums import (
|
|
5
|
+
MovementType,
|
|
6
|
+
DocumentType,
|
|
7
|
+
GenderType,
|
|
8
|
+
MaritalStatusType,
|
|
9
|
+
StreetType,
|
|
10
|
+
HoursType,
|
|
11
|
+
PartTimeType,
|
|
12
|
+
SSNumberType,
|
|
13
|
+
YesNoType,
|
|
14
|
+
SalaryType,
|
|
15
|
+
AdjustmentType,
|
|
16
|
+
PaymentType,
|
|
17
|
+
IRPFType,
|
|
18
|
+
IRPFStatusType,
|
|
19
|
+
DisabilityHRType,
|
|
20
|
+
ManagementType,
|
|
21
|
+
PerceptionKeyType,
|
|
22
|
+
ReductionReasonType,
|
|
23
|
+
TerminationReasonType,
|
|
24
|
+
UnemploymentCauseType,
|
|
25
|
+
ContractType,
|
|
26
|
+
LaborRelationshipType,
|
|
27
|
+
SubstitutionCauseType
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class EmployeeSchema(BaseModel):
|
|
32
|
+
"""
|
|
33
|
+
Pydantic schema for Meta4 Employee data.
|
|
34
|
+
Based on EQU_Interfaz_Empleados v.0 specification.
|
|
35
|
+
"""
|
|
36
|
+
# Movement and Dates
|
|
37
|
+
movement_type: MovementType = Field(description="Tipo de movimiento", example=MovementType.ALTA, alias="TIPO DE MOVIMIENTO")
|
|
38
|
+
effective_date: datetime = Field(description="Fecha efectividad", example="2024-01-01", alias="FECHA EFECTIVIDAD")
|
|
39
|
+
termination_date: Optional[datetime] = Field(None, description="Fecha de baja", example="2024-12-31", alias="FECHA DE BAJA")
|
|
40
|
+
termination_reason: Optional[TerminationReasonType] = Field(None, description="Motivo de baja", example=TerminationReasonType.MOTIVOS_ECONOMICOS, alias="MOTIVO DE BAJA")
|
|
41
|
+
unemployment_cause: Optional[UnemploymentCauseType] = Field(None, description="Causa desempleo", example=UnemploymentCauseType.WORKER_DISMISSAL, alias="CAUSA DESEMPLEO")
|
|
42
|
+
vacation_end_date: Optional[datetime] = Field(None, description="Fecha fin vacaciones", example="2024-08-31", alias="FECHA FIN VACACIONES")
|
|
43
|
+
|
|
44
|
+
# Personal Information
|
|
45
|
+
person_id: Optional[str] = Field(None, description="ID Persona", example="EMP001", alias="ID PERSONA")
|
|
46
|
+
first_surname: Optional[str] = Field(None, description="Apellido primero", example="García", alias="APELLIDO PRIMERO")
|
|
47
|
+
second_surname: Optional[str] = Field(None, description="Apellido segundo", example="López", alias="APELLIDO SEGUNDO")
|
|
48
|
+
employee_name: Optional[str] = Field(None, description="Nombre empleado", example="Juan Carlos", alias="NOMBRE EMPLEADO")
|
|
49
|
+
document_type: Optional[DocumentType] = Field(None, description="Tipo documento", example=DocumentType.DNI, alias="TIPO DOCUMENTO")
|
|
50
|
+
document_number: Optional[str] = Field(None, description="Número documento", example="12345678A", alias="NÚM. DOCUMENTO")
|
|
51
|
+
document_country: Optional[str] = Field(None, description="País emisor documento", example="724", alias="PAÍS EMISOR DOCUMENTO")
|
|
52
|
+
birth_date: Optional[datetime] = Field(None, description="Fecha de nacimiento", example="1985-03-15", alias="FECHA DE NACIMIENTO")
|
|
53
|
+
nationality: Optional[str] = Field(None, description="Nacionalidad", example="724", alias="NACIONALIDAD")
|
|
54
|
+
birth_country: Optional[str] = Field(None, description="País nacimiento", example="724", alias="PAÍS NACIMIENTO")
|
|
55
|
+
birth_province: Optional[str] = Field(None, description="Provincia nacimiento", example="", alias="PROVINCIA NACIMIENTO")
|
|
56
|
+
birth_community: Optional[str] = Field(None, description="Comunidad nacimiento", example="", alias="COMUNIDAD NACIMIENTO")
|
|
57
|
+
gender: Optional[GenderType] = Field(None, description="Sexo", example=GenderType.MALE, alias="SEXO")
|
|
58
|
+
marital_status: Optional[MaritalStatusType] = Field(None, description="Estado civil", example=MaritalStatusType.SINGLE, alias="ESTADO CIVIL")
|
|
59
|
+
phone: Optional[str] = Field(None, description="Teléfono", example="912345678", alias="TELÉFONO")
|
|
60
|
+
mobile_phone: Optional[str] = Field(None, description="Teléfono móvil", example="612345678", alias="TELEF. MÓVIL")
|
|
61
|
+
email: Optional[str] = Field(None, description="Correo electrónico", example="juan.garcia@empresa.com", alias="CORREO ELECTRÓNICO")
|
|
62
|
+
|
|
63
|
+
# Address Information
|
|
64
|
+
location_type: Optional[Literal["1"]] = Field(None, description="Tipo localización", example="1", alias="TIPO LOCALIZACIÓN")
|
|
65
|
+
street_type: Optional[StreetType] = Field(None, description="Tipo vía", example=StreetType.CALLE, alias="TIPO VÍA")
|
|
66
|
+
send_mail: Optional[Literal["0"]] = Field(None, description="Enviar correo", example="0", alias="ENVIAR CORREO")
|
|
67
|
+
employee_address: Optional[str] = Field(None, description="Dirección del empleado", example="Calle Mayor", alias="DIRECCIÓN DEL EMPLEADO")
|
|
68
|
+
street_number: Optional[int] = Field(None, description="Número", example=123, alias="NÚMERO")
|
|
69
|
+
building: Optional[str] = Field(None, description="Bloque", example="A", alias="BLOQUE")
|
|
70
|
+
staircase: Optional[str] = Field(None, description="Escalera", example="1", alias="ESCALERA")
|
|
71
|
+
floor: Optional[str] = Field(None, description="Piso", example="2", alias="PISO")
|
|
72
|
+
door: Optional[str] = Field(None, description="Puerta", example="A", alias="PUERTA")
|
|
73
|
+
country: Optional[str] = Field(None, description="País", example="", alias="PAÍS")
|
|
74
|
+
city: Optional[str] = Field(None, description="Población", example="", alias="POBLACIÓN")
|
|
75
|
+
province: Optional[str] = Field(None, description="Provincia", example="", alias="PROVINCIA")
|
|
76
|
+
community: Optional[str] = Field(None, description="Comunidad", example="", alias="COMUNIDAD")
|
|
77
|
+
postal_code: Optional[str] = Field(None, description="Código postal", example="28001", alias="CÓDIGO POSTAL")
|
|
78
|
+
|
|
79
|
+
# Employment Information
|
|
80
|
+
company: Optional[str] = Field(None, description="Empresa", example="EMPRESA001", alias="EMPRESA")
|
|
81
|
+
job: Optional[str] = Field(None, description="Puesto", example="ANALISTA", alias="PUESTO")
|
|
82
|
+
position: Optional[str] = Field(None, description="Posición", example="", alias="POSICION")
|
|
83
|
+
organizational_unit: Optional[str] = Field(None, description="Unidad organizativa", example="IT", alias="UNIDAD ORGANIZATIVA")
|
|
84
|
+
work_location: Optional[str] = Field(None, description="Lugar trabajo", example="MADRID", alias="LUGAR TRABAJO")
|
|
85
|
+
cost_center: Optional[str] = Field(None, description="Centro de costo", example="CC001", alias="CENTRO DE COSTO")
|
|
86
|
+
start_reason: Optional[Literal["001"]] = Field(None, description="Motivo inicio", example="001", alias="MOTIVO INICIO")
|
|
87
|
+
employee_type: Optional[str] = Field(None, description="Tipo empleado", example="FIJO", alias="TIPO EMPLEADO")
|
|
88
|
+
probation_end_date: Optional[datetime] = Field(None, description="Fin periodo prueba", example="2024-03-31", alias="FIN PERIODO PRUEBA")
|
|
89
|
+
expected_end_date: Optional[datetime] = Field(None, description="Finalización prevista", example="2024-12-31", alias="FINALIZACIÓN PREVISTA")
|
|
90
|
+
duration: Optional[int] = Field(None, description="Duración", example=12, alias="DURACIÓN")
|
|
91
|
+
|
|
92
|
+
# Social Security Information
|
|
93
|
+
has_ss_number: Optional[SSNumberType] = Field(None, description="Con/sin num. S.S.", example=SSNumberType.WITH_SS, alias="CON/SIN NUM. S.S.")
|
|
94
|
+
ss_province: Optional[str] = Field(None, description="Núm S.S. (provincia)", example="28", alias="NÚM S.S. (PROVINCIA)")
|
|
95
|
+
ss_number: Optional[str] = Field(None, description="Núm S.S. (número)", example="12345678", alias="NÚM S.S. (NÚMERO)")
|
|
96
|
+
ss_check_digit: Optional[str] = Field(None, description="Núm S.S. (D.C.)", example="12", alias="NÚM S.S. (D.C.)")
|
|
97
|
+
ss_header: Optional[str] = Field(None, description="Cabecera TC1", example="TC1", alias="CABECERA TC1")
|
|
98
|
+
tariff_group: Optional[str] = Field(None, description="Grupo de tarifa", example="01", alias="GRUPO DE TARIFA")
|
|
99
|
+
occupation: Optional[str] = Field(None, description="Ocupación", example="", alias="OCUPACIÓN")
|
|
100
|
+
ss_agreement: Optional[str] = Field(None, description="Convenio S.S.", example="", alias="CONVENIO S.S.")
|
|
101
|
+
multi_employee_number: Optional[str] = Field(None, description="Número pluriempleado", example="", alias="NÚMERO PLURIEMPLEADO")
|
|
102
|
+
legal_contract: Optional[str] = Field(None, description="Contrato legal", example="", alias="CONTRATO LEGAL")
|
|
103
|
+
internal_contract: Optional[ContractType] = Field(None, description="Contrato interno", example=ContractType.REGULAR_INDEFINITE_FULL_TIME, alias="CONTRATO INTERNO")
|
|
104
|
+
contract_end_date: Optional[datetime] = Field(None, description="Fecha fin contrato", example="2024-12-31", alias="FECHA FIN CONTRATO")
|
|
105
|
+
labor_relationship: Optional[LaborRelationshipType] = Field(None, description="Relación laboral", example=LaborRelationshipType.HIGH_MANAGEMENT_PERSONNEL, alias="RELACIÓN LABORAL")
|
|
106
|
+
part_time_percentage: Optional[float] = Field(None, ge=0, le=100, description="% Jornada parcial", example=50.0, alias="% JORNADA PARCIAL")
|
|
107
|
+
hours_type: Optional[HoursType] = Field(None, description="Tipo horas", example=HoursType.WEEKLY, alias="TIPO HORAS")
|
|
108
|
+
hours_number: Optional[int] = Field(None, description="Número horas", example=40, alias="NÚMERO HORAS")
|
|
109
|
+
part_time_type: Optional[PartTimeType] = Field(None, description="Tipo jornada parcial", example=PartTimeType.REGULAR, alias="TIPO JORNADA PARCIAL")
|
|
110
|
+
work_days_weekly: Optional[str] = Field(None, description="Días trabajo semanales", example="5", alias="DÍAS TRABAJO SEMANALES")
|
|
111
|
+
|
|
112
|
+
# Reduction Information
|
|
113
|
+
reduction_percentage: Optional[str] = Field(None, description="% Reducción", example="25", alias="% REDUCCIÓN")
|
|
114
|
+
reduction_reason: Optional[ReductionReasonType] = Field(None, description="Motivo reducción", example=ReductionReasonType.CARE_OF_MINOR, alias="MOTIVO REDUCCIÓN")
|
|
115
|
+
substitution_cause: Optional[SubstitutionCauseType] = Field(None, description="Causa sustitución", example=SubstitutionCauseType.SUBSTITUTION_LEAVE_FAMILY_CARE, alias="CAUSA SUSTITUCIÓN")
|
|
116
|
+
substituted_ss_province: Optional[str] = Field(None, description="Núm S.S. del sustituido (provincia)", example="", alias="NÚM S.S. DEL SUSTITUIDO (PROVINCIA)")
|
|
117
|
+
substituted_ss_number: Optional[str] = Field(None, description="Núm S.S. del sustituido (número)", example="", alias="NÚM S.S. DEL SUSTITUIDO (NÚMERO)")
|
|
118
|
+
substituted_ss_check_digit: Optional[str] = Field(None, description="Núm S.S. del sustituido (D.C.)", example="", alias="NÚM S.S. DEL SUSTITUIDO (D.C.)")
|
|
119
|
+
disability_percentage: Optional[float] = Field(None, ge=0, le=100, description="% Minusvalía", example=33.0, alias="% MINUSVALÍA")
|
|
120
|
+
old_contract_start: Optional[datetime] = Field(None, description="Inicio antiguo contrato", example="2020-01-01", alias="INICIO ANTIGUO CONTRATO")
|
|
121
|
+
woman_maternity_24_months: Optional[YesNoType] = Field(None, description="Mujer mater. 24 meses", example=YesNoType.NO, alias="MUJER MATER. 24 MESES")
|
|
122
|
+
woman_underrepresented: Optional[YesNoType] = Field(None, description="Mujer subrepresentada", example=YesNoType.NO, alias="MUJER SUBREPRESENTADA")
|
|
123
|
+
probation_days: Optional[int] = Field(None, description="Días de prueba", example=30, alias="DÍAS DE PRUEBA")
|
|
124
|
+
additional_clause: Optional[str] = Field(None, description="Cláusula adicional", example="SC", alias="CLÁUSULA ADICIONAL")
|
|
125
|
+
adjustment_type: Optional[AdjustmentType] = Field(None, description="Tipo de ajuste", example=AdjustmentType.WITH_ADJUSTMENT, alias="TIPO DE AJUSTE")
|
|
126
|
+
agreement: Optional[str] = Field(None, description="Convenio", example="CONV001", alias="CONVENIO")
|
|
127
|
+
category: Optional[str] = Field(None, description="Categoría", example="TECNICO", alias="CATEGORÍA")
|
|
128
|
+
annual_gross: Optional[float] = Field(None, ge=0, description="Bruto anual", example=30000.0, alias="BRUTO ANUAL")
|
|
129
|
+
salary_type: Optional[SalaryType] = Field(None, description="Tipo salario", example=SalaryType.MONTHLY, alias="TIPO SALARIO")
|
|
130
|
+
seniority_date: Optional[datetime] = Field(None, description="Fecha antigüedad", example="2020-01-01", alias="FECHA ANTIGÜEDAD")
|
|
131
|
+
extras_date: Optional[datetime] = Field(None, description="Fecha extras", example="2024-01-01", alias="FECHA EXTRAS")
|
|
132
|
+
union: Optional[str] = Field(None, description="Sindicato", example="", alias="SINDICATO")
|
|
133
|
+
irpf_type: Optional[IRPFType] = Field(None, description="Tipo IRPF", example=IRPFType.NATIONAL, alias="TIPO IRPF")
|
|
134
|
+
irpf_status: Optional[IRPFStatusType] = Field(None, description="Estado IRPF", example=IRPFStatusType.SINGLE_WIDOWED_DIVORCED_SEPARATED_WITH_MINORS, alias="ESTADO IRPF")
|
|
135
|
+
disability_hr: Optional[DisabilityHRType] = Field(None, description="Minusvalía RH", example="", alias="MINUSVALÍA RH")
|
|
136
|
+
payment_type: Optional[PaymentType] = Field(None, description="Tipo pago", example=PaymentType.BANK_TRANSFER, alias="TIPO PAGO")
|
|
137
|
+
company_bank: Optional[str] = Field(None, description="Banco empresa", example="BANK001", alias="BANCO EMPRESA")
|
|
138
|
+
bank_branch: Optional[str] = Field(None, description="Banco + sucursal", example="BRANCH001", alias="BANCO + SUCURSAL")
|
|
139
|
+
account_number: Optional[str] = Field(None, description="Núm. cuenta", example="12345678901234567890", alias="NÚM. CUENTA")
|
|
140
|
+
check_digit: Optional[str] = Field(None, description="D.C.", example="12", alias="D.C.")
|
|
141
|
+
activity_type: Optional[str] = Field(None, description="Tipo actividad", example="ACT001", alias="TIPO ACTIVIDAD")
|
|
142
|
+
producer_type: Optional[str] = Field(None, description="Tipo productor", example="", alias="TIPO PRODUCTOR")
|
|
143
|
+
management_type: Optional[ManagementType] = Field(None, description="Tipo gestión", example=ManagementType.UNICA, alias="TIPO GESTION")
|
|
144
|
+
professional_group: Optional[str] = Field(None, description="Grupo profesional", example="", alias="GRUPO PROFESIONAL")
|
|
145
|
+
professional_level: Optional[str] = Field(None, description="Nivel profesional", example="", alias="NIVEL PROFESIONAL")
|
|
146
|
+
project: Optional[str] = Field(None, description="Proyecto", example="", alias="PROYECTO")
|
|
147
|
+
team: Optional[str] = Field(None, description="Equipo", example="", alias="EQUIPO")
|
|
148
|
+
product: Optional[str] = Field(None, description="Producto", example="", alias="PRODUCTO")
|
|
149
|
+
function: Optional[str] = Field(None, description="Función", example="", alias="FUNCION")
|
|
150
|
+
organizational_subgroup: Optional[str] = Field(None, description="Subgrupo organizativo", example="", alias="SUBGRUPO ORGANIZATIVO")
|
|
151
|
+
department: Optional[str] = Field(None, description="Departamento", example="", alias="DEPARTAMENTO")
|
|
152
|
+
region: Optional[str] = Field(None, description="Región", example="", alias="REGION")
|
|
153
|
+
country_region: Optional[str] = Field(None, description="País", example="", alias="PAIS")
|
|
154
|
+
territory: Optional[str] = Field(None, description="Territorio", example="", alias="TERRITORIO")
|
|
155
|
+
perception_key: Optional[PerceptionKeyType] = Field(None, description="Clave percepción", example=PerceptionKeyType.A, alias="CLAVE PERCEPCIÓN")
|
|
156
|
+
external_employee_id: Optional[str] = Field(None, description="ID empleado externo", example="", alias="ID EMPLEADO EXTERNO")
|
|
157
|
+
|
|
158
|
+
# -------- DATETIME PARSE --------
|
|
159
|
+
@field_validator(
|
|
160
|
+
"effective_date", "termination_date", "vacation_end_date",
|
|
161
|
+
"birth_date", "probation_end_date", "expected_end_date",
|
|
162
|
+
"old_contract_start", "seniority_date", "extras_date",
|
|
163
|
+
mode="before"
|
|
164
|
+
)
|
|
165
|
+
def parse_dd_mm_yyyy(cls, v):
|
|
166
|
+
if v in (None, "") or isinstance(v, datetime):
|
|
167
|
+
return v
|
|
168
|
+
return datetime.strptime(v, "%d/%m/%Y")
|
|
169
|
+
|
|
170
|
+
# -------- MOVEMENT TYPE VALIDATION --------
|
|
171
|
+
@model_validator(mode='after')
|
|
172
|
+
def validate_movement_requirements(self):
|
|
173
|
+
"""
|
|
174
|
+
Validate mandatory fields based on movement type.
|
|
175
|
+
"""
|
|
176
|
+
if self.movement_type == MovementType.MODIFICACION:
|
|
177
|
+
# For MODIFICACION (3), only effective_date and person_id are mandatory
|
|
178
|
+
mandatory_fields_modificacion = [
|
|
179
|
+
'effective_date', 'person_id'
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
for field_name in mandatory_fields_modificacion:
|
|
183
|
+
field_value = getattr(self, field_name, None)
|
|
184
|
+
if field_value is None or field_value == "":
|
|
185
|
+
raise ValueError(f"{field_name} is mandatory for MODIFICACION movement")
|
|
186
|
+
|
|
187
|
+
elif self.movement_type == MovementType.BAJA:
|
|
188
|
+
# For BAJA (2), termination_date and termination_reason are mandatory
|
|
189
|
+
mandatory_fields_baja = [
|
|
190
|
+
'effective_date', 'termination_date', 'termination_reason',
|
|
191
|
+
'unemployment_cause', 'person_id'
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
for field_name in mandatory_fields_baja:
|
|
195
|
+
field_value = getattr(self, field_name, None)
|
|
196
|
+
if field_value is None or field_value == "":
|
|
197
|
+
raise ValueError(f"{field_name} is mandatory for BAJA movement")
|
|
198
|
+
|
|
199
|
+
elif self.movement_type == MovementType.ALTA:
|
|
200
|
+
# For ALTA (1), all basic employee information is mandatory
|
|
201
|
+
mandatory_fields_alta = [
|
|
202
|
+
'effective_date', 'person_id', 'first_surname', 'second_surname',
|
|
203
|
+
'employee_name', 'document_type', 'document_number', 'birth_date',
|
|
204
|
+
'nationality', 'birth_country', 'gender', 'location_type', 'street_type',
|
|
205
|
+
'employee_address', 'street_number', 'postal_code', 'company', 'job',
|
|
206
|
+
'organizational_unit', 'work_location', 'cost_center', 'start_reason',
|
|
207
|
+
'employee_type', 'has_ss_number', 'ss_province', 'ss_number',
|
|
208
|
+
'ss_check_digit', 'internal_contract', 'reduction_percentage',
|
|
209
|
+
'reduction_reason', 'additional_clause', 'adjustment_type', 'agreement',
|
|
210
|
+
'category', 'salary_type', 'irpf_type', 'irpf_status', 'payment_type',
|
|
211
|
+
'company_bank', 'bank_branch', 'account_number', 'check_digit',
|
|
212
|
+
'management_type', 'perception_key'
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
for field_name in mandatory_fields_alta:
|
|
216
|
+
field_value = getattr(self, field_name, None)
|
|
217
|
+
if field_value is None or field_value == "":
|
|
218
|
+
raise ValueError(f"{field_name} is mandatory for ALTA movement")
|
|
219
|
+
|
|
220
|
+
return self
|
|
221
|
+
|
|
222
|
+
class Config:
|
|
223
|
+
json_encoders = {datetime: lambda d: d.strftime("%d/%m/%Y")}
|
|
224
|
+
allow_population_by_field_name = True
|
|
225
|
+
populate_by_name = True
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Meta4 Job schema based on EQU_Interfaz_Puestos specification.
|
|
3
|
+
|
|
4
|
+
This schema defines the structure for job data that will be exported to Meta4 HR system.
|
|
5
|
+
The schema includes movement type, job information, and date fields with proper formatting.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
10
|
+
from typing import Optional, Literal
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JobSchema(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
Pydantic schema for Meta4 Job data.
|
|
17
|
+
Based on EQU_Interfaz_Puestos specification.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Movement Information
|
|
21
|
+
movement_type: Literal["-28", "-29"] = Field(description="Tipo de movimiento", example="-28", alias="Tipo de movimiento", max_length=3)
|
|
22
|
+
|
|
23
|
+
# Job Information
|
|
24
|
+
job_id: Optional[str] = Field(None, description="ID Puesto", example="100", alias="ID Puesto", max_length=8)
|
|
25
|
+
job_name: Optional[str] = Field(None, description="Nombre Puesto", example="Puesto 1", alias="Nombre Puesto", max_length=62)
|
|
26
|
+
start_date: Optional[datetime] = Field(None, description="Fecha inicio", example="01/01/1800", alias="Fecha Inicio", max_length=10)
|
|
27
|
+
end_date: Optional[datetime] = Field(None, description="Fecha fin", example="01/01/180", alias="Fecha Fin ", max_length=10)
|
|
28
|
+
cno_subcode: Optional[str] = Field(None, description="ID Subcódigo CNO del Puesto", example="1120", alias="CNO", max_length=4)
|
|
29
|
+
|
|
30
|
+
@model_validator(mode='after')
|
|
31
|
+
def validate_movement_requirements(self):
|
|
32
|
+
"""Validate mandatory fields based on movement_type"""
|
|
33
|
+
if self.movement_type == "-28":
|
|
34
|
+
# All fields are mandatory for CREATE (-28)
|
|
35
|
+
mandatory_fields_create = ['job_id', 'job_name', 'start_date', 'end_date', 'cno_subcode']
|
|
36
|
+
for field_name in mandatory_fields_create:
|
|
37
|
+
field_value = getattr(self, field_name, None)
|
|
38
|
+
if field_value is None or field_value == "":
|
|
39
|
+
raise ValueError(f"{field_name} is mandatory for CREATE (-28) movement")
|
|
40
|
+
elif self.movement_type == "-29":
|
|
41
|
+
# Only basic fields are mandatory for UPDATE (-29)
|
|
42
|
+
mandatory_fields_update = ['job_id', 'job_name']
|
|
43
|
+
for field_name in mandatory_fields_update:
|
|
44
|
+
field_value = getattr(self, field_name, None)
|
|
45
|
+
if field_value is None or field_value == "":
|
|
46
|
+
raise ValueError(f"{field_name} is mandatory for UPDATE (-29) movement")
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
# --- DATETIME PARSE ---
|
|
50
|
+
@field_validator("start_date", "end_date", mode="before")
|
|
51
|
+
def parse_dd_mm_yyyy(cls, v):
|
|
52
|
+
if v in (None, "") or isinstance(v, datetime):
|
|
53
|
+
return v
|
|
54
|
+
return datetime.strptime(v, "%d/%m/%Y")
|
|
55
|
+
|
|
56
|
+
class Config:
|
|
57
|
+
json_encoders = {datetime: lambda d: d.strftime("%d/%m/%Y")}
|
|
58
|
+
allow_population_by_field_name = True
|
|
59
|
+
populate_by_name = True
|