VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "ValidationRule"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

' --- Properties ---
' RuleKind (ValRule_*), ColIndex, ColLetter, CacheName, MinVal, MaxVal,
' CharLen, VarcharLen, NumberDigits, NumberDec, StartRow
Public RuleKind      As Long
Public ColIndex      As Long
Public ColLetter     As String
Public CacheName     As String
Public MinVal        As Double
Public MaxVal        As Double
Public CharLen       As Long
Public VarcharLen    As Long
Public NumberDigits  As Long
Public NumberDec     As Long
Public StartRow      As Long

' ============================================================
' Execute this rule against a worksheet row.
' Returns a ValidationResult object.
' ============================================================
Public Function ValidateRow(ws As Worksheet, rowNum As Long, Optional lastDataRow As Long = 0) As ValidationResult
    Dim result As ValidationResult: Set result = New ValidationResult

    Select Case RuleKind

        Case ValRule_Required
            If Trim(ws.Cells(rowNum, ColIndex).Value & "") = "" Then
                result.SetFail ERR_REQUIRED, ColIndex, rowNum
            End If

        Case ValRule_Date
            Dim dateVal As String: dateVal = Trim(ws.Cells(rowNum, ColIndex).Value & "")
            If dateVal <> "" Then
                If Len(dateVal) <> 10 Or Mid(dateVal, 5, 1) <> "-" Or Mid(dateVal, 8, 1) <> "-" Then
                    result.SetFail ERR_INVALID, ColIndex, rowNum
                ElseIf Not IsNumeric(Left(dateVal, 4)) Then
                    result.SetFail ERR_INVALID, ColIndex, rowNum
                ElseIf Not IsNumeric(Mid(dateVal, 6, 2)) Then
                    result.SetFail ERR_INVALID, ColIndex, rowNum
                ElseIf Not IsNumeric(Right(dateVal, 2)) Then
                    result.SetFail ERR_INVALID, ColIndex, rowNum
                End If
            End If

        Case ValRule_Number
            Dim numVal As String: numVal = Trim(ws.Cells(rowNum, ColIndex).Value & "")
            If numVal <> "" Then
                If Not IsNumeric(numVal) Then
                    result.SetFail ERR_INVALID, ColIndex, rowNum
                ElseIf NumberDigits > 0 Or NumberDec > 0 Then
                    Dim dotPos As Long: dotPos = InStr(numVal, ".")
                    Dim intPart As String
                    Dim decPart As String
                    If dotPos > 0 Then
                        intPart = Left(numVal, dotPos - 1)
                        decPart = Mid(numVal, dotPos + 1)
                    Else
                        intPart = numVal
                        decPart = ""
                    End If
                    If Left(intPart, 1) = "-" Then intPart = Mid(intPart, 2)
                    If Len(intPart) = 0 Then
                        result.SetFail ERR_NUMDIGITS, ColIndex, rowNum, "Number(" & NumberDigits & ", " & NumberDec & ")"
                    ElseIf NumberDec = 0 And dotPos > 0 Then
                        result.SetFail ERR_NUMDIGITS, ColIndex, rowNum, "Number(" & NumberDigits & ", " & NumberDec & ")"
                    ElseIf Len(intPart) > NumberDigits - NumberDec Then
                        result.SetFail ERR_NUMDIGITS, ColIndex, rowNum, "Number(" & NumberDigits & ", " & NumberDec & ")"
                    ElseIf NumberDec > 0 And Len(decPart) > NumberDec Then
                        result.SetFail ERR_NUMDIGITS, ColIndex, rowNum, "Number(" & NumberDigits & ", " & NumberDec & ")"
                    End If
                End If
            End If

        Case ValRule_CodeSelect
            Dim codeVal As String: codeVal = Trim(ws.Cells(rowNum, ColIndex).Value & "")
            If codeVal <> "" Then
                Dim cache As Object: Set cache = GetCache(CacheName)
                Dim code As String: code = GetCode(codeVal)
                If Not cache.Exists(code) Then
                    result.SetFail ERR_NOT_EXIST, ColIndex, rowNum
                End If
            End If

        Case ValRule_Range
            Dim rangeVal As String: rangeVal = Trim(ws.Cells(rowNum, ColIndex).Value & "")
            If rangeVal <> "" Then
                If Not IsNumeric(rangeVal) Then
                    result.SetFail ERR_INVALID, ColIndex, rowNum
                ElseIf CDbl(rangeVal) < MinVal Or CDbl(rangeVal) > MaxVal Then
                    result.SetFail ERR_RANGE, ColIndex, rowNum
                End If
            End If

        Case ValRule_Duplicate
            Dim dupVal As String: dupVal = Trim(ws.Cells(rowNum, ColIndex).Value & "")
            If dupVal <> "" Then
                Dim upperRow As Long: upperRow = rowNum - 1
                Dim firstRow As Long: firstRow = IIf(StartRow > 0, StartRow, 7)
                Dim i As Long
                For i = firstRow To upperRow
                    If Trim(ws.Cells(i, ColIndex).Value & "") = dupVal Then
                        result.SetFail ERR_DUPLICATE, ColIndex, rowNum, dupVal
                        Exit For
                    End If
                Next i
            End If

        Case ValRule_Char
            Dim charVal As String: charVal = Trim(ws.Cells(rowNum, ColIndex).Value & "")
            If charVal <> "" And Len(charVal) <> CharLen Then
                result.SetFail ERR_CHARLEN, ColIndex, rowNum, CStr(CharLen)
            End If

        Case ValRule_Varchar
            Dim varcharVal As String: varcharVal = Trim(ws.Cells(rowNum, ColIndex).Value & "")
            If varcharVal <> "" And Len(varcharVal) > VarcharLen Then
                result.SetFail ERR_VARLEN, ColIndex, rowNum, CStr(VarcharLen)
            End If

        Case ValRule_Check01
            Dim chk01Val As String: chk01Val = Trim(ws.Cells(rowNum, ColIndex).Value & "")
            If chk01Val <> "" Then
                If Len(chk01Val) <> 1 Or (chk01Val <> "0" And chk01Val <> "1") Then
                    result.SetFail ERR_CHECK01, ColIndex, rowNum
                End If
            End If

        Case ValRule_Alphanumeric
            Dim alphaVal As String: alphaVal = Trim(ws.Cells(rowNum, ColIndex).Value & "")
            If alphaVal <> "" Then
                Dim j As Long
                Dim ch2 As String
                For j = 1 To Len(alphaVal)
                    ch2 = Mid(alphaVal, j, 1)
                    If Not ((ch2 >= "0" And ch2 <= "9") Or (ch2 >= "A" And ch2 <= "Z") Or (ch2 >= "a" And ch2 <= "z")) Then
                        result.SetFail ERR_INVALID, ColIndex, rowNum
                        Exit For
                    End If
                Next j
            End If

        Case ValRule_Custom
            ' Reserved for future extension

    End Select

    Set ValidateRow = result
End Function