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..")