196 lines
7.8 KiB
Python
196 lines
7.8 KiB
Python
import re
|
|
|
|
from datetime import datetime
|
|
from decimal import Decimal, ROUND_HALF_UP
|
|
from typing import Any, Optional
|
|
from udpak_semistruktur.logger import hent_logger
|
|
|
|
logger = hent_logger(__name__)
|
|
|
|
def _parse_number(value: Any) -> Decimal:
|
|
"""
|
|
Konverterer en talværdi til Decimal.
|
|
Håndterer EU-format (punktum som tusindtalsseparator, komma som decimal)
|
|
samt US-format og rene integers. Kaster ValueError ved ugyldigt format.
|
|
"""
|
|
|
|
if isinstance(value, (int, float, Decimal)):
|
|
return Decimal(str(value))
|
|
|
|
s = str(value).strip()
|
|
# Fjern valuta og ikke-tal (men bevar cifre, komma, punktum og minus)
|
|
s = re.sub(r'[^\d,.\-]', '', s)
|
|
|
|
# Håndter typiske EU-formater
|
|
if ',' in s and '.' in s:
|
|
# Antag '.' = tusind, ',' = decimal, fx "391.211,75" -> "391211.75"
|
|
s = s.replace('.', '').replace(',', '.')
|
|
elif ',' in s:
|
|
# Kun komma => decimal komma, fx "391211,75" -> "391211.75"
|
|
s = s.replace(',', '.')
|
|
else:
|
|
# Kun punktum eller rene cifre -> int/US-format
|
|
pass
|
|
|
|
# Tom streng eller bare "-" er ugyldig
|
|
if s in ('', '-', '.'):
|
|
raise ValueError(f"Ugyldigt talformat: {value!r}")
|
|
|
|
return Decimal(s)
|
|
|
|
def konverter(data: dict, file_config: dict, global_config: dict) -> dict:
|
|
"""
|
|
Konverterer kolonneværdier i data til de typer der er angivet i file_config.
|
|
Håndterer string, integer, float, decimal, boolean og date.
|
|
Rækker med fejl samles i data['fejlede_rækker'] hvis fejl_fil er konfigureret,
|
|
ellers kastes en exception.
|
|
"""
|
|
if not isinstance(data, dict) or "rækker" not in data:
|
|
return data
|
|
|
|
fejl_fil = global_config.get("fejl_fil_ext", None)
|
|
kolonner = file_config.get("kolonner", [])
|
|
|
|
nye_rækker = []
|
|
fejlede_rækker = []
|
|
|
|
for række in data["rækker"]:
|
|
ny_række = række.copy()
|
|
fejl_i_række = False
|
|
|
|
for kolonne in kolonner:
|
|
field_type = kolonne.get("type", "string")
|
|
string_max_len = kolonne.get("max_længde", None)
|
|
string_truncate = kolonne.get("truncate", None)
|
|
|
|
if field_type == "string" and not string_max_len and not string_truncate:
|
|
continue
|
|
|
|
kolonnenavn = kolonne.get("navn")
|
|
value = ny_række.get(kolonnenavn)
|
|
|
|
krav = kolonne.get("påkrævet", False)
|
|
|
|
# Hvis værdien er None
|
|
if value is None:
|
|
if krav:
|
|
logger.warning(f"Påkrævet felt '{kolonnenavn}' mangler.")
|
|
if fejl_fil:
|
|
fejl_i_række = True
|
|
break
|
|
else:
|
|
raise ValueError(f"Påkrævet felt '{kolonnenavn}' mangler.")
|
|
else:
|
|
continue # spring konvertering over for denne kolonne
|
|
|
|
try:
|
|
if field_type in ["integer", "biginteger", "bigint"]:
|
|
tmp = int(value)
|
|
elif field_type in ["float", "decimal"]:
|
|
dec = _parse_number(value)
|
|
decimal_places = kolonne.get("decimaler", 2)
|
|
q = Decimal(10) ** (-decimal_places) # fx 2 -> Decimal('0.01')
|
|
dec = dec.quantize(q, rounding=ROUND_HALF_UP)
|
|
|
|
if field_type == "float":
|
|
tmp = f"{dec:.{decimal_places}f}"
|
|
else:
|
|
# "decimal" som streng bevaret med præcision
|
|
tmp = f"{dec:.{decimal_places}f}"
|
|
|
|
elif field_type == "boolean":
|
|
tmp = str(value).lower() in ["true", "1", "ja"]
|
|
elif field_type == "date":
|
|
tmp_value = str(value) # altid str
|
|
if '[' in tmp_value and tmp_value.endswith(']'):
|
|
tmp_value = tmp_value[:tmp_value.index('[')]
|
|
|
|
dato_ind_raw = kolonne.get("dato_ind", global_config.get("dato_ind"))
|
|
dato_ud = kolonne.get("dato_ud", global_config.get("dato_ud"))
|
|
|
|
# Tillad både string og liste
|
|
if isinstance(dato_ind_raw, str):
|
|
dato_ind_liste = [dato_ind_raw]
|
|
elif isinstance(dato_ind_raw, list):
|
|
dato_ind_liste = dato_ind_raw
|
|
else:
|
|
raise ValueError(f"'dato_ind' skal være streng eller liste, men var: {type(dato_ind_raw)}")
|
|
|
|
tmp_dato = None
|
|
parse_errors = []
|
|
|
|
for dato_ind in dato_ind_liste:
|
|
try:
|
|
if "%f" in dato_ind:
|
|
if re.search(r'\.\d+', tmp_value):
|
|
tmp_value_padded = re.sub(
|
|
r'\.(\d{1,6})',
|
|
lambda m: '.' + m.group(1).ljust(6, '0'),
|
|
tmp_value
|
|
)
|
|
else:
|
|
dato_ind = dato_ind.replace(".%f", "")
|
|
tmp_value_padded = tmp_value
|
|
else:
|
|
tmp_value_padded = tmp_value
|
|
|
|
# Fjern kolon i tidszonedelen: +03:00 → +0300, hvis %z bruges
|
|
if "%z" in dato_ind:
|
|
tmp_value_padded = re.sub(r'([+-]\d{2}):(\d{2})$', r'\1\2', tmp_value_padded)
|
|
|
|
tmp_dato = datetime.strptime(tmp_value_padded, dato_ind)
|
|
break # succes!
|
|
except ValueError as e:
|
|
parse_errors.append(f" - Format: {dato_ind} -> {e}")
|
|
|
|
if tmp_dato is None:
|
|
fejlbesked = "\n".join(parse_errors)
|
|
raise ValueError(f"Kunne ikke parse dato '{tmp_value}' med nogen af formaterne:\n{fejlbesked}")
|
|
|
|
# Output-format
|
|
if dato_ud.upper().strip() == "SYBASE":
|
|
tmp = tmp_dato.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
|
elif dato_ud.upper().strip() == "DATE":
|
|
tmp = tmp_dato.strftime('%Y-%m-%d')
|
|
elif dato_ud.upper().strip() == "INFORMATICA_US":
|
|
tmp = tmp_dato.strftime('%m/%d/%Y %H:%M:%S.%f')
|
|
else:
|
|
tmp = tmp_dato.strftime(dato_ud)
|
|
|
|
|
|
elif field_type == "string":
|
|
value = str(value)
|
|
if string_max_len and len(value) > string_max_len:
|
|
tmp = value[:string_max_len - 3] + "..."
|
|
elif string_truncate and len(value) > string_truncate:
|
|
tmp = value[:string_truncate]
|
|
else:
|
|
tmp = value
|
|
elif field_type in ["hash", "id", "file", "rod_variant"]:
|
|
tmp = value
|
|
else:
|
|
raise ValueError(f"Ukendt datatype '{field_type}' for feltet '{kolonnenavn}'.")
|
|
|
|
ny_række[kolonnenavn] = tmp
|
|
|
|
except (ValueError, TypeError) as e:
|
|
logger.error(f"[CONVERT]Fejl ved konvertering af felt '{kolonnenavn}' med værdi '{value}': {e}")
|
|
if fejl_fil:
|
|
fejl_i_række = True
|
|
break # Stop konvertering af denne række
|
|
else:
|
|
raise e
|
|
|
|
if fejl_i_række:
|
|
fejlede_rækker.append(række) # Tilføj original række
|
|
else:
|
|
nye_rækker.append(ny_række)
|
|
|
|
# Overskriv med gyldige rækker
|
|
data["rækker"] = nye_rækker
|
|
|
|
if fejl_fil:
|
|
data["fejlede_rækker"] = fejlede_rækker
|
|
|
|
return data
|