Merge branch 'newMaster2024' of https://git.it.verag.ag/edv/SDL into newMaster2024

This commit is contained in:
2026-03-31 16:22:08 +02:00
8 changed files with 969 additions and 3 deletions

View File

@@ -762,7 +762,8 @@
<DesignTime>True</DesignTime>
<DependentUpon>Reference.svcmap</DependentUpon>
</Compile>
<Compile Include="VERAG_Zollanmeldung\cVERAG_Zollanmeldung.vb" />
<Compile Include="VERAG_Zollanmeldung\cVERAG_CustomsDeclarations.vb" />
<Compile Include="VERAG_Zollanmeldung\cVERAG_CustomsDeclarations_Convert.vb" />
<Compile Include="Web References\at.gv.bmf.finanzonline.session\Reference.vb">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>

View File

@@ -0,0 +1,615 @@
Imports ClosedXML.Excel
Imports System.Globalization
Imports System.IO
Imports System.Linq
Public Class cVERAG_CustomsDeclarations_Convert
Public Const EXPECTED_TEMPLATE_VERSION As String = "CBAM-2026-V1.0"
Public Shared Function LOAD_FROM_CBAM_DETAIL_EXCEL(filePath As String,
Optional ByRef errors As List(Of String) = Nothing,
Optional stopOnFirstInvalidRow As Boolean = False) As List(Of cVERAG_CustomsDeclarations)
Dim result As New List(Of cVERAG_CustomsDeclarations)
Try
If errors Is Nothing Then errors = New List(Of String)
If String.IsNullOrWhiteSpace(filePath) Then
errors.Add("Es wurde kein Dateipfad übergeben.")
Return result
End If
If Not File.Exists(filePath) Then
errors.Add("Datei nicht gefunden: " & filePath)
Return result
End If
Using wb As New XLWorkbook(filePath)
Dim ws As IXLWorksheet = Nothing
If wb.Worksheets.Any(Function(x) x.Name.Trim().ToUpper() = "CBAM_REPORT") Then
ws = wb.Worksheet("CBAM_Report")
Else
ws = wb.Worksheet(1)
End If
If ws Is Nothing Then
errors.Add("Kein Worksheet gefunden.")
Return result
End If
' =========================================================
' Template-Version prüfen
' =========================================================
Dim versionInFile As String = TrimSafe(ws.Cell("F3").GetString())
If Not String.Equals(versionInFile, EXPECTED_TEMPLATE_VERSION, StringComparison.OrdinalIgnoreCase) Then
errors.Add("Ungültige Template-Version in F3. Erwartet: '" & EXPECTED_TEMPLATE_VERSION & "', gefunden: '" & versionInFile & "'")
Return result
End If
Dim headerRow As Integer = 13
Dim dataStartRow As Integer = 14
Dim lastRow = If(ws.LastRowUsed() Is Nothing, 0, ws.LastRowUsed().RowNumber())
Dim lastCol = If(ws.LastColumnUsed() Is Nothing, 0, ws.LastColumnUsed().ColumnNumber())
If lastRow < dataStartRow OrElse lastCol = 0 Then
errors.Add("Keine Positionsdaten gefunden.")
Return result
End If
' =========================================================
' Header-Mapping aufbauen
' =========================================================
Dim colMap As New Dictionary(Of String, Integer)(StringComparer.OrdinalIgnoreCase)
For c As Integer = 1 To lastCol
Dim h = TrimSafe(ws.Cell(headerRow, c).GetString())
If h <> "" AndAlso Not colMap.ContainsKey(h) Then
colMap.Add(h, c)
End If
Next
' =========================================================
' Pflichtspalten im Excel prüfen
' =========================================================
Dim requiredHeaders = New String() {"MRN", "declaration_date", "tariff_code"}
For Each req In requiredHeaders
If Not colMap.ContainsKey(req) Then
errors.Add("Pflichtspalte im Excel nicht gefunden: " & req)
End If
Next
If errors.Any(Function(x) x.StartsWith("Pflichtspalte im Excel nicht gefunden:")) Then
Return result
End If
' =========================================================
' Bereits vorhandene / ungültige MRN verwalten
' =========================================================
Dim skippedExistingMrns As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
Dim invalidMrns As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
Dim dict As New Dictionary(Of String, cVERAG_CustomsDeclarations)(StringComparer.OrdinalIgnoreCase)
Dim artificialCounter As Integer = 0
For r As Integer = dataStartRow To lastRow
Dim mrn As String = GetCellString(ws, r, colMap, "MRN")
Dim referenceNo As String = GetCellString(ws, r, colMap, "reference_no")
Dim tariffCode As String = GetCellString(ws, r, colMap, "tariff_code")
Dim itemNoTxt As String = GetCellString(ws, r, colMap, "position_no")
' Komplette Leerzeile überspringen
If mrn = "" AndAlso referenceNo = "" AndAlso tariffCode = "" AndAlso itemNoTxt = "" Then
Continue For
End If
' =====================================================
' Zeilenvalidierung
' =====================================================
Dim validationMessage As String = ""
If Not ValidateImportRow(ws, r, colMap, validationMessage) Then
errors.Add(validationMessage)
If mrn <> "" Then
invalidMrns.Add(mrn)
End If
If stopOnFirstInvalidRow Then
Return result
End If
Continue For
End If
' Wenn MRN bereits als ungültig markiert wurde -> überspringen
If mrn <> "" AndAlso invalidMrns.Contains(mrn) Then
Continue For
End If
' =====================================================
' MRN bereits in DB?
' Dann komplette Anmeldung überspringen
' =====================================================
If mrn <> "" Then
If skippedExistingMrns.Contains(mrn) Then
Continue For
End If
If MRN_EXISTS_IN_DB(mrn) Then
skippedExistingMrns.Add(mrn)
errors.Add("MRN bereits vorhanden - Import übersprungen: " & mrn & " (Zeile " & r & ")")
Continue For
End If
End If
' =====================================================
' Gruppenschlüssel
' =====================================================
Dim grpKey As String = ""
If mrn <> "" Then
grpKey = "MRN|" & mrn
ElseIf referenceNo <> "" Then
grpKey = "REF|" & referenceNo
Else
artificialCounter += 1
grpKey = "AUTO|" & artificialCounter.ToString()
End If
Dim za As cVERAG_CustomsDeclarations = Nothing
If Not dict.ContainsKey(grpKey) Then
za = New cVERAG_CustomsDeclarations()
za.za_MRN = mrn
za.za_LRN = referenceNo
za.za_ReferenceCustomer = GetCellString(ws, r, colMap, "customer_reference")
za.za_RepresentationCode = GetCellString(ws, r, colMap, "representation_type")
za.za_DeclarationDate = GetCellDateNullable(ws, r, colMap, "declaration_date")
za.za_ReleaseDate = GetCellDateNullable(ws, r, colMap, "declaration_date")
za.za_CountryDispatch = GetCellString(ws, r, colMap, "dispatch_country")
za.za_CountryDestination = GetCellString(ws, r, colMap, "destination_country")
za.za_CountryImport = GetCellString(ws, r, colMap, "destination_country")
za.za_MainProcedure = GetCellString(ws, r, colMap, "requested_procedure")
za.za_InvoiceCurrency = GetCellString(ws, r, colMap, "invoice_currency")
za.za_InvoiceAmount = GetCellDecimalNullable(ws, r, colMap, "invoice_amount")
za.za_IsFinalDeclaration = True
za.za_REGIME = "IMPORT"
za.za_System = "CBAM_EXCEL_IMPORT"
za.za_CustomsSystem = ""
za.za_CustomsSystemCountry = ""
za.za_IsExternalSystem = True
za.za_Firma = ""
za.za_Niederlassung = ""
za.za_AdditionalProcedure = ""
za.za_WarehouseCode = ""
za.za_Incoterms = ""
za.za_IncotermsPlace = ""
za.za_TotGrossMass = Nothing
AddOrMergeParty(za.Parties, CreateParty(
role:="EXPORTER",
eori:=GetCellString(ws, r, colMap, "sender_eori"),
name:=GetCellString(ws, r, colMap, "sender_name"),
street:=GetCellString(ws, r, colMap, "sender_address"),
postalCode:="",
city:="",
country:=GetCellString(ws, r, colMap, "sender_country"),
email:="",
phone:=""
))
AddOrMergeParty(za.Parties, CreateParty(
role:="CONSIGNEE",
eori:=GetCellString(ws, r, colMap, "recipient_eori"),
name:=GetCellString(ws, r, colMap, "recipient_name"),
street:=GetCellString(ws, r, colMap, "recipient_address"),
postalCode:="",
city:="",
country:=GetCellString(ws, r, colMap, "recipient_country"),
email:="",
phone:=""
))
AddOrMergeParty(za.Parties, CreateParty(
role:="IMPORTER",
eori:=GetCellString(ws, r, colMap, "importer_eori"),
name:=GetCellString(ws, r, colMap, "importer_name"),
street:=GetCellString(ws, r, colMap, "importer_address"),
postalCode:="",
city:="",
country:=GetCellString(ws, r, colMap, "importer_country"),
email:=GetCellString(ws, r, colMap, "importer_email"),
phone:=GetCellString(ws, r, colMap, "importer_phone")
))
AddOrMergeParty(za.Parties, CreateParty(
role:="DECLARANT",
eori:=GetCellString(ws, r, colMap, "declarant_eori"),
name:=GetCellString(ws, r, colMap, "declarant_name"),
street:=GetCellString(ws, r, colMap, "declarant_address"),
postalCode:="",
city:="",
country:=GetCellString(ws, r, colMap, "declarant_country"),
email:="",
phone:=""
))
Dim invNo = GetCellString(ws, r, colMap, "invoice_number")
Dim invDate = GetCellString(ws, r, colMap, "invoice_date")
If invNo <> "" OrElse invDate <> "" Then
za.Documents.Add(New cVERAG_CustomsDeclarations_Document With {
.zaDoc_Code = "N380",
.zaDoc_Reference = invNo,
.zaDoc_Date = invDate,
.zaDoc_Description = "Invoice"
})
End If
dict.Add(grpKey, za)
Else
za = dict(grpKey)
AddOrMergeParty(za.Parties, CreateParty(
role:="EXPORTER",
eori:=GetCellString(ws, r, colMap, "sender_eori"),
name:=GetCellString(ws, r, colMap, "sender_name"),
street:=GetCellString(ws, r, colMap, "sender_address"),
postalCode:="",
city:="",
country:=GetCellString(ws, r, colMap, "sender_country"),
email:="",
phone:=""
))
AddOrMergeParty(za.Parties, CreateParty(
role:="CONSIGNEE",
eori:=GetCellString(ws, r, colMap, "recipient_eori"),
name:=GetCellString(ws, r, colMap, "recipient_name"),
street:=GetCellString(ws, r, colMap, "recipient_address"),
postalCode:="",
city:="",
country:=GetCellString(ws, r, colMap, "recipient_country"),
email:="",
phone:=""
))
AddOrMergeParty(za.Parties, CreateParty(
role:="IMPORTER",
eori:=GetCellString(ws, r, colMap, "importer_eori"),
name:=GetCellString(ws, r, colMap, "importer_name"),
street:=GetCellString(ws, r, colMap, "importer_address"),
postalCode:="",
city:="",
country:=GetCellString(ws, r, colMap, "importer_country"),
email:=GetCellString(ws, r, colMap, "importer_email"),
phone:=GetCellString(ws, r, colMap, "importer_phone")
))
AddOrMergeParty(za.Parties, CreateParty(
role:="DECLARANT",
eori:=GetCellString(ws, r, colMap, "declarant_eori"),
name:=GetCellString(ws, r, colMap, "declarant_name"),
street:=GetCellString(ws, r, colMap, "declarant_address"),
postalCode:="",
city:="",
country:=GetCellString(ws, r, colMap, "declarant_country"),
email:="",
phone:=""
))
End If
Dim it As New cVERAG_CustomsDeclarations_Item()
it.zaItem_PosNo = GetCellInt(ws, r, colMap, "position_no", za.Items.Count + 1)
it.zaItem_HSCode = GetCellString(ws, r, colMap, "tariff_code")
it.zaItem_OriginCountry = GetCellString(ws, r, colMap, "origin_country")
it.zaItem_GrossMass = GetCellDecimalNullable(ws, r, colMap, "gross_mass_kg")
it.zaItem_NetMass = GetCellDecimalNullable(ws, r, colMap, "net_mass_kg")
it.zaItem_SuppUnitCode = GetCellString(ws, r, colMap, "measurement_unit")
it.zaItem_PrevProcedure = GetCellString(ws, r, colMap, "previous_procedure")
it.zaItem_MainProcedure = GetCellString(ws, r, colMap, "requested_procedure")
it.zaItem_InvoiceValueEUR = GetCellDecimalNullable(ws, r, colMap, "invoice_amount")
it.zaItem_InvoiceCurrency = GetCellString(ws, r, colMap, "invoice_currency")
it.zaItem_StatisticalValueEUR = GetCellDecimalNullable(ws, r, colMap, "invoice_amount")
Dim remarks As String = ""
Dim bench = GetCellString(ws, r, colMap, "Benchmark (Default)")
Dim emi = GetCellString(ws, r, colMap, "Emission (Default)")
Dim fac = GetCellString(ws, r, colMap, "Factor")
Dim est = GetCellString(ws, r, colMap, "Estimated Cost")
If bench <> "" OrElse emi <> "" OrElse fac <> "" OrElse est <> "" Then
remarks =
"CBAM Import | " &
"Benchmark=" & bench & "; " &
"Emission=" & emi & "; " &
"Factor=" & fac & "; " &
"EstimatedCost=" & est
End If
it.zaItem_Remarks = remarks
Dim itemInvNo = GetCellString(ws, r, colMap, "invoice_number")
Dim itemInvDate = GetCellString(ws, r, colMap, "invoice_date")
If itemInvNo <> "" OrElse itemInvDate <> "" Then
it.Documents.Add(New cVERAG_CustomsDeclarations_Document With {
.zaDoc_Code = "N380",
.zaDoc_Reference = itemInvNo,
.zaDoc_Date = itemInvDate,
.zaDoc_Description = "Invoice"
})
End If
za.Items.Add(it)
Next
For Each za In dict.Values
If za.Items IsNot Nothing AndAlso za.Items.Count > 0 Then
za.za_TotGrossMass = za.Items.Sum(Function(x) If(x.zaItem_GrossMass, 0D))
If Not za.za_InvoiceAmount.HasValue Then
Dim invSum = za.Items.Sum(Function(x) If(x.zaItem_InvoiceValueEUR, 0D))
If invSum <> 0D Then
za.za_InvoiceAmount = invSum
End If
End If
result.Add(za)
End If
Next
End Using
Catch ex As Exception
VERAG_PROG_ALLGEMEIN.cErrorHandler.ERR(ex.Message, ex.StackTrace, Reflection.MethodInfo.GetCurrentMethod.Name)
End Try
Return result
End Function
Private Shared Function ValidateImportRow(ws As IXLWorksheet,
row As Integer,
colMap As Dictionary(Of String, Integer),
ByRef validationMessage As String) As Boolean
validationMessage = ""
Dim mrn As String = GetCellString(ws, row, colMap, "MRN")
Dim tariffCode As String = GetCellString(ws, row, colMap, "tariff_code")
Dim declarationDate As Date? = GetCellDateNullable(ws, row, colMap, "declaration_date")
Dim missing As New List(Of String)
If mrn = "" Then missing.Add("MRN")
If Not declarationDate.HasValue Then missing.Add("declaration_date")
If tariffCode = "" Then missing.Add("tariff_code")
If missing.Count > 0 Then
validationMessage = "Zeile " & row & " ungültig. Pflichtfeld(er) fehlen oder sind ungültig: " & String.Join(", ", missing)
Return False
End If
Return True
End Function
Private Shared Function MRN_EXISTS_IN_DB(mrn As String) As Boolean
Try
mrn = TrimSafe(mrn)
If mrn = "" Then Return False
Dim za = cVERAG_CustomsDeclarations.loadByMRN(mrn, False)
Return za IsNot Nothing AndAlso za.hasEntry
Catch ex As Exception
VERAG_PROG_ALLGEMEIN.cErrorHandler.ERR(ex.Message, ex.StackTrace, Reflection.MethodInfo.GetCurrentMethod.Name)
End Try
Return False
End Function
Private Shared Function CreateParty(role As String,
eori As String,
name As String,
street As String,
postalCode As String,
city As String,
country As String,
email As String,
phone As String) As cVERAG_CustomsDeclarations_Parties
If TrimSafe(eori) = "" AndAlso
TrimSafe(name) = "" AndAlso
TrimSafe(street) = "" AndAlso
TrimSafe(country) = "" AndAlso
TrimSafe(email) = "" AndAlso
TrimSafe(phone) = "" Then
Return Nothing
End If
Return New cVERAG_CustomsDeclarations_Parties With {
.zaParty_Role = role,
.zaParty_EORI = TrimSafe(eori),
.zaParty_Name = TrimSafe(name),
.zaParty_Street = TrimSafe(street),
.zaParty_PostalCode = TrimSafe(postalCode),
.zaParty_City = TrimSafe(city),
.zaParty_Country = TrimSafe(country),
.zaParty_Email = TrimSafe(email),
.zaParty_Phone = TrimSafe(phone)
}
End Function
Private Shared Sub AddOrMergeParty(list As List(Of cVERAG_CustomsDeclarations_Parties),
p As cVERAG_CustomsDeclarations_Parties)
If p Is Nothing Then Exit Sub
If list Is Nothing Then Exit Sub
Dim existing = list.FirstOrDefault(Function(x)
Return String.Equals(TrimSafe(x.zaParty_Role), TrimSafe(p.zaParty_Role), StringComparison.OrdinalIgnoreCase) AndAlso
String.Equals(TrimSafe(x.zaParty_EORI), TrimSafe(p.zaParty_EORI), StringComparison.OrdinalIgnoreCase) AndAlso
String.Equals(TrimSafe(x.zaParty_Name), TrimSafe(p.zaParty_Name), StringComparison.OrdinalIgnoreCase)
End Function)
If existing Is Nothing Then
list.Add(p)
Else
If TrimSafe(existing.zaParty_Street) = "" Then existing.zaParty_Street = p.zaParty_Street
If TrimSafe(existing.zaParty_PostalCode) = "" Then existing.zaParty_PostalCode = p.zaParty_PostalCode
If TrimSafe(existing.zaParty_City) = "" Then existing.zaParty_City = p.zaParty_City
If TrimSafe(existing.zaParty_Country) = "" Then existing.zaParty_Country = p.zaParty_Country
If TrimSafe(existing.zaParty_Email) = "" Then existing.zaParty_Email = p.zaParty_Email
If TrimSafe(existing.zaParty_Phone) = "" Then existing.zaParty_Phone = p.zaParty_Phone
End If
End Sub
Private Shared Function GetCellString(ws As IXLWorksheet,
row As Integer,
colMap As Dictionary(Of String, Integer),
header As String) As String
Try
If ws Is Nothing OrElse colMap Is Nothing Then Return ""
If Not colMap.ContainsKey(header) Then Return ""
Return TrimSafe(ws.Cell(row, colMap(header)).GetString())
Catch
Return ""
End Try
End Function
Private Shared Function GetCellDecimalNullable(ws As IXLWorksheet,
row As Integer,
colMap As Dictionary(Of String, Integer),
header As String) As Decimal?
Try
If ws Is Nothing OrElse colMap Is Nothing Then Return Nothing
If Not colMap.ContainsKey(header) Then Return Nothing
Dim cell = ws.Cell(row, colMap(header))
If cell Is Nothing OrElse cell.IsEmpty() Then Return Nothing
If cell.DataType = XLDataType.Number Then
Return Convert.ToDecimal(cell.GetDouble())
End If
Dim txt = TrimSafe(cell.GetString())
If txt = "" Then Return Nothing
Dim d As Decimal
txt = txt.Replace(" ", "")
If Decimal.TryParse(txt, NumberStyles.Any, CultureInfo.InvariantCulture, d) Then Return d
If Decimal.TryParse(txt, NumberStyles.Any, CultureInfo.GetCultureInfo("de-AT"), d) Then Return d
If Decimal.TryParse(txt, NumberStyles.Any, CultureInfo.GetCultureInfo("de-DE"), d) Then Return d
Catch
End Try
Return Nothing
End Function
Private Shared Function GetCellDateNullable(ws As IXLWorksheet,
row As Integer,
colMap As Dictionary(Of String, Integer),
header As String) As Date?
Try
If ws Is Nothing OrElse colMap Is Nothing Then Return Nothing
If Not colMap.ContainsKey(header) Then Return Nothing
Dim cell = ws.Cell(row, colMap(header))
If cell Is Nothing OrElse cell.IsEmpty() Then Return Nothing
If cell.DataType = XLDataType.DateTime Then
Return cell.GetDateTime()
End If
If cell.DataType = XLDataType.Number Then
Return DateTime.FromOADate(cell.GetDouble())
End If
Dim txt = TrimSafe(cell.GetString())
If txt = "" Then Return Nothing
Dim dt As DateTime
If DateTime.TryParse(txt, CultureInfo.InvariantCulture, DateTimeStyles.None, dt) Then Return dt
If DateTime.TryParse(txt, CultureInfo.GetCultureInfo("de-AT"), DateTimeStyles.None, dt) Then Return dt
If DateTime.TryParse(txt, CultureInfo.GetCultureInfo("de-DE"), DateTimeStyles.None, dt) Then Return dt
Catch
End Try
Return Nothing
End Function
Private Shared Function GetCellInt(ws As IXLWorksheet,
row As Integer,
colMap As Dictionary(Of String, Integer),
header As String,
Optional defaultValue As Integer = 0) As Integer
Try
If ws Is Nothing OrElse colMap Is Nothing Then Return defaultValue
If Not colMap.ContainsKey(header) Then Return defaultValue
Dim cell = ws.Cell(row, colMap(header))
If cell Is Nothing OrElse cell.IsEmpty() Then Return defaultValue
If cell.DataType = XLDataType.Number Then
Return Convert.ToInt32(Math.Truncate(cell.GetDouble()))
End If
Dim txt = TrimSafe(cell.GetString())
If txt = "" Then Return defaultValue
Dim i As Integer
If Integer.TryParse(txt, i) Then Return i
Dim d As Decimal
If Decimal.TryParse(txt, NumberStyles.Any, CultureInfo.InvariantCulture, d) Then
Return Convert.ToInt32(Math.Truncate(d))
End If
If Decimal.TryParse(txt, NumberStyles.Any, CultureInfo.GetCultureInfo("de-AT"), d) Then
Return Convert.ToInt32(Math.Truncate(d))
End If
If Decimal.TryParse(txt, NumberStyles.Any, CultureInfo.GetCultureInfo("de-DE"), d) Then
Return Convert.ToInt32(Math.Truncate(d))
End If
Catch
End Try
Return defaultValue
End Function
Private Shared Function TrimSafe(value As Object) As String
If value Is Nothing Then Return ""
Return Convert.ToString(value).Trim()
End Function
End Class