diff --git a/app/TaskFlow/ClassFormat.vb b/app/TaskFlow/ClassFormat.vb
index 331c44b..433c7c6 100644
--- a/app/TaskFlow/ClassFormat.vb
+++ b/app/TaskFlow/ClassFormat.vb
@@ -39,46 +39,85 @@ Public Class ClassFormat
Dim normalized As String = pValue.Trim()
+ LOGGER.Debug($"[NormalizeNumericString] Input: [{pValue}]")
+
' Entferne Währungssymbole und Leerzeichen
normalized = System.Text.RegularExpressions.Regex.Replace(normalized, "[€$£¥\s]", "")
- ' Prüfe, ob der String sowohl Punkt als auch Komma enthält
Dim hasDot As Boolean = normalized.Contains(".")
Dim hasComma As Boolean = normalized.Contains(",")
+ LOGGER.Debug($"[NormalizeNumericString] After cleanup: [{normalized}], HasDot={hasDot}, HasComma={hasComma}")
+
If hasDot AndAlso hasComma Then
' Beide vorhanden: Das letzte ist der Dezimaltrenner
Dim lastDotPos As Integer = normalized.LastIndexOf(".")
Dim lastCommaPos As Integer = normalized.LastIndexOf(",")
+ LOGGER.Debug($"[NormalizeNumericString] Both separators found: LastDotPos={lastDotPos}, LastCommaPos={lastCommaPos}")
+
If lastDotPos > lastCommaPos Then
- ' Punkt ist Dezimaltrenner, Komma ist Tausendertrenner
normalized = normalized.Replace(",", "")
Else
- ' Komma ist Dezimaltrenner, Punkt ist Tausendertrenner
normalized = normalized.Replace(".", "").Replace(",", ".")
End If
+
ElseIf hasComma Then
- ' Nur Komma: Könnte Dezimal- oder Tausendertrenner sein
- ' Wenn mehr als ein Komma → Tausendertrenner
- ' Wenn nur ein Komma und <= 3 Stellen danach → Dezimaltrenner
Dim commaCount As Integer = normalized.Count(Function(c) c = ","c)
+ LOGGER.Debug($"[NormalizeNumericString] Only comma found: CommaCount={commaCount}")
+
If commaCount = 1 Then
Dim lastCommaPos As Integer = normalized.LastIndexOf(",")
Dim digitsAfterComma As Integer = normalized.Length - lastCommaPos - 1
+ LOGGER.Debug($"[NormalizeNumericString] Single comma: DigitsAfterComma={digitsAfterComma}")
+
If digitsAfterComma <= 3 Then
- ' Wahrscheinlich Dezimaltrenner
normalized = normalized.Replace(",", ".")
Else
- ' Wahrscheinlich Tausendertrenner
normalized = normalized.Replace(",", "")
End If
Else
- ' Mehrere Kommas → Tausendertrenner
normalized = normalized.Replace(",", "")
End If
+
+ ElseIf hasDot Then
+ Dim dotCount As Integer = normalized.Count(Function(c) c = "."c)
+ LOGGER.Debug($"[NormalizeNumericString] Only dot found: DotCount={dotCount}")
+
+ If dotCount = 1 Then
+ Dim lastDotPos As Integer = normalized.LastIndexOf(".")
+ Dim digitsAfterDot As Integer = normalized.Length - lastDotPos - 1
+
+ LOGGER.Debug($"[NormalizeNumericString] Single dot: DigitsAfterDot={digitsAfterDot}")
+
+ ' ✅ KRITISCHE ÄNDERUNG: Prüfe auch Stellen VOR dem Punkt
+ Dim digitsBeforeDot As Integer = lastDotPos
+
+ ' Heuristik: Wenn <= 3 Stellen nach Punkt UND >= 1 Stelle davor → Dezimaltrenner
+ ' Wenn > 3 Stellen davor UND <= 3 Stellen nach Punkt → unklar, vermutlich Dezimal
+ ' Wenn > 3 Stellen nach Punkt → definitiv KEIN Dezimaltrenner
+
+ If digitsAfterDot > 3 Then
+ LOGGER.Warn($"⚠️ [NormalizeNumericString] Dot with {digitsAfterDot} digits after → treating as THOUSAND separator!")
+ normalized = normalized.Replace(".", "")
+ ElseIf digitsAfterDot >= 1 AndAlso digitsAfterDot <= 3 Then
+ ' Wahrscheinlich Dezimaltrenner (z.B. 5464.17 oder 120.5)
+ LOGGER.Debug($"[NormalizeNumericString] Dot treated as decimal separator ({digitsBeforeDot} digits before, {digitsAfterDot} after)")
+ Else
+ ' digitsAfterDot = 0 → Punkt am Ende, vermutlich Fehler
+ LOGGER.Warn($"⚠️ [NormalizeNumericString] Dot at end of string → removing")
+ normalized = normalized.Replace(".", "")
+ End If
+ Else
+ ' Mehrere Punkte → Tausendertrenner
+ LOGGER.Debug($"[NormalizeNumericString] Multiple dots → removing all")
+ normalized = normalized.Replace(".", "")
+ End If
+ Else
+ LOGGER.Debug($"[NormalizeNumericString] No separators found → integer or already normalized")
End If
- ' Wenn nur Punkt vorhanden: bereits im richtigen Format
+
+ LOGGER.Debug($"[NormalizeNumericString] Output: [{normalized}]")
Return normalized
End Function
@@ -96,6 +135,7 @@ Public Class ClassFormat
Select Case pType
Case ClassControlCreator.CONTROL_TYPE_DOUBLE
Try
+ ' ✅ IMMER normalisieren – auch für DB-Werte!
Dim normalizedValue As String = NormalizeNumericString(pValue?.ToString())
If Double.TryParse(normalizedValue, NumberStyles.Float, CultureInfo.InvariantCulture, oConvertedValue) Then
Return oConvertedValue
@@ -106,12 +146,15 @@ Public Class ClassFormat
Case ClassControlCreator.CONTROL_TYPE_CURRENCY
Try
+ ' ✅ KRITISCH: Normalisierung VOR Konvertierung
Dim normalizedValue As String = NormalizeNumericString(pValue?.ToString())
- LOGGER.Debug($"GetConvertedValue: Converting {pValue.ToString} (normalized: {normalizedValue}) to Currency ")
+ LOGGER.Debug($"GetConvertedValue CURRENCY: Original=[{pValue}], Normalized=[{normalizedValue}]")
+
If Double.TryParse(normalizedValue, NumberStyles.Float, CultureInfo.InvariantCulture, oConvertedValue) Then
Return oConvertedValue
End If
Catch ex As Exception
+ LOGGER.Error($"Currency conversion failed for [{pValue}]: {ex.Message}")
LOGGER.Error(ex)
End Try
@@ -124,6 +167,7 @@ Public Class ClassFormat
Catch ex As Exception
LOGGER.Error(ex)
End Try
+
Case Else
LOGGER.Debug($"GetConvertedValue - Case ELSE - pType is {pType}")
Try
@@ -132,7 +176,6 @@ Public Class ClassFormat
LOGGER.Warn($"Error in GetConvertedValue: pType is {pType} - converting value to String")
oConvertedValue = ""
End Try
-
End Select
Return oConvertedValue
@@ -140,26 +183,32 @@ Public Class ClassFormat
'''
''' Converts values to their respective data type and then back to string
- ''' according to the current culture
+ ''' using INVARIANT culture for consistency across systems.
'''
'''
'''
Public Shared Function GetStringValue(pValue As Object) As String
+ ' ✅ FIX: Immer InvariantCulture verwenden für DB-Speicherung
Select Case pValue.GetType
Case GetType(Single)
- Return DirectCast(pValue, Single).ToString(CultureInfo.CurrentCulture)
+ ' ✅ NEU: InvariantCulture statt CurrentCulture
+ Return DirectCast(pValue, Single).ToString(CultureInfo.InvariantCulture)
Case GetType(Double)
- Return DirectCast(pValue, Double).ToString(CultureInfo.CurrentCulture)
+ ' ✅ NEU: InvariantCulture statt CurrentCulture
+ Return DirectCast(pValue, Double).ToString(CultureInfo.InvariantCulture)
Case GetType(Decimal)
- Return DirectCast(pValue, Decimal).ToString(CultureInfo.CurrentCulture)
+ ' ✅ NEU: InvariantCulture statt CurrentCulture
+ Return DirectCast(pValue, Decimal).ToString(CultureInfo.InvariantCulture)
Case GetType(Date)
- Return DirectCast(pValue, Date).ToString(CultureInfo.CurrentCulture)
+ ' Datum: ISO 8601 Format für Culture-Unabhängigkeit
+ Return DirectCast(pValue, Date).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
Case GetType(DateTime)
- Return DirectCast(pValue, DateTime).ToString(CultureInfo.CurrentCulture)
+ ' DateTime: ISO 8601 Format
+ Return DirectCast(pValue, DateTime).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
Case Else
Return pValue.ToString
diff --git a/app/TaskFlow/frmValidator.vb b/app/TaskFlow/frmValidator.vb
index cea128c..16062a7 100644
--- a/app/TaskFlow/frmValidator.vb
+++ b/app/TaskFlow/frmValidator.vb
@@ -3779,12 +3779,15 @@ Public Class frmValidator
MyValidationLogger.Error(ex)
errormessage = "unexpected error in Load_Next_Document:" & ex.Message
My.Settings.Save()
+
MyValidationLogger.Info("unexpected error in Load_Next_Document: " & ex.Message)
+
frmError.ShowDialog()
Finally
If layoutSuspended Then
PanelValidatorControl.ResumeLayout()
End If
+ CloseOverlaySafe()
If LOG_HOTSPOTS Then
' ========== DIAGNOSE ENDE ==========
MyValidationLogger.Info($"[INFO] Load_Next_Document ENDE")
@@ -4533,33 +4536,61 @@ Public Class frmValidator
MyValidationLogger.Debug("Creating new row..")
For index = 0 To oDTColumnsPerDevExGrid.Rows.Count - 1
- Dim rawValue As String = If(index < oColValuesfromSource.Length, oColValuesfromSource(index), String.Empty)
- Dim targetColumn As DataColumn = oDataSource.Columns(index)
- Dim colName As String = targetColumn.ColumnName
- Dim colType As String = targetColumn.DataType.FullName
-
- MyValidationLogger.Debug("Grid row assign: RowIdx={0}, ColIdx={1}, ColName={2}, ColType={3}, RawValue=[{4}], IsEmpty={5}",
- oDataSource.Rows.Count, index, colName, colType, rawValue, String.IsNullOrWhiteSpace(rawValue))
-
Try
- If oColValuesfromSource.Length > index Then
- oNewRow.Item(index) = oColValuesfromSource(index)
- Else
- If colType = "System.Double" Or colType = "System.Int32" Or colType = "System.Int64" Then
- oNewRow.Item(index) = 0
+ ' 1. Basis-Checks
+ Dim rawValue As String = If(index < oColValuesfromSource.Length, oColValuesfromSource(index), String.Empty)
+ Dim targetColumn As DataColumn = oDataSource.Columns(index)
+ Dim colType As Type = targetColumn.DataType
+
+ ' 2. NULL-Handling
+ If String.IsNullOrWhiteSpace(rawValue) Then
+ If colType.IsValueType AndAlso Nullable.GetUnderlyingType(colType) Is Nothing Then
+ ' Nicht-Nullable Value-Type → Default-Wert setzen
+ oNewRow.Item(index) = Activator.CreateInstance(colType)
+ MyValidationLogger.Debug($"Grid row default: ColIdx={index}, ColType={colType.Name}")
Else
- oNewRow.Item(index) = String.Empty
+ ' Nullable/Reference-Type → DBNull
+ oNewRow.Item(index) = DBNull.Value
End If
+ Continue For
End If
- Catch ex As Exception
- MyValidationLogger.Warn("⚠️ Grid row assign FAILED RowIdx = {0}, ColIdx={1}, ColName={2}, ColType={3}, RawValue=[{4}]",
- oDataSource.Rows.Count, index, colName, colType, rawValue)
- MyValidationLogger.Error(ex)
+
+ ' 3. Typ-spezifische Konvertierung
Try
- MyValidationLogger.Debug("Column.AllowDBNull={0}, Column.MaxLength={1}", targetColumn.AllowDBNull, targetColumn.MaxLength)
- Catch
+ Select Case Type.GetTypeCode(colType)
+ Case TypeCode.Int32
+ oNewRow.Item(index) = Integer.Parse(rawValue.Trim(), CultureInfo.InvariantCulture)
+ Case TypeCode.Int64
+ oNewRow.Item(index) = Long.Parse(rawValue.Trim(), CultureInfo.InvariantCulture)
+ Case TypeCode.Double
+ oNewRow.Item(index) = Double.Parse(rawValue.Trim().Replace(",", "."), CultureInfo.InvariantCulture)
+ Case TypeCode.Decimal
+ oNewRow.Item(index) = Decimal.Parse(rawValue.Trim().Replace(",", "."), CultureInfo.InvariantCulture)
+ Case TypeCode.Boolean
+ oNewRow.Item(index) = Boolean.Parse(rawValue.Trim())
+ Case TypeCode.DateTime
+ oNewRow.Item(index) = DateTime.Parse(rawValue.Trim(), CultureInfo.CurrentCulture)
+ Case Else
+ ' String oder Custom-Type → direkt übernehmen
+ oNewRow.Item(index) = rawValue
+ End Select
+
+ Catch convEx As FormatException
+ MyValidationLogger.Warn($"⚠️ Grid conversion FAILED: ColIdx={index}, ColType={colType.Name}, RawValue=[{rawValue}] → using default")
+
+ ' Fallback: Default-Wert statt Crash
+ If colType.IsValueType Then
+ oNewRow.Item(index) = Activator.CreateInstance(colType)
+ Else
+ oNewRow.Item(index) = DBNull.Value
+ End If
End Try
- Throw
+
+ Catch logEx As Exception
+ ' Fallback: Minimales Logging ohne Fehler
+ MyValidationLogger.Debug($"Grid row assign CRITICAL: ColIdx={index} [error: {logEx.Message}]")
+ ' Leere Zelle → DBNull
+ oNewRow.Item(index) = DBNull.Value
End Try
Next
MyValidationLogger.Debug("Adding row To grid..")