From d45eb6821e14a1e0d51cf829af97cdb3ad8979f1 Mon Sep 17 00:00:00 2001 From: Andreas Luxbauer Date: Wed, 23 Sep 2026 07:35:13 +0200 Subject: [PATCH] spedbuch auswert; ReadT1ATEZ,Greenpulse API --- .../usrCntlSpeditionsBuchSuche.vb | 23 +- .../usrCntlMessenger_ListNewChats.vb | 4 + .../cATEZ_Greenpulse_CBAM_CostCalculation.vb | 1085 +++++++++-------- ...TEZ_Greenpulse_CBAM_CostCalculation_NEU.vb | 1015 +++++++++++++++ .../ATEZ/Read_T1/cATEZ_Read_T1.vb | 558 ++++++--- .../VERAG_PROG_ALLGEMEIN.vbproj | 1 + 6 files changed, 1951 insertions(+), 735 deletions(-) create mode 100644 VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/GREENPULSE/cATEZ_Greenpulse_CBAM_CostCalculation_NEU.vb diff --git a/SDL/Speditionsbuch/usrCntlSpeditionsBuchSuche.vb b/SDL/Speditionsbuch/usrCntlSpeditionsBuchSuche.vb index a2ad12e3..2c499c72 100644 --- a/SDL/Speditionsbuch/usrCntlSpeditionsBuchSuche.vb +++ b/SDL/Speditionsbuch/usrCntlSpeditionsBuchSuche.vb @@ -4072,10 +4072,13 @@ Class cSpeditionsbuchReport datei = exclApp.Workbooks.Open(path) blatt = datei.Worksheets(1) - - CreateDetailReport(blatt, dt, KDNR) + Dim errorTmp = "" + CreateDetailReport(blatt, dt, KDNR, errorTmp) ' CreateGeneralReport(blatt, dt, KDNR, AbfVon, AbfBis) + If errorTmp <> "" Then + MsgBox("Fehlende Datensätze des Detailberichtes (SpedBuch vorhanden): " & vbCrLf & errorTmp.Trim) + End If datei.Save() @@ -4181,7 +4184,7 @@ Class cSpeditionsbuchReport Dim SQL As New VERAG_PROG_ALLGEMEIN.SQL Dim sqlText As String = - "SELECT CAST([FilialenNr] as varchar) + '/' + CAST([AbfertigungsNr] as varchar) Abfertigungsnummer, " & + "Select CAST([FilialenNr] As varchar) + '/' + CAST([AbfertigungsNr] as varchar) Abfertigungsnummer, " & " CAST([Abfertigungsdatum] as date) Abfertigungsdatum, " & " Absender, Empfänger, " & " Rechnungsbetrag Betrag, " & @@ -4237,7 +4240,7 @@ Class cSpeditionsbuchReport Return cd End Function - Private Sub CreateDetailReport(blatt As Object, dt As DataTable, kdnr As Integer) + Private Sub CreateDetailReport(blatt As Object, dt As DataTable, kdnr As Integer, ByRef errorTmp As String) Dim cnt As Integer = 2 For Each r As DataRow In dt.Rows @@ -4245,14 +4248,14 @@ Class cSpeditionsbuchReport Dim cd As cVERAG_CustomsDeclarations = LoadCustomsDeclaration(r) If Not cd.hasEntry Then - MsgBox("ERROR: Nicht gefunden: " & - If(r("BelegNr"), "").ToString() & " " & + errorTmp &= If(r("BelegNr"), "").ToString() & " " & If(r("FilialenNr"), "").ToString() & "/" & - If(r("AbfertigungsNr"), "").ToString()) - End If - 's - WriteDetailReportRow(blatt, r, cd, cnt, kdnr) + If(r("AbfertigungsNr"), "").ToString() & vbNewLine + Else 'Durch else wird nur Zeile geschrieben, wenn auch eine Zolldeklaration gefunden wurde. Sonst wird nur eine Fehlermeldung ausgegeben. + WriteDetailReportRow(blatt, r, cd, cnt, kdnr) + + End If Catch ex As Exception MsgBox(ex.Message & vbCrLf & ex.StackTrace) End Try diff --git a/VERAG_PROG_ALLGEMEIN/Messenger/usrCntlMessenger_ListNewChats.vb b/VERAG_PROG_ALLGEMEIN/Messenger/usrCntlMessenger_ListNewChats.vb index d3067978..ef86297f 100644 --- a/VERAG_PROG_ALLGEMEIN/Messenger/usrCntlMessenger_ListNewChats.vb +++ b/VERAG_PROG_ALLGEMEIN/Messenger/usrCntlMessenger_ListNewChats.vb @@ -249,5 +249,9 @@ Public Class usrCntlMessenger_ListNewChats VERAG_PROG_ALLGEMEIN.cErrorHandler.ERR(ex.Message, ex.StackTrace, System.Reflection.MethodInfo.GetCurrentMethod.Name) End Try End Sub + Public Sub initFontSizeSmaller() + dgvChats.RowsDefaultCellStyle.Font = New Font(dgvChats.Font.FontFamily, 8, dgvChats.Font.Style) + End Sub + End Class diff --git a/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/GREENPULSE/cATEZ_Greenpulse_CBAM_CostCalculation.vb b/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/GREENPULSE/cATEZ_Greenpulse_CBAM_CostCalculation.vb index f89038cd..29f78a80 100644 --- a/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/GREENPULSE/cATEZ_Greenpulse_CBAM_CostCalculation.vb +++ b/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/GREENPULSE/cATEZ_Greenpulse_CBAM_CostCalculation.vb @@ -1,542 +1,543 @@ -' ============================================================================ -' GreenPulse – CBAM Carbon Cost API -' Single-file VB.NET implementation using Chilkat -' Adapted to Postman Collection 29.12.2025 -' ============================================================================ - -Imports Chilkat -Imports System.Globalization - -' ============================================================================ -' MAIN API CLASS -' ============================================================================ -Public Class cATEZ_Greenpulse_CBAM_CostCalculation - - Private ReadOnly _baseUrl As String = "https://test-greenpulse-api.singlewindow.io/api/v1-0/public" - 'Private ReadOnly _baseUrl As String = "https://greenpulse-api.singlewindow.io/api/v1-0/public" - Private ReadOnly _http As Http - - Public Sub New() - _http = New Http() - _http.SetRequestHeader("Content-Type", "application/json") - End Sub - - ' ------------------------------------------------------------------------ - ' GET /carbon-cost/cn-codes - ' ------------------------------------------------------------------------ - Public Function GetCnCodes() As cCBAM_CnCode_Response - - Dim resp = _http.QuickGetObj(_baseUrl & "/carbon-cost/cn-codes") - If resp Is Nothing Then Throw New Exception(_http.LastErrorText) - - Dim json As New JsonObject() - json.Load(resp.BodyStr) - - Dim r As New cCBAM_CnCode_Response With {.success = json.BoolOf("success")} - If r.success Then - r.data = New List(Of cCBAM_CnCode) - Dim a = json.ArrayOf("data") - For i = 0 To a.Size - 1 - Dim o = a.ObjectAt(i) - r.data.Add(New cCBAM_CnCode With { - .cn_code = o.StringOf("cn_code"), - .cn_description = o.StringOf("cn_description") - }) - Next - Else - r.error = ParseError(json) - End If - - Return r - End Function - - - Public Function insertinto_Greendeal_CBAM_Trn(cCBAM_CnCode_Response As cCBAM_CnCode_Response) As Boolean - - VERAG_PROG_ALLGEMEIN.cGreendeal_CBAM_Trn.DELETE_ALL() - - For Each CN In cCBAM_CnCode_Response.data - Dim CBAM As New VERAG_PROG_ALLGEMEIN.cGreendeal_CBAM_Trn - - - CBAM.trnPattern = CN.cn_code & "%" - CBAM.sektor = "" - CBAM.ware = CN.cn_description - CBAM.is_exclusion = False - CBAM.is_active = True - CBAM.start_date = "01.10.2023" - CBAM.end_date = Nothing - CBAM.SAVE() - Next - Return True - End Function - - ' ------------------------------------------------------------------------ - ' GET /carbon-cost/cn-code-defaults - ' ------------------------------------------------------------------------ - Public Function GetCnCodeDefaults( - cn_code As String, - country_code As String, - Optional year As Integer? = Nothing - ) As cCBAM_CnCodeDefaults_Response - - Dim url = $"{_baseUrl}/carbon-cost/cn-code-defaults?cn_code={cn_code}&country_code={country_code}" - If year.HasValue Then url &= $"&year={year.Value}" - - Console.WriteLine($"[GetCnCodeDefaults] Request URL: {url}") - - Dim resp = _http.QuickGetObj(url) - If resp Is Nothing Then - Console.WriteLine("[GetCnCodeDefaults] HTTP request failed: " & _http.LastErrorText) - Throw New Exception(_http.LastErrorText) - End If - - Console.WriteLine("[GetCnCodeDefaults] HTTP response received.") - If resp.BodyStr IsNot Nothing Then - Console.WriteLine("[GetCnCodeDefaults] Response body length: " & resp.BodyStr.Length) - Console.WriteLine("[GetCnCodeDefaults] Body: " & resp.BodyStr) - Else - Console.WriteLine("[GetCnCodeDefaults] Response body is empty.") - End If - - Dim json As New JsonObject() - json.Load(resp.BodyStr) - - Dim successFlag = json.BoolOf("success") - Console.WriteLine($"[GetCnCodeDefaults] JSON 'success' flag: {successFlag}") - - Dim r As New cCBAM_CnCodeDefaults_Response With {.success = successFlag} - - If r.success Then - Dim d = json.ObjectOf("data") - r.data = New cCBAM_CnCodeDefaults With { - .default_emission = GetDec(d, "default_emission"), - .benchmark = GetNullableDec(d, "benchmark") - } - Console.WriteLine($"[GetCnCodeDefaults] Parsed defaults: default_emission={r.data.default_emission}, benchmark={(If(r.data.benchmark.HasValue, r.data.benchmark.Value.ToString(), "null"))}") - Else - r.error = ParseError(json) - Console.WriteLine($"[GetCnCodeDefaults] API error: code={r.error.code}, message={r.error.message}, statusCode={r.error.statusCode}") - End If - - Return r - End Function - - ' ------------------------------------------------------------------------ - ' POST /carbon-cost/calculate - ' ------------------------------------------------------------------------ - Public Function CalculateCost(req As cCBAM_CostCalculation_Request) _ - As cCBAM_CostCalculation_Response - - VERAG_PROG_ALLGEMEIN.cChilkat_Helper.UnlockCilkat() - - Dim resp = _http.PostJson2( - _baseUrl & "/carbon-cost/calculate", - "application/json", - req.ToJson() - ) - - If resp Is Nothing Then Throw New Exception(_http.LastErrorText) - Return ParseCalcResponse(resp.BodyStr) - End Function - - ' ------------------------------------------------------------------------ - ' calcCBAM – Convenience Wrapper - ' ------------------------------------------------------------------------ - ' ------------------------------------------------------------------------ - ' calcCBAM – mit automatischem Fallback auf CN-Code Defaults - ' ------------------------------------------------------------------------ - Public Shared Function calcCBAM(cn_code As String, weight As Object, country_code As String, Optional ByRef see_total As Object = Nothing, Optional year As Object = Nothing, Optional ByRef benchmark_value As Object = Nothing, Optional ByRef CBAM_COST As Decimal = -1, Optional ByRef CBAM_EMISSION As Decimal = -1, Optional ByRef CBAM_BENCHMARK As Decimal = -1, Optional ByRef CBAM_FAKTOR As Decimal = -1) As String - - ' ------------------------------------------------------------ - ' Basis-Validierung - ' ------------------------------------------------------------ - If cn_code Is Nothing OrElse Not IsNumeric(cn_code) _ - OrElse Not {4, 6, 8}.Contains(cn_code.Length) Then - Return "Fehler: CN-Code muss 4-, 6- oder 8-stellig numerisch sein" - End If - - If weight Is Nothing OrElse Not IsNumeric(weight) OrElse CDbl(weight) <= 0 Then - Return "Fehler: Gewicht muss > 0 sein" - End If - - If country_code Is Nothing OrElse country_code.Length <> 2 Then - Return "Fehler: country_code muss ISO-2 sein" - End If - - If year IsNot Nothing AndAlso Not IsNumeric(year) Then - Return "Fehler: ungültiges Jahr" - End If - - ' ------------------------------------------------------------ - ' API initialisieren - ' ------------------------------------------------------------ - Dim api As New cATEZ_Greenpulse_CBAM_CostCalculation() - - ' ------------------------------------------------------------ - ' Prüfen, ob Defaults benötigt werden - ' ------------------------------------------------------------ - Dim needDefaults As Boolean = False - - If see_total Is Nothing OrElse Not IsNumeric(see_total) OrElse CDbl(see_total) < 0 Then - needDefaults = True - End If - - If benchmark_value Is Nothing OrElse Not IsNumeric(benchmark_value) OrElse CDbl(benchmark_value) < 0 Then - needDefaults = True - End If - - Dim defaultEmission As Decimal? = Nothing - Dim defaultBenchmark As Decimal? = Nothing - - If needDefaults Then - Try - Dim defResp = api.GetCnCodeDefaults( - cn_code, - country_code.ToUpperInvariant(), - If(IsNumeric(year), CInt(year), CType(Nothing, Integer?)) - ) - - If defResp.success Then - defaultEmission = defResp.data.default_emission - defaultBenchmark = defResp.data.benchmark - - benchmark_value = defaultBenchmark - see_total = defaultEmission - Else - Return $"FEHLER DEFAULTS {defResp.error.code}: {defResp.error.message}" - End If - - Catch ex As Exception - Return "Technischer Fehler beim Laden der CN-Code Defaults: " & ex.Message - End Try - End If - - ' ------------------------------------------------------------ - ' Request aufbauen - ' ------------------------------------------------------------ - Dim req As New cCBAM_CostCalculation_Request With { - .cn_code = cn_code, - .country_code = country_code.ToUpperInvariant(), - .weight = CDec(weight) - } - - ' see_total - If IsNumeric(see_total) AndAlso CDbl(see_total) >= 0 Then - req.see_total = CDec(see_total) - ElseIf defaultEmission.HasValue Then - req.see_total = defaultEmission.Value - End If - - ' year - If IsNumeric(year) Then - req.year = CInt(year) - End If - - ' benchmark_value - If IsNumeric(benchmark_value) AndAlso CDbl(benchmark_value) >= 0 Then - req.benchmark_value = CDec(benchmark_value) - ElseIf defaultBenchmark.HasValue Then - req.benchmark_value = defaultBenchmark.Value - End If - - ' ------------------------------------------------------------ - ' Calculate - ' ------------------------------------------------------------ - Dim resp As cCBAM_CostCalculation_Response - - Try - resp = api.CalculateCost(req) - Catch ex As Exception - Return "Technischer Fehler bei Kostenberechnung: " & ex.Message - End Try - - If Not resp.success Then - Return $"FEHLER {resp.error.code}: {resp.error.message}" - End If - - ' ------------------------------------------------------------ - ' Ergebnis formatieren - ' ------------------------------------------------------------ - Dim d = resp.data - Dim s As String = "" - - s &= "CBAM Kostenberechnung" & vbCrLf - s &= "----------------------" & vbCrLf - s &= $"CN-Code: {cn_code}" & vbCrLf - s &= $"Ursprungsland: {req.country_code}" & vbCrLf - s &= $"Gewicht: {req.weight:N2} t" & vbCrLf - s &= vbCrLf - - 's &= $"Kosten: {d.cost:N2} {d.currency}" & vbCrLf - s &= $"CBAM Emission: {d.cbam_emission:N5}" & vbCrLf - s &= $"Benchmark: {d.benchmark:N5}" & vbCrLf - s &= $"Phase-Faktor: {d.phase_factor:P2}" & vbCrLf - s &= $"CO₂ Preis: {d.carbon_price:N2} EUR/t" & vbCrLf - - If Not String.IsNullOrWhiteSpace(d.info_message) Then - s &= vbCrLf & "Info: " & d.info_message & vbCrLf - End If - s &= vbCrLf - s &= $"CBAM Kosten: {d.cost:N2} {d.currency}" & vbCrLf - CBAM_COST = d.cost - CBAM_EMISSION = see_total - CBAM_BENCHMARK = benchmark_value - CBAM_FAKTOR = d.phase_factor - Return s - - End Function - - Public Shared Function calcCBAM_ByCertificatePrice(cn_code As String, weight As Object, country_code As String, certificate_price As Object, year As Object, Optional ByRef CBAM_COST As Decimal = -1, Optional ByRef CBAM_EMISSION As Decimal = -1, Optional ByRef CBAM_BENCHMARK As Decimal = -1, Optional ByRef CBAM_Faktor As Decimal = -1) As String - - VERAG_PROG_ALLGEMEIN.cChilkat_Helper.UnlockCilkat() - - ' ------------------------------------------------------------ - ' Validierung - ' ------------------------------------------------------------ - If cn_code Is Nothing OrElse Not IsNumeric(cn_code) _ - OrElse Not {4, 6, 8}.Contains(cn_code.Length) Then - Return "Fehler: CN-Code muss 4-, 6- oder 8-stellig numerisch sein" - End If - - If weight Is Nothing OrElse Not IsNumeric(weight) OrElse CDbl(weight) <= 0 Then - Return "Fehler: Gewicht muss > 0 sein" - End If - - If country_code Is Nothing OrElse country_code.Length <> 2 Then - Return "Fehler: country_code muss ISO-2 sein" - End If - - If certificate_price Is Nothing OrElse Not IsNumeric(certificate_price) OrElse CDbl(certificate_price) <= 0 Then - Return "Fehler: Zertifikatspreis muss > 0 sein" - End If - - If year Is Nothing Or Not IsNumeric(year) Then - Return "Fehler: ungültiges Jahr" - End If - - ' ------------------------------------------------------------ - ' SEE total (Default-Emission) laden - ' ------------------------------------------------------------ - Dim api As New cATEZ_Greenpulse_CBAM_CostCalculation() - Dim see_total As Decimal - Dim benchmark As Decimal - - Try - Dim defResp = api.GetCnCodeDefaults( - cn_code, - country_code.ToUpperInvariant(), - CInt(year) - ) - - If Not defResp.success Then - Return $"FEHLER DEFAULTS {defResp.error.code}: {defResp.error.message}" - End If - - If defResp.data.default_emission < 0 Then - Return "Fehler: Keine Default-Emission (see_total) verfügbar" - End If - - see_total = defResp.data.default_emission - benchmark = defResp.data.benchmark - - Catch ex As Exception - Return "Technischer Fehler beim Laden der CN-Code Defaults: " & ex.Message - End Try - - ' ------------------------------------------------------------ - ' Berechnung (ohne Phase-Faktor!) - ' ------------------------------------------------------------ - Dim w As Decimal = CDec(weight) - Dim price As Decimal = CDec(certificate_price) - - - ' --- Faktoren als echte Faktoren (nicht Prozent) --- - Dim cbamf As Decimal = 0D - Select Case CInt(year) - Case 2026 : cbamf = 0.975D - Case 2027 : cbamf = 0.95D - Case 2028 : cbamf = 0.9D - Case 2029 : cbamf = 0.775D - Case 2030 : cbamf = 0.515D - Case 2031 : cbamf = 0.39D - Case 2032 : cbamf = 0.265D - Case 2033 : cbamf = 0.14D - Case Else : cbamf = 0D - End Select - - - ' SEFA = Benchmark * CBAMF * CSCF - Dim sefa As Decimal = benchmark * cbamf - - ' Zertifikatspflicht je Tonne Ware - Dim certPerTon As Decimal = see_total - sefa - If certPerTon < 0D Then certPerTon = 0D - - ' Gesamte Zertifikatspflicht (t CO2e) - Dim certTotal As Decimal = certPerTon * w - - ' Kosten - Dim cbamCost As Decimal = certTotal * price - - ' --- Ausgabe --- - Dim s As String = "" - s &= "CBAM Kostenberechnung (Default EE mit SEFA-Abzug)" & vbCrLf - s &= "-----------------------------------------------------------" & vbCrLf - s &= $"CN-Code: {cn_code}" & vbCrLf - s &= $"Ursprungsland: {country_code.ToUpperInvariant()}" & vbCrLf - s &= $"Jahr: {year}" & vbCrLf - s &= $"Gewicht: {w:N2} t" & vbCrLf - s &= $"EE / SEE total (Default): {see_total:N6} t CO₂e/t" & vbCrLf - s &= $"Benchmark BMg: {benchmark:N6} t CO₂e/t" & vbCrLf - s &= $"CBAMF: {cbamf:N3}" - Console.WriteLine(s) - - CBAM_COST = cbamCost - CBAM_EMISSION = see_total - CBAM_BENCHMARK = benchmark - CBAM_Faktor = cbamf - - Return s - - End Function - - ' ------------------------------------------------------------------------ - ' Helpers - ' ------------------------------------------------------------------------ - Private Function ParseCalcResponse(jsonStr As String) _ - As cCBAM_CostCalculation_Response - - Dim json As New JsonObject() - json.Load(jsonStr) - - Dim r As New cCBAM_CostCalculation_Response With {.success = json.BoolOf("success")} - - If r.success Then - Dim d = json.ObjectOf("data") - r.data = New cCBAM_CostCalculation_Data With { - .cost = GetDec(d, "cost"), - .cbam_emission = GetDec(d, "cbam_emission"), - .benchmark = GetDec(d, "benchmark"), - .phase_factor = GetDec(d, "phase_factor"), - .carbon_price = GetDec(d, "carbon_price"), - .currency = d.StringOf("currency"), - .info_message = d.StringOf("info_message") - } - - Dim cd = d.ObjectOf("calculation_details") - r.data.calculation_details = New cCBAM_CostCalculation_Details With { - .cbam_emission = GetDec(cd, "cbam_emission"), - .benchmark = GetDec(cd, "benchmark"), - .adjusted_benchmark = GetDec(cd, "adjusted_benchmark"), - .emission_difference = GetDec(cd, "emission_difference"), - .weight = GetDec(cd, "weight"), - .carbon_price = GetDec(cd, "carbon_price") - } - Else - r.error = ParseError(json) - End If - - Return r - End Function - - Private Shared Function ParseError(j As JsonObject) As cCBAM_Error - Dim e = j.ObjectOf("error") - Return New cCBAM_Error With { - .code = e.StringOf("code"), - .message = e.StringOf("message"), - .statusCode = e.IntOf("statusCode") - } - End Function - - Private Shared Function GetDec(o As JsonObject, n As String) As Decimal - Dim s = o.StringOf(n).Replace(",", ".") - Dim v As Decimal - Decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, v) - Return v - End Function - - Private Shared Function GetNullableDec(o As JsonObject, n As String) As Decimal? - Dim s = o.StringOf(n) - If String.IsNullOrWhiteSpace(s) Then Return Nothing - Return GetDec(o, n) - End Function - -End Class - -' ============================================================================ -' DTOs -' ============================================================================ -Public Class cCBAM_CostCalculation_Request - Public Property cn_code As String - Public Property country_code As String - Public Property weight As Decimal - Public Property see_total As Decimal? - Public Property year As Integer? - Public Property benchmark_value As Decimal? - - Public Function ToJson() As String - Dim j As New JsonObject() - j.UpdateString("cn_code", cn_code) - j.UpdateString("country_code", country_code) - j.UpdateNumber("weight", weight.ToString(CultureInfo.InvariantCulture)) - If see_total.HasValue Then j.UpdateNumber("see_total", see_total.Value.ToString(CultureInfo.InvariantCulture)) - If year.HasValue Then j.UpdateInt("year", year.Value) - If benchmark_value.HasValue Then j.UpdateNumber("benchmark_value", benchmark_value.Value.ToString(CultureInfo.InvariantCulture)) - Return j.Emit() - End Function -End Class - -Public Class cCBAM_CostCalculation_Response - Public Property success As Boolean - Public Property data As cCBAM_CostCalculation_Data - Public Property [error] As cCBAM_Error -End Class - -Public Class cCBAM_CostCalculation_Data - Public Property cost As Decimal - Public Property cbam_emission As Decimal - Public Property benchmark As Decimal - Public Property phase_factor As Decimal - Public Property carbon_price As Decimal - Public Property currency As String - Public Property info_message As String - Public Property calculation_details As cCBAM_CostCalculation_Details -End Class - -Public Class cCBAM_CostCalculation_Details - Public Property cbam_emission As Decimal - Public Property benchmark As Decimal - Public Property adjusted_benchmark As Decimal - Public Property emission_difference As Decimal - Public Property weight As Decimal - Public Property carbon_price As Decimal -End Class - -Public Class cCBAM_CnCode_Response - Public Property success As Boolean - Public Property data As List(Of cCBAM_CnCode) - Public Property [error] As cCBAM_Error -End Class - -Public Class cCBAM_CnCode - Public Property cn_code As String - Public Property cn_description As String -End Class - -Public Class cCBAM_CnCodeDefaults_Response - Public Property success As Boolean - Public Property data As cCBAM_CnCodeDefaults - Public Property [error] As cCBAM_Error -End Class - -Public Class cCBAM_CnCodeDefaults - Public Property default_emission As Decimal - Public Property benchmark As Decimal? -End Class - -Public Class cCBAM_Error - Public Property code As String - Public Property message As String - Public Property statusCode As Integer -End Class +'' ============================================================================ +'' GreenPulse – CBAM Carbon Cost API +'' Single-file VB.NET implementation using Chilkat +'' Adapted to Postman Collection 29.12.2025 +'' ============================================================================ + +'Imports Chilkat +'Imports System.Globalization + +'' ============================================================================ +'' MAIN API CLASS +'' ============================================================================ + +' Public Class cATEZ_Greenpulse_CBAM_CostCalculation_OLD + +' Private ReadOnly _baseUrl As String = "https://test-greenpulse-api.singlewindow.io/api/v1-0/public" +' 'Private ReadOnly _baseUrl As String = "https://greenpulse-api.singlewindow.io/api/v1-0/public" +' Private ReadOnly _http As Http + +' Public Sub New() +' _http = New Http() +' _http.SetRequestHeader("Content-Type", "application/json") +' End Sub + +' ' ------------------------------------------------------------------------ +' ' GET /carbon-cost/cn-codes +' ' ------------------------------------------------------------------------ +' Public Function GetCnCodes() As cCBAM_CnCode_Response + +' Dim resp = _http.QuickGetObj(_baseUrl & "/carbon-cost/cn-codes") +' If resp Is Nothing Then Throw New Exception(_http.LastErrorText) + +' Dim json As New JsonObject() +' json.Load(resp.BodyStr) + +' Dim r As New cCBAM_CnCode_Response With {.success = json.BoolOf("success")} +' If r.success Then +' r.data = New List(Of cCBAM_CnCode) +' Dim a = json.ArrayOf("data") +' For i = 0 To a.Size - 1 +' Dim o = a.ObjectAt(i) +' r.data.Add(New cCBAM_CnCode With { +' .cn_code = o.StringOf("cn_code"), +' .cn_description = o.StringOf("cn_description") +' }) +' Next +' Else +' r.error = ParseError(json) +' End If + +' Return r +' End Function + + +' Public Function insertinto_Greendeal_CBAM_Trn(cCBAM_CnCode_Response As cCBAM_CnCode_Response) As Boolean + +' VERAG_PROG_ALLGEMEIN.cGreendeal_CBAM_Trn.DELETE_ALL() + +' For Each CN In cCBAM_CnCode_Response.data +' Dim CBAM As New VERAG_PROG_ALLGEMEIN.cGreendeal_CBAM_Trn + + +' CBAM.trnPattern = CN.cn_code & "%" +' CBAM.sektor = "" +' CBAM.ware = CN.cn_description +' CBAM.is_exclusion = False +' CBAM.is_active = True +' CBAM.start_date = "01.10.2023" +' CBAM.end_date = Nothing +' CBAM.SAVE() +' Next +' Return True +' End Function + +' ' ------------------------------------------------------------------------ +' ' GET /carbon-cost/cn-code-defaults +' ' ------------------------------------------------------------------------ +' Public Function GetCnCodeDefaults( +' cn_code As String, +' country_code As String, +' Optional year As Integer? = Nothing +' ) As cCBAM_CnCodeDefaults_Response + +' Dim url = $"{_baseUrl}/carbon-cost/cn-code-defaults?cn_code={cn_code}&country_code={country_code}" +' If year.HasValue Then url &= $"&year={year.Value}" + +' Console.WriteLine($"[GetCnCodeDefaults] Request URL: {url}") + +' Dim resp = _http.QuickGetObj(url) +' If resp Is Nothing Then +' Console.WriteLine("[GetCnCodeDefaults] HTTP request failed: " & _http.LastErrorText) +' Throw New Exception(_http.LastErrorText) +' End If + +' Console.WriteLine("[GetCnCodeDefaults] HTTP response received.") +' If resp.BodyStr IsNot Nothing Then +' Console.WriteLine("[GetCnCodeDefaults] Response body length: " & resp.BodyStr.Length) +' Console.WriteLine("[GetCnCodeDefaults] Body: " & resp.BodyStr) +' Else +' Console.WriteLine("[GetCnCodeDefaults] Response body is empty.") +' End If + +' Dim json As New JsonObject() +' json.Load(resp.BodyStr) + +' Dim successFlag = json.BoolOf("success") +' Console.WriteLine($"[GetCnCodeDefaults] JSON 'success' flag: {successFlag}") + +' Dim r As New cCBAM_CnCodeDefaults_Response With {.success = successFlag} + +' If r.success Then +' Dim d = json.ObjectOf("data") +' r.data = New cCBAM_CnCodeDefaults With { +' .default_emission = GetDec(d, "default_emission"), +' .benchmark = GetNullableDec(d, "benchmark") +' } +' Console.WriteLine($"[GetCnCodeDefaults] Parsed defaults: default_emission={r.data.default_emission}, benchmark={(If(r.data.benchmark.HasValue, r.data.benchmark.Value.ToString(), "null"))}") +' Else +' r.error = ParseError(json) +' Console.WriteLine($"[GetCnCodeDefaults] API error: code={r.error.code}, message={r.error.message}, statusCode={r.error.statusCode}") +' End If + +' Return r +' End Function + +' ' ------------------------------------------------------------------------ +' ' POST /carbon-cost/calculate +' ' ------------------------------------------------------------------------ +' Public Function CalculateCost(req As cCBAM_CostCalculation_Request) _ +' As cCBAM_CostCalculation_Response + +' VERAG_PROG_ALLGEMEIN.cChilkat_Helper.UnlockCilkat() + +' Dim resp = _http.PostJson2( +' _baseUrl & "/carbon-cost/calculate", +' "application/json", +' req.ToJson() +' ) + +' If resp Is Nothing Then Throw New Exception(_http.LastErrorText) +' Return ParseCalcResponse(resp.BodyStr) +' End Function + +' ' ------------------------------------------------------------------------ +' ' calcCBAM – Convenience Wrapper +' ' ------------------------------------------------------------------------ +' ' ------------------------------------------------------------------------ +' ' calcCBAM – mit automatischem Fallback auf CN-Code Defaults +' ' ------------------------------------------------------------------------ +' Public Shared Function calcCBAM(cn_code As String, weight As Object, country_code As String, Optional ByRef see_total As Object = Nothing, Optional year As Object = Nothing, Optional ByRef benchmark_value As Object = Nothing, Optional ByRef CBAM_COST As Decimal = -1, Optional ByRef CBAM_EMISSION As Decimal = -1, Optional ByRef CBAM_BENCHMARK As Decimal = -1, Optional ByRef CBAM_FAKTOR As Decimal = -1) As String + +' ' ------------------------------------------------------------ +' ' Basis-Validierung +' ' ------------------------------------------------------------ +' If cn_code Is Nothing OrElse Not IsNumeric(cn_code) _ +' OrElse Not {4, 6, 8}.Contains(cn_code.Length) Then +' Return "Fehler: CN-Code muss 4-, 6- oder 8-stellig numerisch sein" +' End If + +' If weight Is Nothing OrElse Not IsNumeric(weight) OrElse CDbl(weight) <= 0 Then +' Return "Fehler: Gewicht muss > 0 sein" +' End If + +' If country_code Is Nothing OrElse country_code.Length <> 2 Then +' Return "Fehler: country_code muss ISO-2 sein" +' End If + +' If year IsNot Nothing AndAlso Not IsNumeric(year) Then +' Return "Fehler: ungültiges Jahr" +' End If + +' ' ------------------------------------------------------------ +' ' API initialisieren +' ' ------------------------------------------------------------ +' Dim api As New cATEZ_Greenpulse_CBAM_CostCalculation() + +' ' ------------------------------------------------------------ +' ' Prüfen, ob Defaults benötigt werden +' ' ------------------------------------------------------------ +' Dim needDefaults As Boolean = False + +' If see_total Is Nothing OrElse Not IsNumeric(see_total) OrElse CDbl(see_total) < 0 Then +' needDefaults = True +' End If + +' If benchmark_value Is Nothing OrElse Not IsNumeric(benchmark_value) OrElse CDbl(benchmark_value) < 0 Then +' needDefaults = True +' End If + +' Dim defaultEmission As Decimal? = Nothing +' Dim defaultBenchmark As Decimal? = Nothing + +' If needDefaults Then +' Try +' Dim defResp = api.GetCnCodeDefaults( +' cn_code, +' country_code.ToUpperInvariant(), +' If(IsNumeric(year), CInt(year), CType(Nothing, Integer?)) +' ) + +' If defResp.success Then +' defaultEmission = defResp.data.default_emission +' defaultBenchmark = defResp.data.benchmark + +' benchmark_value = defaultBenchmark +' see_total = defaultEmission +' Else +' Return $"FEHLER DEFAULTS {defResp.error.code}: {defResp.error.message}" +' End If + +' Catch ex As Exception +' Return "Technischer Fehler beim Laden der CN-Code Defaults: " & ex.Message +' End Try +' End If + +' ' ------------------------------------------------------------ +' ' Request aufbauen +' ' ------------------------------------------------------------ +' Dim req As New cCBAM_CostCalculation_Request With { +' .cn_code = cn_code, +' .country_code = country_code.ToUpperInvariant(), +' .weight = CDec(weight) +' } + +' ' see_total +' If IsNumeric(see_total) AndAlso CDbl(see_total) >= 0 Then +' req.see_total = CDec(see_total) +' ElseIf defaultEmission.HasValue Then +' req.see_total = defaultEmission.Value +' End If + +' ' year +' If IsNumeric(year) Then +' req.year = CInt(year) +' End If + +' ' benchmark_value +' If IsNumeric(benchmark_value) AndAlso CDbl(benchmark_value) >= 0 Then +' req.benchmark_value = CDec(benchmark_value) +' ElseIf defaultBenchmark.HasValue Then +' req.benchmark_value = defaultBenchmark.Value +' End If + +' ' ------------------------------------------------------------ +' ' Calculate +' ' ------------------------------------------------------------ +' Dim resp As cCBAM_CostCalculation_Response + +' Try +' resp = api.CalculateCost(req) +' Catch ex As Exception +' Return "Technischer Fehler bei Kostenberechnung: " & ex.Message +' End Try + +' If Not resp.success Then +' Return $"FEHLER {resp.error.code}: {resp.error.message}" +' End If + +' ' ------------------------------------------------------------ +' ' Ergebnis formatieren +' ' ------------------------------------------------------------ +' Dim d = resp.data +' Dim s As String = "" + +' s &= "CBAM Kostenberechnung" & vbCrLf +' s &= "----------------------" & vbCrLf +' s &= $"CN-Code: {cn_code}" & vbCrLf +' s &= $"Ursprungsland: {req.country_code}" & vbCrLf +' s &= $"Gewicht: {req.weight:N2} t" & vbCrLf +' s &= vbCrLf + +' 's &= $"Kosten: {d.cost:N2} {d.currency}" & vbCrLf +' s &= $"CBAM Emission: {d.cbam_emission:N5}" & vbCrLf +' s &= $"Benchmark: {d.benchmark:N5}" & vbCrLf +' s &= $"Phase-Faktor: {d.phase_factor:P2}" & vbCrLf +' s &= $"CO₂ Preis: {d.carbon_price:N2} EUR/t" & vbCrLf + +' If Not String.IsNullOrWhiteSpace(d.info_message) Then +' s &= vbCrLf & "Info: " & d.info_message & vbCrLf +' End If +' s &= vbCrLf +' s &= $"CBAM Kosten: {d.cost:N2} {d.currency}" & vbCrLf +' CBAM_COST = d.cost +' CBAM_EMISSION = see_total +' CBAM_BENCHMARK = benchmark_value +' CBAM_FAKTOR = d.phase_factor +' Return s + +' End Function + +' Public Shared Function calcCBAM_ByCertificatePrice(cn_code As String, weight As Object, country_code As String, certificate_price As Object, year As Object, Optional ByRef CBAM_COST As Decimal = -1, Optional ByRef CBAM_EMISSION As Decimal = -1, Optional ByRef CBAM_BENCHMARK As Decimal = -1, Optional ByRef CBAM_Faktor As Decimal = -1) As String + +' VERAG_PROG_ALLGEMEIN.cChilkat_Helper.UnlockCilkat() + +' ' ------------------------------------------------------------ +' ' Validierung +' ' ------------------------------------------------------------ +' If cn_code Is Nothing OrElse Not IsNumeric(cn_code) _ +' OrElse Not {4, 6, 8}.Contains(cn_code.Length) Then +' Return "Fehler: CN-Code muss 4-, 6- oder 8-stellig numerisch sein" +' End If + +' If weight Is Nothing OrElse Not IsNumeric(weight) OrElse CDbl(weight) <= 0 Then +' Return "Fehler: Gewicht muss > 0 sein" +' End If + +' If country_code Is Nothing OrElse country_code.Length <> 2 Then +' Return "Fehler: country_code muss ISO-2 sein" +' End If + +' If certificate_price Is Nothing OrElse Not IsNumeric(certificate_price) OrElse CDbl(certificate_price) <= 0 Then +' Return "Fehler: Zertifikatspreis muss > 0 sein" +' End If + +' If year Is Nothing Or Not IsNumeric(year) Then +' Return "Fehler: ungültiges Jahr" +' End If + +' ' ------------------------------------------------------------ +' ' SEE total (Default-Emission) laden +' ' ------------------------------------------------------------ +' Dim api As New cATEZ_Greenpulse_CBAM_CostCalculation() +' Dim see_total As Decimal +' Dim benchmark As Decimal + +' Try +' Dim defResp = api.GetCnCodeDefaults( +' cn_code, +' country_code.ToUpperInvariant(), +' CInt(year) +' ) + +' If Not defResp.success Then +' Return $"FEHLER DEFAULTS {defResp.error.code}: {defResp.error.message}" +' End If + +' If defResp.data.default_emission < 0 Then +' Return "Fehler: Keine Default-Emission (see_total) verfügbar" +' End If + +' see_total = defResp.data.default_emission +' benchmark = defResp.data.benchmark + +' Catch ex As Exception +' Return "Technischer Fehler beim Laden der CN-Code Defaults: " & ex.Message +' End Try + +' ' ------------------------------------------------------------ +' ' Berechnung (ohne Phase-Faktor!) +' ' ------------------------------------------------------------ +' Dim w As Decimal = CDec(weight) +' Dim price As Decimal = CDec(certificate_price) + + +' ' --- Faktoren als echte Faktoren (nicht Prozent) --- +' Dim cbamf As Decimal = 0D +' Select Case CInt(year) +' Case 2026 : cbamf = 0.975D +' Case 2027 : cbamf = 0.95D +' Case 2028 : cbamf = 0.9D +' Case 2029 : cbamf = 0.775D +' Case 2030 : cbamf = 0.515D +' Case 2031 : cbamf = 0.39D +' Case 2032 : cbamf = 0.265D +' Case 2033 : cbamf = 0.14D +' Case Else : cbamf = 0D +' End Select + + +' ' SEFA = Benchmark * CBAMF * CSCF +' Dim sefa As Decimal = benchmark * cbamf + +' ' Zertifikatspflicht je Tonne Ware +' Dim certPerTon As Decimal = see_total - sefa +' If certPerTon < 0D Then certPerTon = 0D + +' ' Gesamte Zertifikatspflicht (t CO2e) +' Dim certTotal As Decimal = certPerTon * w + +' ' Kosten +' Dim cbamCost As Decimal = certTotal * price + +' ' --- Ausgabe --- +' Dim s As String = "" +' s &= "CBAM Kostenberechnung (Default EE mit SEFA-Abzug)" & vbCrLf +' s &= "-----------------------------------------------------------" & vbCrLf +' s &= $"CN-Code: {cn_code}" & vbCrLf +' s &= $"Ursprungsland: {country_code.ToUpperInvariant()}" & vbCrLf +' s &= $"Jahr: {year}" & vbCrLf +' s &= $"Gewicht: {w:N2} t" & vbCrLf +' s &= $"EE / SEE total (Default): {see_total:N6} t CO₂e/t" & vbCrLf +' s &= $"Benchmark BMg: {benchmark:N6} t CO₂e/t" & vbCrLf +' s &= $"CBAMF: {cbamf:N3}" +' Console.WriteLine(s) + +' CBAM_COST = cbamCost +' CBAM_EMISSION = see_total +' CBAM_BENCHMARK = benchmark +' CBAM_Faktor = cbamf + +' Return s + +' End Function + +' ' ------------------------------------------------------------------------ +' ' Helpers +' ' ------------------------------------------------------------------------ +' Private Function ParseCalcResponse(jsonStr As String) _ +' As cCBAM_CostCalculation_Response + +' Dim json As New JsonObject() +' json.Load(jsonStr) + +' Dim r As New cCBAM_CostCalculation_Response With {.success = json.BoolOf("success")} + +' If r.success Then +' Dim d = json.ObjectOf("data") +' r.data = New cCBAM_CostCalculation_Data With { +' .cost = GetDec(d, "cost"), +' .cbam_emission = GetDec(d, "cbam_emission"), +' .benchmark = GetDec(d, "benchmark"), +' .phase_factor = GetDec(d, "phase_factor"), +' .carbon_price = GetDec(d, "carbon_price"), +' .currency = d.StringOf("currency"), +' .info_message = d.StringOf("info_message") +' } + +' Dim cd = d.ObjectOf("calculation_details") +' r.data.calculation_details = New cCBAM_CostCalculation_Details With { +' .cbam_emission = GetDec(cd, "cbam_emission"), +' .benchmark = GetDec(cd, "benchmark"), +' .adjusted_benchmark = GetDec(cd, "adjusted_benchmark"), +' .emission_difference = GetDec(cd, "emission_difference"), +' .weight = GetDec(cd, "weight"), +' .carbon_price = GetDec(cd, "carbon_price") +' } +' Else +' r.error = ParseError(json) +' End If + +' Return r +' End Function + +' Private Shared Function ParseError(j As JsonObject) As cCBAM_Error +' Dim e = j.ObjectOf("error") +' Return New cCBAM_Error With { +' .code = e.StringOf("code"), +' .message = e.StringOf("message"), +' .statusCode = e.IntOf("statusCode") +' } +' End Function + +' Private Shared Function GetDec(o As JsonObject, n As String) As Decimal +' Dim s = o.StringOf(n).Replace(",", ".") +' Dim v As Decimal +' Decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, v) +' Return v +' End Function + +' Private Shared Function GetNullableDec(o As JsonObject, n As String) As Decimal? +' Dim s = o.StringOf(n) +' If String.IsNullOrWhiteSpace(s) Then Return Nothing +' Return GetDec(o, n) +' End Function + +' End Class + +' ' ============================================================================ +' ' DTOs +' ' ============================================================================ +' Public Class cCBAM_CostCalculation_Request +' Public Property cn_code As String +' Public Property country_code As String +' Public Property weight As Decimal +' Public Property see_total As Decimal? +' Public Property year As Integer? +' Public Property benchmark_value As Decimal? + +' Public Function ToJson() As String +' Dim j As New JsonObject() +' j.UpdateString("cn_code", cn_code) +' j.UpdateString("country_code", country_code) +' j.UpdateNumber("weight", weight.ToString(CultureInfo.InvariantCulture)) +' If see_total.HasValue Then j.UpdateNumber("see_total", see_total.Value.ToString(CultureInfo.InvariantCulture)) +' If year.HasValue Then j.UpdateInt("year", year.Value) +' If benchmark_value.HasValue Then j.UpdateNumber("benchmark_value", benchmark_value.Value.ToString(CultureInfo.InvariantCulture)) +' Return j.Emit() +' End Function +'End Class + +'Public Class cCBAM_CostCalculation_Response +' Public Property success As Boolean +' Public Property data As cCBAM_CostCalculation_Data +' Public Property [error] As cCBAM_Error +'End Class + +'Public Class cCBAM_CostCalculation_Data +' Public Property cost As Decimal +' Public Property cbam_emission As Decimal +' Public Property benchmark As Decimal +' Public Property phase_factor As Decimal +' Public Property carbon_price As Decimal +' Public Property currency As String +' Public Property info_message As String +' Public Property calculation_details As cCBAM_CostCalculation_Details +'End Class + +'Public Class cCBAM_CostCalculation_Details +' Public Property cbam_emission As Decimal +' Public Property benchmark As Decimal +' Public Property adjusted_benchmark As Decimal +' Public Property emission_difference As Decimal +' Public Property weight As Decimal +' Public Property carbon_price As Decimal +'End Class + +'Public Class cCBAM_CnCode_Response +' Public Property success As Boolean +' Public Property data As List(Of cCBAM_CnCode) +' Public Property [error] As cCBAM_Error +'End Class + +'Public Class cCBAM_CnCode +' Public Property cn_code As String +' Public Property cn_description As String +'End Class + +'Public Class cCBAM_CnCodeDefaults_Response +' Public Property success As Boolean +' Public Property data As cCBAM_CnCodeDefaults +' Public Property [error] As cCBAM_Error +'End Class + +'Public Class cCBAM_CnCodeDefaults +' Public Property default_emission As Decimal +' Public Property benchmark As Decimal? +'End Class + +' Public Class cCBAM_Error +' Public Property code As String +' Public Property message As String +' Public Property statusCode As Integer +' End Class diff --git a/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/GREENPULSE/cATEZ_Greenpulse_CBAM_CostCalculation_NEU.vb b/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/GREENPULSE/cATEZ_Greenpulse_CBAM_CostCalculation_NEU.vb new file mode 100644 index 00000000..60ead4ed --- /dev/null +++ b/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/GREENPULSE/cATEZ_Greenpulse_CBAM_CostCalculation_NEU.vb @@ -0,0 +1,1015 @@ +' ============================================================================ +' GreenPulse – CBAM Carbon Cost API +' Single-file VB.NET implementation using Chilkat +' Adapted to current Postman Collection +' +' Bestehende öffentliche Logik / Methodensignaturen wurden beibehalten. +' Angepasst wurden: +' - aktuelle Endpoints +' - API-Key Header +' - Request-Felder der neuen API +' - neues Response-Modell mit scenarios[] +' ============================================================================ + +Imports Chilkat +Imports System.Globalization +Imports System.Linq + +' ============================================================================ +' MAIN API CLASS +' ============================================================================ +Public Class cATEZ_Greenpulse_CBAM_CostCalculation + + ' Wichtig: + ' _baseUrl enthält bereits /public, daher werden unten nur die Pfade + ' unterhalb von /public ergänzt. + 'Private ReadOnly _baseUrl As String = "https://test-greenpulse-api.singlewindow.io/api/v1-0/public" + Private ReadOnly _baseUrl As String = "https://greenpulse-api.singlewindow.io/api/v1-0/public" + + ' Optional hier fest hinterlegen. + ' Alternativ können die Environment-Variablen + ' GREENPULSE_PUBLIC_KEY_ID und GREENPULSE_PUBLIC_KEY_SECRET verwendet werden. + Private Const _configuredPublicKeyId As String = "pk_60b20843c4d54e019262decdeac74dd3" + Private Const _configuredPublicKeySecret As String = "ps_59167796d7b44dcea4d92949ac5965c5c0b507c7b6714f6f820b1fe1410970f8" + + Private ReadOnly _http As Http + + Public Sub New() + + VERAG_PROG_ALLGEMEIN.cChilkat_Helper.UnlockCilkat() + + _http = New Http() + _http.SetRequestHeader("Content-Type", "application/json") + + + _http.SetRequestHeader("x-public-key-id", _configuredPublicKeyId) + _http.SetRequestHeader("x-public-key-secret", _configuredPublicKeySecret) + + + + 'Dim keyId As String = publicKeyId + 'Dim keySecret As String = publicKeySecret + + 'If String.IsNullOrWhiteSpace(keyId) Then + ' keyId = Environment.GetEnvironmentVariable("GREENPULSE_PUBLIC_KEY_ID") + 'End If + 'If String.IsNullOrWhiteSpace(keySecret) Then + ' keySecret = Environment.GetEnvironmentVariable("GREENPULSE_PUBLIC_KEY_SECRET") + 'End If + + 'If String.IsNullOrWhiteSpace(keyId) Then keyId = _configuredPublicKeyId + 'If String.IsNullOrWhiteSpace(keySecret) Then keySecret = _configuredPublicKeySecret + + 'If Not String.IsNullOrWhiteSpace(keyId) Then + ' _http.SetRequestHeader("x-public-key-id", keyId) + 'End If + + 'If Not String.IsNullOrWhiteSpace(keySecret) Then + ' _http.SetRequestHeader("x-public-key-secret", keySecret) + 'End If + End Sub + + ' ------------------------------------------------------------------------ + ' GET /cbam-cn-codes + ' ------------------------------------------------------------------------ + Public Function GetCnCodes() As cCBAM_CnCode_Response + + Dim resp = _http.QuickGetObj(_baseUrl & "/cbam-cn-codes") + If resp Is Nothing Then Throw New Exception(_http.LastErrorText) + + Dim json As New JsonObject() + json.Load(resp.BodyStr) + + Dim r As New cCBAM_CnCode_Response With { + .success = json.BoolOf("success") + } + + If r.success Then + r.data = New List(Of cCBAM_CnCode) + + Dim a = json.ArrayOf("data") + If a IsNot Nothing Then + For i = 0 To a.Size - 1 + Dim o = a.ObjectAt(i) + r.data.Add(New cCBAM_CnCode With { + .cn_code = o.StringOf("cn_code"), + .cn_description = o.StringOf("cn_description") + }) + Next + End If + Else + r.error = ParseError(json) + End If + + Return r + End Function + + Public Function insertinto_Greendeal_CBAM_Trn(cCBAM_CnCode_Response As cCBAM_CnCode_Response) As Boolean + + VERAG_PROG_ALLGEMEIN.cGreendeal_CBAM_Trn.DELETE_ALL() + + For Each CN In cCBAM_CnCode_Response.data + Dim CBAM As New VERAG_PROG_ALLGEMEIN.cGreendeal_CBAM_Trn + + CBAM.trnPattern = CN.cn_code & "%" + CBAM.sektor = "" + CBAM.ware = CN.cn_description + CBAM.is_exclusion = False + CBAM.is_active = True + CBAM.start_date = "01.10.2023" + CBAM.end_date = Nothing + CBAM.SAVE() + Next + + Return True + End Function + + ' ------------------------------------------------------------------------ + ' GET /cn-code-defaults/{cn_code} + ' ------------------------------------------------------------------------ + Public Function GetCnCodeDefaults( + cn_code As String, + country_code As String, + Optional year As Integer? = Nothing + ) As cCBAM_CnCodeDefaults_Response + + Dim url As String = + $"{_baseUrl}/cn-code-defaults/{cn_code}?country_code={country_code}" + + If year.HasValue Then + url &= $"&year={year.Value}" + End If + + + + + Dim resp = _http.QuickGetObj(url) + + If resp Is Nothing Then + Console.WriteLine("[GetCnCodeDefaults] HTTP request failed: " & _http.LastErrorText) + Throw New Exception(_http.LastErrorText) + End If + + Console.WriteLine("[GetCnCodeDefaults] HTTP response received.") + + If resp.BodyStr IsNot Nothing Then + Console.WriteLine("[GetCnCodeDefaults] Response body length: " & resp.BodyStr.Length) + Console.WriteLine("[GetCnCodeDefaults] Body: " & resp.BodyStr) + Else + Console.WriteLine("[GetCnCodeDefaults] Response body is empty.") + End If + + Dim json As New JsonObject() + json.Load(resp.BodyStr) + + Dim successFlag = json.BoolOf("success") + Console.WriteLine($"[GetCnCodeDefaults] JSON 'success' flag: {successFlag}") + + Dim r As New cCBAM_CnCodeDefaults_Response With { + .success = successFlag + } + + If r.success Then + + Dim d = json.ObjectOf("data") + + If d IsNot Nothing Then + r.data = New cCBAM_CnCodeDefaults With { + .cn_code = d.StringOf("cn_code"), + .cn_description = d.StringOf("cn_description"), + .default_emission = GetDec(d, "default_emission"), + .benchmark = GetNullableDec(d, "benchmark"), + .country_code = d.StringOf("country_code"), + .fallback_used = d.BoolOf("fallback_used"), + .year = GetNullableInt(d, "year") + } + End If + + If r.data IsNot Nothing Then + Console.WriteLine( + $"[GetCnCodeDefaults] Parsed defaults: default_emission={r.data.default_emission}, benchmark={(If(r.data.benchmark.HasValue, r.data.benchmark.Value.ToString(), "null"))}" + ) + End If + + Else + r.error = ParseError(json) + + If r.error IsNot Nothing Then + Console.WriteLine( + $"[GetCnCodeDefaults] API error: code={r.error.code}, message={r.error.message}, statusCode={r.error.statusCode}" + ) + End If + End If + + Return r + End Function + + ' ------------------------------------------------------------------------ + ' POST /carbon-cost/cost-calculate + ' ------------------------------------------------------------------------ + Public Function CalculateCost(req As cCBAM_CostCalculation_Request) _ + As cCBAM_CostCalculation_Response + + VERAG_PROG_ALLGEMEIN.cChilkat_Helper.UnlockCilkat() + + + + _http.SetRequestHeader("x-public-key-id", _configuredPublicKeyId) + _http.SetRequestHeader("x-public-key-secret", _configuredPublicKeySecret) + + Dim resp = _http.PostJson2( + _baseUrl & "/carbon-cost/cost-calculate", + "application/json", + req.ToJson() + ) + + If resp Is Nothing Then + Throw New Exception(_http.LastErrorText) + End If + + Return ParseCalcResponse(resp.BodyStr) + End Function + + ' ------------------------------------------------------------------------ + ' calcCBAM – mit automatischem Fallback auf CN-Code Defaults + ' + ' Methodensignatur und Aufruflogik bewusst beibehalten. + ' ------------------------------------------------------------------------ + Public Shared Function calcCBAM( + cn_code As String, + weight As Object, + country_code As String, + Optional ByRef see_total As Object = Nothing, + Optional year As Object = Nothing, + Optional ByRef benchmark_value As Object = Nothing, + Optional ByRef CBAM_COST As Decimal = -1, + Optional ByRef CBAM_EMISSION As Decimal = -1, + Optional ByRef CBAM_BENCHMARK As Decimal = -1, + Optional ByRef CBAM_FAKTOR As Decimal = -1 + ) As String + + ' ------------------------------------------------------------ + ' Basis-Validierung + ' ------------------------------------------------------------ + If cn_code Is Nothing OrElse + Not IsNumeric(cn_code) OrElse + Not {4, 6, 8}.Contains(cn_code.Length) Then + + Return "Fehler: CN-Code muss 4-, 6- oder 8-stellig numerisch sein" + End If + + If weight Is Nothing OrElse + Not IsNumeric(weight) OrElse + CDbl(weight) <= 0 Then + + Return "Fehler: Gewicht muss > 0 sein" + End If + + If country_code Is Nothing OrElse country_code.Length <> 2 Then + Return "Fehler: country_code muss ISO-2 sein" + End If + + If year IsNot Nothing AndAlso Not IsNumeric(year) Then + Return "Fehler: ungültiges Jahr" + End If + + ' ------------------------------------------------------------ + ' API initialisieren + ' ------------------------------------------------------------ + Dim api As New cATEZ_Greenpulse_CBAM_CostCalculation() + + ' ------------------------------------------------------------ + ' Prüfen, ob Defaults benötigt werden + ' ------------------------------------------------------------ + Dim needDefaults As Boolean = False + + If see_total Is Nothing OrElse + Not IsNumeric(see_total) OrElse + CDbl(see_total) < 0 Then + + needDefaults = True + End If + + If benchmark_value Is Nothing OrElse + Not IsNumeric(benchmark_value) OrElse + CDbl(benchmark_value) < 0 Then + + needDefaults = True + End If + + Dim defaultEmission As Decimal? = Nothing + Dim defaultBenchmark As Decimal? = Nothing + + If needDefaults Then + Try + + Dim defResp = api.GetCnCodeDefaults( + cn_code, + country_code.ToUpperInvariant(), + If(IsNumeric(year), + CInt(year), + CType(Nothing, Integer?)) + ) + + If defResp.success Then + + If defResp.data Is Nothing Then + Return "FEHLER DEFAULTS: Keine Default-Daten zurückgegeben" + End If + + defaultEmission = defResp.data.default_emission + defaultBenchmark = defResp.data.benchmark + + benchmark_value = defaultBenchmark + see_total = defaultEmission + + Else + Return $"FEHLER DEFAULTS {defResp.error.code}: {defResp.error.message}" + End If + + Catch ex As Exception + Return "Technischer Fehler beim Laden der CN-Code Defaults: " & ex.Message + End Try + End If + + ' ------------------------------------------------------------ + ' Request aufbauen + ' ------------------------------------------------------------ + Dim req As New cCBAM_CostCalculation_Request With { + .cn_code = cn_code, + .country_code = country_code.ToUpperInvariant(), + .origin = country_code.ToUpperInvariant(), + .weight = CDec(weight) + } + + ' see_total + If IsNumeric(see_total) AndAlso CDbl(see_total) >= 0 Then + req.see_total = CDec(see_total) + ElseIf defaultEmission.HasValue Then + req.see_total = defaultEmission.Value + End If + + ' year + ' Die neue API kennt kein eigenes year-Feld bei cost-calculate, + ' sondern verwendet date. Damit die bestehende year-Logik erhalten + ' bleibt, wird year weiterhin im DTO gehalten und zusätzlich auf + ' ein Datum innerhalb dieses Jahres abgebildet. + If IsNumeric(year) Then + req.year = CInt(year) + req.date = CInt(year).ToString("0000") & "-01-01" + End If + + ' benchmark_value + ' Wird für bestehende Logik / Rückgabewerte beibehalten. + ' Die neue API akzeptiert benchmark_value nicht mehr im Request. + If IsNumeric(benchmark_value) AndAlso CDbl(benchmark_value) >= 0 Then + req.benchmark_value = CDec(benchmark_value) + ElseIf defaultBenchmark.HasValue Then + req.benchmark_value = defaultBenchmark.Value + End If + + ' ------------------------------------------------------------ + ' Calculate + ' ------------------------------------------------------------ + Dim resp As cCBAM_CostCalculation_Response + + Try + resp = api.CalculateCost(req) + Catch ex As Exception + Return "Technischer Fehler bei Kostenberechnung: " & ex.Message + End Try + + If Not resp.success Then + Return $"FEHLER {resp.error.code}: {resp.error.message}" + End If + + If resp.data Is Nothing Then + Return "FEHLER: Keine Berechnungsdaten von der API zurückgegeben" + End If + + ' ------------------------------------------------------------ + ' Passendes Szenario bestimmen + ' + ' Bestehende Logik liefert ein einzelnes Ergebnis. + ' Die neue API liefert scenarios[]. + ' + ' Priorität: + ' 1. actual + ' 2. default + ' 3. erstes vorhandenes Szenario + ' ------------------------------------------------------------ + Dim scenario As cCBAM_CostCalculation_Scenario = Nothing + + If resp.data.scenarios IsNot Nothing Then + scenario = resp.data.scenarios. + FirstOrDefault(Function(x) String.Equals(x.type, "actual", StringComparison.OrdinalIgnoreCase)) + + If scenario Is Nothing Then + scenario = resp.data.scenarios. + FirstOrDefault(Function(x) String.Equals(x.type, "default", StringComparison.OrdinalIgnoreCase)) + End If + + If scenario Is Nothing Then + scenario = resp.data.scenarios.FirstOrDefault() + End If + End If + + If scenario Is Nothing Then + Return "FEHLER: Kein Berechnungsszenario von der API zurückgegeben" + End If + + ' ------------------------------------------------------------ + ' Ergebnis formatieren + ' ------------------------------------------------------------ + Dim d = resp.data + Dim s As String = "" + + Dim carbonPrice As Decimal = 0D + + If d.price_resolution IsNot Nothing AndAlso + d.price_resolution.price.HasValue Then + + carbonPrice = d.price_resolution.price.Value + ElseIf scenario.calculation_details IsNot Nothing AndAlso + scenario.calculation_details.carbon_price.HasValue Then + + carbonPrice = scenario.calculation_details.carbon_price.Value + End If + + Dim benchmarkForOutput As Decimal = 0D + + If IsNumeric(benchmark_value) Then + benchmarkForOutput = CDec(benchmark_value) + ElseIf scenario.calculation_details IsNot Nothing AndAlso + scenario.calculation_details.benchmark.HasValue Then + + benchmarkForOutput = scenario.calculation_details.benchmark.Value + End If + + s &= "CBAM Kostenberechnung" & vbCrLf + s &= "----------------------" & vbCrLf + s &= $"CN-Code: {cn_code}" & vbCrLf + s &= $"Ursprungsland: {req.country_code}" & vbCrLf + s &= $"Gewicht: {req.weight:N2} t" & vbCrLf + s &= vbCrLf + + s &= $"CBAM Emission: {scenario.cbam_emission:N5}" & vbCrLf + s &= $"Benchmark: {benchmarkForOutput:N5}" & vbCrLf + s &= $"Phase-Faktor: {d.phase_factor:P2}" & vbCrLf + s &= $"CO₂ Preis: {carbonPrice:N2} EUR/t" & vbCrLf + + If d.warnings IsNot Nothing AndAlso d.warnings.Count > 0 Then + For Each warning In d.warnings + If warning IsNot Nothing AndAlso + Not String.IsNullOrWhiteSpace(warning.message) Then + + s &= vbCrLf & "Info: " & warning.message & vbCrLf + End If + Next + End If + + s &= vbCrLf + s &= $"CBAM Kosten: {scenario.cost:N2} EUR" & vbCrLf + + CBAM_COST = scenario.cost + + ' Bestehendes Verhalten bewusst beibehalten: + ' CBAM_EMISSION gibt see_total zurück, nicht scenario.cbam_emission. + If IsNumeric(see_total) Then + CBAM_EMISSION = CDec(see_total) + Else + CBAM_EMISSION = scenario.cbam_emission + End If + + If IsNumeric(benchmark_value) Then + CBAM_BENCHMARK = CDec(benchmark_value) + Else + CBAM_BENCHMARK = benchmarkForOutput + End If + + CBAM_FAKTOR = d.phase_factor + + Return s + End Function + + ' ------------------------------------------------------------------------ + ' calcCBAM_ByCertificatePrice + ' + ' Bestehende lokale Berechnungslogik unverändert. + ' Nur GetCnCodeDefaults verwendet intern den neuen API-Endpoint. + ' ------------------------------------------------------------------------ + Public Shared Function calcCBAM_ByCertificatePrice( + cn_code As String, + weight As Object, + country_code As String, + certificate_price As Object, + year As Object, + Optional ByRef CBAM_COST As Decimal = -1, + Optional ByRef CBAM_EMISSION As Decimal = -1, + Optional ByRef CBAM_BENCHMARK As Decimal = -1, + Optional ByRef CBAM_Faktor As Decimal = -1 + ) As String + + VERAG_PROG_ALLGEMEIN.cChilkat_Helper.UnlockCilkat() + + ' ------------------------------------------------------------ + ' Validierung + ' ------------------------------------------------------------ + If cn_code Is Nothing OrElse + Not IsNumeric(cn_code) OrElse + Not {4, 6, 8}.Contains(cn_code.Length) Then + + Return "Fehler: CN-Code muss 4-, 6- oder 8-stellig numerisch sein" + End If + + If weight Is Nothing OrElse + Not IsNumeric(weight) OrElse + CDbl(weight) <= 0 Then + + Return "Fehler: Gewicht muss > 0 sein" + End If + + If country_code Is Nothing OrElse country_code.Length <> 2 Then + Return "Fehler: country_code muss ISO-2 sein" + End If + + If certificate_price Is Nothing OrElse + Not IsNumeric(certificate_price) OrElse + CDbl(certificate_price) <= 0 Then + + Return "Fehler: Zertifikatspreis muss > 0 sein" + End If + + If year Is Nothing OrElse Not IsNumeric(year) Then + Return "Fehler: ungültiges Jahr" + End If + + ' ------------------------------------------------------------ + ' SEE total (Default-Emission) laden + ' ------------------------------------------------------------ + Dim api As New cATEZ_Greenpulse_CBAM_CostCalculation() + Dim see_total As Decimal + Dim benchmark As Decimal + + Try + + Dim defResp = api.GetCnCodeDefaults( + cn_code, + country_code.ToUpperInvariant(), + CInt(year) + ) + + If Not defResp.success Then + Return $"FEHLER DEFAULTS {defResp.error.code}: {defResp.error.message}" + End If + + If defResp.data Is Nothing Then + Return "Fehler: Keine CN-Code Defaults verfügbar" + End If + + If defResp.data.default_emission < 0 Then + Return "Fehler: Keine Default-Emission (see_total) verfügbar" + End If + + see_total = defResp.data.default_emission + + If defResp.data.benchmark.HasValue Then + benchmark = defResp.data.benchmark.Value + Else + Return "Fehler: Kein Benchmark verfügbar" + End If + + Catch ex As Exception + Return "Technischer Fehler beim Laden der CN-Code Defaults: " & ex.Message + End Try + + ' ------------------------------------------------------------ + ' Berechnung (bestehende Logik unverändert) + ' ------------------------------------------------------------ + Dim w As Decimal = CDec(weight) + Dim price As Decimal = CDec(certificate_price) + + ' --- Faktoren als echte Faktoren (nicht Prozent) --- + Dim cbamf As Decimal = 0D + + Select Case CInt(year) + Case 2026 : cbamf = 0.975D + Case 2027 : cbamf = 0.95D + Case 2028 : cbamf = 0.9D + Case 2029 : cbamf = 0.775D + Case 2030 : cbamf = 0.515D + Case 2031 : cbamf = 0.39D + Case 2032 : cbamf = 0.265D + Case 2033 : cbamf = 0.14D + Case Else : cbamf = 0D + End Select + + ' SEFA = Benchmark * CBAMF * CSCF + Dim sefa As Decimal = benchmark * cbamf + + ' Zertifikatspflicht je Tonne Ware + Dim certPerTon As Decimal = see_total - sefa + If certPerTon < 0D Then certPerTon = 0D + + ' Gesamte Zertifikatspflicht (t CO2e) + Dim certTotal As Decimal = certPerTon * w + + ' Kosten + Dim cbamCost As Decimal = certTotal * price + + ' --- Ausgabe --- + Dim s As String = "" + + s &= "CBAM Kostenberechnung (Default EE mit SEFA-Abzug)" & vbCrLf + s &= "-----------------------------------------------------------" & vbCrLf + s &= $"CN-Code: {cn_code}" & vbCrLf + s &= $"Ursprungsland: {country_code.ToUpperInvariant()}" & vbCrLf + s &= $"Jahr: {year}" & vbCrLf + s &= $"Gewicht: {w:N2} t" & vbCrLf + s &= $"EE / SEE total (Default): {see_total:N6} t CO₂e/t" & vbCrLf + s &= $"Benchmark BMg: {benchmark:N6} t CO₂e/t" & vbCrLf + s &= $"CBAMF: {cbamf:N3}" + + Console.WriteLine(s) + + CBAM_COST = cbamCost + CBAM_EMISSION = see_total + CBAM_BENCHMARK = benchmark + CBAM_Faktor = cbamf + + Return s + End Function + + ' ------------------------------------------------------------------------ + ' Helpers + ' ------------------------------------------------------------------------ + Private Function ParseCalcResponse(jsonStr As String) _ + As cCBAM_CostCalculation_Response + + Dim json As New JsonObject() + json.Load(jsonStr) + + Dim r As New cCBAM_CostCalculation_Response With { + .success = json.BoolOf("success") + } + + If r.success Then + + Dim d = json.ObjectOf("data") + + If d Is Nothing Then + r.success = False + r.error = New cCBAM_Error With { + .code = "EMPTY_DATA", + .message = "Die API hat kein data-Objekt zurückgegeben.", + .statusCode = 0 + } + Return r + End If + + r.data = New cCBAM_CostCalculation_Data With { + .cn_code = d.StringOf("cn_code"), + .weight = GetDec(d, "weight"), + .origin = d.StringOf("origin"), + .phase_factor = GetDec(d, "phase_factor"), + .scenarios = New List(Of cCBAM_CostCalculation_Scenario), + .warnings = New List(Of cCBAM_Warning) + } + + ' price_resolution + Dim pr = d.ObjectOf("price_resolution") + + If pr IsNot Nothing Then + r.data.price_resolution = New cCBAM_PriceResolution With { + .requested_date = pr.StringOf("requested_date"), + .price_type = pr.StringOf("price_type"), + .price = GetNullableDec(pr, "price"), + .term_id = pr.StringOf("term_id"), + .strategy_name = pr.StringOf("strategy_name"), + .resolved_at = pr.StringOf("resolved_at") + } + + Dim fallbackObj = pr.ObjectOf("fallback") + + If fallbackObj IsNot Nothing Then + r.data.price_resolution.fallback = New cCBAM_PriceFallback With { + .used = fallbackObj.BoolOf("used"), + .message = fallbackObj.StringOf("message") + } + End If + End If + + ' scenarios[] + Dim scenarios = d.ArrayOf("scenarios") + + If scenarios IsNot Nothing Then + For i = 0 To scenarios.Size - 1 + + Dim so = scenarios.ObjectAt(i) + + Dim item As New cCBAM_CostCalculation_Scenario With { + .type = so.StringOf("type"), + .source = so.StringOf("source"), + .cost = GetDec(so, "cost"), + .cbam_emission = GetDec(so, "cbam_emission") + } + + Dim cd = so.ObjectOf("calculation_details") + + If cd IsNot Nothing Then + item.calculation_details = New cCBAM_CostCalculation_Details With { + .cbam_emission = GetNullableDec(cd, "cbam_emission"), + .benchmark = GetNullableDec(cd, "benchmark"), + .adjusted_benchmark = GetNullableDec(cd, "adjusted_benchmark"), + .emission_difference = GetNullableDec(cd, "emission_difference"), + .weight = GetNullableDec(cd, "weight"), + .carbon_price = GetNullableDec(cd, "carbon_price"), + .phase_factor = GetNullableDec(cd, "phase_factor") + } + End If + + r.data.scenarios.Add(item) + Next + End If + + ' warnings[] + Dim warnings = d.ArrayOf("warnings") + + If warnings IsNot Nothing Then + For i = 0 To warnings.Size - 1 + + Dim wo = warnings.ObjectAt(i) + + r.data.warnings.Add(New cCBAM_Warning With { + .code = wo.StringOf("code"), + .message = wo.StringOf("message") + }) + Next + End If + + Else + r.error = ParseError(json) + End If + + Return r + End Function + + Private Shared Function ParseError(j As JsonObject) As cCBAM_Error + + If j Is Nothing Then + Return New cCBAM_Error With { + .code = "UNKNOWN", + .message = "Unbekannter API-Fehler", + .statusCode = 0 + } + End If + + Dim e = j.ObjectOf("error") + + If e Is Nothing Then + Return New cCBAM_Error With { + .code = "UNKNOWN", + .message = j.StringOf("message"), + .statusCode = 0 + } + End If + + Return New cCBAM_Error With { + .code = e.StringOf("code"), + .message = e.StringOf("message"), + .statusCode = e.IntOf("statusCode") + } + End Function + + Private Shared Function GetDec(o As JsonObject, n As String) As Decimal + + If o Is Nothing Then Return 0D + + Dim s As String = o.StringOf(n) + + If String.IsNullOrWhiteSpace(s) Then Return 0D + + s = s.Replace(",", ".") + + Dim v As Decimal + Decimal.TryParse( + s, + NumberStyles.Any, + CultureInfo.InvariantCulture, + v + ) + + Return v + End Function + + Private Shared Function GetNullableDec(o As JsonObject, n As String) As Decimal? + + If o Is Nothing Then Return Nothing + + Dim s As String = o.StringOf(n) + + If String.IsNullOrWhiteSpace(s) Then + Return Nothing + End If + + Dim v As Decimal + + If Decimal.TryParse( + s.Replace(",", "."), + NumberStyles.Any, + CultureInfo.InvariantCulture, + v + ) Then + Return v + End If + + Return Nothing + End Function + + Private Shared Function GetNullableInt(o As JsonObject, n As String) As Integer? + + If o Is Nothing Then Return Nothing + + Dim s As String = o.StringOf(n) + + If String.IsNullOrWhiteSpace(s) Then + Return Nothing + End If + + Dim v As Integer + + If Integer.TryParse(s, v) Then + Return v + End If + + Return Nothing + End Function + +End Class + +' ============================================================================ +' DTOs +' ============================================================================ + +' ------------------------------------------------------------------------ +' Cost Calculation Request +' +' Bestehende Properties country_code, year und benchmark_value bleiben +' absichtlich vorhanden, damit bestehender aufrufender Code nicht bricht. +' Sie werden von ToJson() nicht als alte API-Felder gesendet. +' ------------------------------------------------------------------------ +Public Class cCBAM_CostCalculation_Request + + Public Property cn_code As String + + ' Alt / Kompatibilität + Public Property country_code As String + Public Property year As Integer? + Public Property benchmark_value As Decimal? + + ' Aktuelle API + Public Property weight As Decimal + Public Property origin As String + Public Property [date] As String + Public Property carbon_price As Decimal? + Public Property see_total As Decimal? + Public Property supplier_id As String + Public Property article_id As String + + Public Function ToJson() As String + + Dim j As New JsonObject() + + j.UpdateString("cn_code", cn_code) + j.UpdateNumber( + "weight", + weight.ToString(CultureInfo.InvariantCulture) + ) + + Dim requestOrigin As String = origin + + If String.IsNullOrWhiteSpace(requestOrigin) Then + requestOrigin = country_code + End If + + If Not String.IsNullOrWhiteSpace(requestOrigin) Then + j.UpdateString("origin", requestOrigin) + End If + + If Not String.IsNullOrWhiteSpace([date]) Then + j.UpdateString("date", [date]) + End If + + If carbon_price.HasValue Then + j.UpdateNumber( + "carbon_price", + carbon_price.Value.ToString(CultureInfo.InvariantCulture) + ) + End If + + If see_total.HasValue Then + j.UpdateNumber( + "see_total", + see_total.Value.ToString(CultureInfo.InvariantCulture) + ) + End If + + If Not String.IsNullOrWhiteSpace(supplier_id) Then + j.UpdateString("supplier_id", supplier_id) + End If + + If Not String.IsNullOrWhiteSpace(article_id) Then + j.UpdateString("article_id", article_id) + End If + + Return j.Emit() + End Function + +End Class + +Public Class cCBAM_CostCalculation_Response + Public Property success As Boolean + Public Property data As cCBAM_CostCalculation_Data + Public Property [error] As cCBAM_Error +End Class + +Public Class cCBAM_CostCalculation_Data + Public Property cn_code As String + Public Property weight As Decimal + Public Property origin As String + Public Property phase_factor As Decimal + Public Property price_resolution As cCBAM_PriceResolution + Public Property scenarios As List(Of cCBAM_CostCalculation_Scenario) + Public Property warnings As List(Of cCBAM_Warning) +End Class + +Public Class cCBAM_PriceResolution + Public Property requested_date As String + Public Property price_type As String + Public Property price As Decimal? + Public Property term_id As String + Public Property strategy_name As String + Public Property resolved_at As String + Public Property fallback As cCBAM_PriceFallback +End Class + +Public Class cCBAM_PriceFallback + Public Property used As Boolean + Public Property message As String +End Class + +Public Class cCBAM_CostCalculation_Scenario + Public Property type As String + Public Property source As String + Public Property cost As Decimal + Public Property cbam_emission As Decimal + Public Property calculation_details As cCBAM_CostCalculation_Details +End Class + +Public Class cCBAM_CostCalculation_Details + Public Property cbam_emission As Decimal? + Public Property benchmark As Decimal? + Public Property adjusted_benchmark As Decimal? + Public Property emission_difference As Decimal? + Public Property weight As Decimal? + Public Property carbon_price As Decimal? + Public Property phase_factor As Decimal? +End Class + +Public Class cCBAM_Warning + Public Property code As String + Public Property message As String +End Class + +Public Class cCBAM_CnCode_Response + Public Property success As Boolean + Public Property data As List(Of cCBAM_CnCode) + Public Property [error] As cCBAM_Error +End Class + +Public Class cCBAM_CnCode + Public Property cn_code As String + Public Property cn_description As String +End Class + +Public Class cCBAM_CnCodeDefaults_Response + Public Property success As Boolean + Public Property data As cCBAM_CnCodeDefaults + Public Property [error] As cCBAM_Error +End Class + +Public Class cCBAM_CnCodeDefaults + Public Property cn_code As String + Public Property cn_description As String + Public Property default_emission As Decimal + Public Property benchmark As Decimal? + Public Property country_code As String + Public Property fallback_used As Boolean + Public Property year As Integer? +End Class + +Public Class cCBAM_Error + Public Property code As String + Public Property message As String + Public Property statusCode As Integer +End Class diff --git a/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/Read_T1/cATEZ_Read_T1.vb b/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/Read_T1/cATEZ_Read_T1.vb index 0abbd122..e305b779 100644 --- a/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/Read_T1/cATEZ_Read_T1.vb +++ b/VERAG_PROG_ALLGEMEIN/Schnittstellen/ATEZ/Read_T1/cATEZ_Read_T1.vb @@ -37,28 +37,214 @@ Public Class cATEZ_Read_T1 ' ========================== ' Zentrale Konfiguration ' ========================== - Private Const BaseUrl As String = "https://t1.declarant.ai/" - ' lt. API-Dokumentation - Private Const Username As String = "verag" - Private Const Password As String = "verag_password123" - Private Const ClientId As String = "client3" + Private Const TokenUrl As String = + "https://login.singlewindow.io/auth/realms/agsw/protocol/openid-connect/token" + + Private Const ApiUrl As String = + "https://declaranthub.singlewindow.io/api/v1-0/ai-ingestion/base64" + + Private Const Username As String = "dhub@verag.ag" + + ' Gleiches Passwort wie für den Declaranthub-Login + Private Const Password As String = "Dhub1234*" + + Private Const ClientId As String = "agsw-admin" Private Const GrantType As String = "password" - Private Shared ReadOnly client As HttpClient = New HttpClient With { - .BaseAddress = New Uri(BaseUrl) - } + Private Shared ReadOnly client As New HttpClient() + + + ' ========================== + ' TOKEN + ' ========================== + Private Async Function GetTokenAsync() As Task(Of String) + + Dim data = New Dictionary(Of String, String) From { + {"grant_type", GrantType}, + {"username", Username}, + {"password", Password}, + {"client_id", ClientId} + } + + Using content As New FormUrlEncodedContent(data) + + Dim response As HttpResponseMessage + + Try + response = Await client.PostAsync(TokenUrl, content) + + Catch ex As TaskCanceledException + Throw New T1ApiException( + 0, + "Zeitüberschreitung beim Abrufen des Access Tokens.", + Nothing, + Nothing, + ex) + + Catch ex As Exception + Throw New T1ApiException( + 0, + "Fehler beim Abrufen des Access Tokens.", + Nothing, + Nothing, + ex) + End Try + + If Not response.IsSuccessStatusCode Then + Await CreateAndThrowApiExceptionAsync(response, "Token-Anforderung") + End If + + Dim json = Await response.Content.ReadAsStringAsync() + + Dim tokenObj As JObject + + Try + tokenObj = JObject.Parse(json) + Catch ex As Exception + Throw New T1ApiException( + response.StatusCode, + "Token-Response enthält kein gültiges JSON.", + Nothing, + json, + ex) + End Try + + Dim token = tokenObj.Value(Of String)("access_token") + + If String.IsNullOrWhiteSpace(token) Then + Throw New T1ApiException( + response.StatusCode, + "Kein access_token im Token-Response gefunden.", + Nothing, + json) + End If + + Return token + + End Using + + End Function + + + ' ========================== + ' PROCESS PDF + ' ========================== + Public Async Function ProcessPdfAsync(pdfPath As String) As Task(Of T1ProcessResult) + + If Not File.Exists(pdfPath) Then + Throw New FileNotFoundException( + "PDF-Datei nicht gefunden.", + pdfPath) + End If + + ' 1. Access Token holen + Dim token As String = Await GetTokenAsync() + + ' 2. PDF → Base64 + Dim pdfBytes As Byte() = File.ReadAllBytes(pdfPath) + Dim base64 As String = Convert.ToBase64String(pdfBytes) + + ' 3. Payload gemäß neuer API + Dim payload = New With { + .documentType = "transit_declaration", + .mode = "direct", + .outputApplicationId = "dakosy/sftp/verg", + .base64String = base64 + } + + Dim jsonBody As String = + JsonConvert.SerializeObject(payload) + + Using content As New StringContent( + jsonBody, + Encoding.UTF8, + "application/json") + + Using request As New HttpRequestMessage( + HttpMethod.Post, + ApiUrl) + + request.Headers.Authorization = + New AuthenticationHeaderValue("Bearer", token) + + request.Content = content + + Dim response As HttpResponseMessage + + Try + response = Await client.SendAsync(request) + + Catch ex As TaskCanceledException + Throw New T1ApiException( + 0, + "Zeitüberschreitung beim Aufruf der Declaranthub API.", + Nothing, + Nothing, + ex) + + Catch ex As Exception + Throw New T1ApiException( + 0, + "Fehler beim Aufruf der Declaranthub API.", + Nothing, + Nothing, + ex) + End Try + + If Not response.IsSuccessStatusCode Then + Await CreateAndThrowApiExceptionAsync( + response, + "Declaranthub AI Ingestion") + End If + + Dim resultJson As String = + Await response.Content.ReadAsStringAsync() + + Dim result As T1ProcessResult + + Try + result = + JsonConvert.DeserializeObject(Of T1ProcessResult)( + resultJson) + + Catch ex As Exception + Throw New T1ApiException( + response.StatusCode, + "Konnte API-Response nicht in T1ProcessResult deserialisieren.", + Nothing, + resultJson, + ex) + End Try + + If result Is Nothing Then + Throw New T1ApiException( + response.StatusCode, + "Leeres API-Ergebnis nach Deserialisierung.", + Nothing, + resultJson) + End If + + Return result + + End Using + End Using + + End Function ' ========================== ' Zentrale Error-Auswertung ' ========================== - Private Async Function CreateAndThrowApiExceptionAsync(response As HttpResponseMessage, - context As String) As Task + Private Async Function CreateAndThrowApiExceptionAsync( + response As HttpResponseMessage, + context As String) As Task + Dim raw As String = String.Empty + Try raw = Await response.Content.ReadAsStringAsync() Catch - ' Ignorieren, wenn Lesen des Inhalts fehlschlägt + ' Ignorieren, falls Response nicht gelesen werden kann End Try Dim errorKey As String = Nothing @@ -66,14 +252,16 @@ Public Class cATEZ_Read_T1 If Not String.IsNullOrWhiteSpace(raw) Then Try - Dim jo = JObject.Parse(raw) + Dim jo As JObject = JObject.Parse(raw) errorKey = jo.Value(Of String)("error_key") + If String.IsNullOrEmpty(errorKey) Then errorKey = jo.Value(Of String)("error") End If message = jo.Value(Of String)("message") + If String.IsNullOrEmpty(message) Then message = jo.Value(Of String)("detail") End If @@ -83,216 +271,220 @@ Public Class cATEZ_Read_T1 End If Catch - ' Inhalt war kein gültiges JSON – unten als Text verwenden + ' Response ist kein JSON -> Raw Response verwenden + message = raw End Try End If If String.IsNullOrWhiteSpace(message) Then - message = $"API-Fehler {CInt(response.StatusCode)} in {context}." - If Not String.IsNullOrWhiteSpace(raw) Then - message &= " Response: " & raw - End If + message = + $"API-Fehler {CInt(response.StatusCode)} ({response.ReasonPhrase}) in {context}." Else - message = $"API-Fehler in {context}: " & message + message = + $"API-Fehler {CInt(response.StatusCode)} ({response.ReasonPhrase}) in {context}: {message}" End If - Throw New T1ApiException(response.StatusCode, message, errorKey, raw) - End Function + Throw New T1ApiException( + response.StatusCode, + message, + errorKey, + raw) - ' ========================== - ' TOKEN - ' ========================== - Private Async Function GetTokenAsync() As Task(Of String) - - Dim data = New Dictionary(Of String, String) From { - {"username", Username}, - {"password", Password}, - {"client_id", ClientId}, - {"grant_type", GrantType} - } - - Dim content = New FormUrlEncodedContent(data) - - Dim response As HttpResponseMessage - Try - response = Await client.PostAsync("token", content) - Catch ex As TaskCanceledException - Throw New T1ApiException(0, "Zeitüberschreitung beim Aufruf von /token.", Nothing, Nothing, ex) - Catch ex As Exception - Throw New T1ApiException(0, "Fehler beim Aufruf von /token.", Nothing, Nothing, ex) - End Try - - If Not response.IsSuccessStatusCode Then - Await CreateAndThrowApiExceptionAsync(response, "/token") - End If - - Dim json = Await response.Content.ReadAsStringAsync() - Dim tokenObj = JObject.Parse(json) - - Dim token = tokenObj.Value(Of String)("access_token") - If String.IsNullOrEmpty(token) Then - Throw New T1ApiException(response.StatusCode, - "Kein access_token im /token-Response gefunden.", - Nothing, - json) - End If - - Return token - End Function - - ' ========================== - ' PROCESS PDF - ' ========================== - Public Async Function ProcessPdfAsync(pdfPath As String) As Task(Of T1ProcessResult) - - If Not File.Exists(pdfPath) Then - Throw New FileNotFoundException("PDF-Datei nicht gefunden.", pdfPath) - End If - - ' Token holen - Dim token = Await GetTokenAsync() - - ' PDF → Base64 - Dim base64 = Convert.ToBase64String(File.ReadAllBytes(pdfPath)) - - Dim payload = New With { - .document_base64 = base64 - } - - Dim jsonBody = JsonConvert.SerializeObject(payload) - Dim content = New StringContent(jsonBody, Encoding.UTF8, "application/json") - - Dim request = New HttpRequestMessage(HttpMethod.Post, "process") - request.Headers.Authorization = New AuthenticationHeaderValue("Bearer", token) - request.Content = content - - Dim response As HttpResponseMessage - Try - response = Await client.SendAsync(request) - Catch ex As TaskCanceledException - Throw New T1ApiException(0, "Zeitüberschreitung beim Aufruf von /process.", Nothing, Nothing, ex) - Catch ex As Exception - Throw New T1ApiException(0, "Fehler beim Aufruf von /process.", Nothing, Nothing, ex) - End Try - - If Not response.IsSuccessStatusCode Then - Await CreateAndThrowApiExceptionAsync(response, "/process") - End If - - Dim resultJson = Await response.Content.ReadAsStringAsync() - - ' JSON → Klasse - Dim result As T1ProcessResult - Try - result = JsonConvert.DeserializeObject(Of T1ProcessResult)(resultJson) - Catch ex As Exception - Throw New T1ApiException(response.StatusCode, - "Konnte /process-Response nicht in T1ProcessResult deserialisieren.", - Nothing, - resultJson, - ex) - End Try - - If result Is Nothing Then - Throw New T1ApiException(response.StatusCode, - "Leeres /process-Ergebnis nach Deserialisierung.", - Nothing, - resultJson) - End If - - Return result End Function - ' Sync Wrapper falls du ihn brauchst - Public Function ProcessPdf(pdfPath As String) As T1ProcessResult - Return ProcessPdfAsync(pdfPath).GetAwaiter().GetResult() - End Function + '' Sync Wrapper falls du ihn brauchst + 'Public Function ProcessPdf(pdfPath As String) As T1ProcessResult + ' Return ProcessPdfAsync(pdfPath).GetAwaiter().GetResult() + 'End Function ' ========================== ' TEST-Methode – ruft ProcessPdfAsync auf und gibt ALLES aus ' ========================== Public Async Sub TEST(pdfPath As String) + Try - ' dieselbe Instanz verwenden + Cursor.Current = Cursors.WaitCursor + Dim result As T1ProcessResult = Await Me.ProcessPdfAsync(pdfPath) Dim sb As New StringBuilder() - sb.AppendLine("=== Grunddaten ===") - sb.AppendLine("Status: " & If(result.Status, "")) - sb.AppendLine("Document Count: " & result.DocumentCount) - sb.AppendLine("Document Type: " & If(result.DocumentType, "")) - sb.AppendLine("Processing Time (s): " & result.ProcessingTimeSeconds) - + sb.AppendLine("=== DECLARANTHUB RESPONSE ===") sb.AppendLine() - sb.AppendLine("=== Kopf-T1-Daten ===") - sb.AppendLine("Date: " & If(result.DateField, "")) - sb.AppendLine("Dispatch Country: " & If(result.DispatchCountry, "")) - sb.AppendLine("Dispatch Place: " & If(result.DispatchPlace, "")) - sb.AppendLine("Loading Place: " & If(result.LoadingPlace, "")) - sb.AppendLine("Destination Country: " & If(result.DestinationCountry, "")) - sb.AppendLine("Destination Customs Authority: " & If(result.DestinationCustomsAuthority, "")) - sb.AppendLine("Mode of Transport: " & If(result.ModeOfTransport, "")) - sb.AppendLine("Type of Transport: " & If(result.TypeOfTransport, "")) - sb.AppendLine("Transit ID: " & If(result.TransitId, "")) - sb.AppendLine("Truck ID Border: " & If(result.TruckIdBorder, "")) - sb.AppendLine("Truck ID Transit: " & If(result.TruckIdTransit, "")) - sb.AppendLine() - sb.AppendLine("=== Items ===") - If result.Items IsNot Nothing AndAlso result.Items.Count > 0 Then - For i As Integer = 0 To result.Items.Count - 1 - Dim it = result.Items(i) - sb.AppendLine($"Item #{i + 1}") - sb.AppendLine($" Export ID: {it.ExportId}") - sb.AppendLine($" Invoice No: {it.InvoiceNo}") - sb.AppendLine($" Item Index: {it.ItemIndex}") - sb.AppendLine($" Sender: {it.Sender}") - sb.AppendLine($" Total Package: {it.TotalPackage}") - sb.AppendLine($" Total Weight: {it.TotalWeight}") - sb.AppendLine() - Next + If result Is Nothing Then + sb.AppendLine("Keine Daten von der API erhalten.") Else - sb.AppendLine("Keine Items vorhanden.") + + ' ========================================== + ' Grunddaten + ' ========================================== + sb.AppendLine("=== Grunddaten ===") + sb.AppendLine("Status: " & If(result.Status, "")) + sb.AppendLine("Document Count: " & result.DocumentCount.ToString()) + sb.AppendLine("Document Type: " & If(result.DocumentType, "")) + sb.AppendLine("Processing Time (s): " & result.ProcessingTimeSeconds.ToString()) + + ' ========================================== + ' T1 Kopfdaten + ' ========================================== + sb.AppendLine() + sb.AppendLine("=== T1-Daten ===") + + sb.AppendLine("Date: " & If(result.DateField, "")) + sb.AppendLine("Dispatch Country: " & If(result.DispatchCountry, "")) + sb.AppendLine("Dispatch Place: " & If(result.DispatchPlace, "")) + sb.AppendLine("Loading Place: " & If(result.LoadingPlace, "")) + + sb.AppendLine("Destination Country: " & + If(result.DestinationCountry, "")) + + sb.AppendLine("Destination Customs Authority: " & + If(result.DestinationCustomsAuthority, "")) + + sb.AppendLine("Mode of Transport: " & + If(result.ModeOfTransport, "")) + + sb.AppendLine("Type of Transport: " & + If(result.TypeOfTransport, "")) + + sb.AppendLine("Transit ID: " & + If(result.TransitId, "")) + + sb.AppendLine("Truck ID Border: " & + If(result.TruckIdBorder, "")) + + sb.AppendLine("Truck ID Transit: " & + If(result.TruckIdTransit, "")) + + + ' ========================================== + ' Items + ' ========================================== + sb.AppendLine() + sb.AppendLine("=== Items ===") + + If result.Items IsNot Nothing AndAlso + result.Items.Count > 0 Then + + For i As Integer = 0 To result.Items.Count - 1 + + Dim it As T1ProcessItem = result.Items(i) + + sb.AppendLine($"Item #{i + 1}") + sb.AppendLine($" Export ID: {If(it.ExportId, "")}") + sb.AppendLine($" Invoice No: {If(it.InvoiceNo, "")}") + sb.AppendLine($" Item Index: {it.ItemIndex}") + sb.AppendLine($" Sender: {If(it.Sender, "")}") + sb.AppendLine($" Total Package: {it.TotalPackage}") + sb.AppendLine($" Total Weight: {it.TotalWeight}") + sb.AppendLine() + + Next + + Else + sb.AppendLine("Keine Items vorhanden.") + End If + + + ' ========================================== + ' Noch nicht gemappte Felder + ' ========================================== + sb.AppendLine() + sb.AppendLine("=== Additional Data ===") + + If result.AdditionalData IsNot Nothing AndAlso + result.AdditionalData.Count > 0 Then + + For Each kvp In result.AdditionalData + + sb.AppendLine( + $"{kvp.Key}: {kvp.Value.ToString(Formatting.Indented)}" + ) + + Next + + Else + sb.AppendLine("Keine zusätzlichen Felder vorhanden.") + End If + End If - sb.AppendLine("=== Additional Data (ungeparst) ===") - If result.AdditionalData IsNot Nothing AndAlso result.AdditionalData.Count > 0 Then - For Each kvp In result.AdditionalData - sb.AppendLine($" {kvp.Key}: {kvp.Value.ToString()}") - Next - Else - sb.AppendLine("Keine zusätzlichen Felder.") - End If - MessageBox.Show(sb.ToString(), "T1 Dokument verarbeitet", MessageBoxButtons.OK, MessageBoxIcon.Information) + MessageBox.Show( + sb.ToString(), + "Declaranthub – T1 verarbeitet", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ) + Catch apiEx As T1ApiException + Dim sbErr As New StringBuilder() - sbErr.AppendLine("T1 API Fehler") - sbErr.AppendLine("HTTP-Status: " & CInt(apiEx.StatusCode)) - If Not String.IsNullOrEmpty(apiEx.ErrorKey) Then - sbErr.AppendLine("Error Key: " & apiEx.ErrorKey) - End If - sbErr.AppendLine("Nachricht: " & apiEx.Message) - If Not String.IsNullOrEmpty(apiEx.RawResponse) Then - sbErr.AppendLine() - sbErr.AppendLine("Raw Response:") - sbErr.AppendLine(apiEx.RawResponse) + + sbErr.AppendLine("DECLARANTHUB API FEHLER") + sbErr.AppendLine() + + If CInt(apiEx.StatusCode) > 0 Then + sbErr.AppendLine( + "HTTP-Status: " & CInt(apiEx.StatusCode).ToString() + ) + Else + sbErr.AppendLine( + "HTTP-Status: keine HTTP-Antwort" + ) End If - MessageBox.Show(sbErr.ToString(), "T1 API Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error) + If Not String.IsNullOrWhiteSpace(apiEx.ErrorKey) Then + sbErr.AppendLine( + "Error Key: " & apiEx.ErrorKey + ) + End If + + sbErr.AppendLine() + sbErr.AppendLine("Nachricht:") + sbErr.AppendLine(apiEx.Message) + + + ' Besonders wichtig beim ersten Test der neuen API + If Not String.IsNullOrWhiteSpace(apiEx.RawResponse) Then + + sbErr.AppendLine() + sbErr.AppendLine("=== RAW RESPONSE ===") + sbErr.AppendLine(apiEx.RawResponse) + + End If + + + MessageBox.Show( + sbErr.ToString(), + "Declaranthub API Fehler", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ) + Catch ex As Exception - MessageBox.Show(ex.ToString(), "Allgemeiner Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error) + + MessageBox.Show( + ex.ToString(), + "Allgemeiner Fehler", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ) + + Finally + + Cursor.Current = Cursors.Default + End Try + End Sub - End Class - Public Class T1ProcessItem Public Property ExportId As String diff --git a/VERAG_PROG_ALLGEMEIN/VERAG_PROG_ALLGEMEIN.vbproj b/VERAG_PROG_ALLGEMEIN/VERAG_PROG_ALLGEMEIN.vbproj index a970ea64..cef0a197 100644 --- a/VERAG_PROG_ALLGEMEIN/VERAG_PROG_ALLGEMEIN.vbproj +++ b/VERAG_PROG_ALLGEMEIN/VERAG_PROG_ALLGEMEIN.vbproj @@ -441,6 +441,7 @@ UserControl +