Imports Newtonsoft.Json Public Class cRelayHub Public Class cRelayHubDocument Public Property type As String Public Property base64String As String Public Property filename As String Public Property fileType As String End Class Public Class cRelayHubTransaction Public Property ioPartner As String Public Property ioDivision3 As String Public Property ioReference As String End Class Public Class cRelayHubObjectIdentification Public Property objectName As String Public Property objectAlias As String Public Property declarationType As String Public Property referenceNumberOverlay As String ' <--- NEU Public Property username As String ' <--- NEU Public Property avisoId As String ' <--- NEU Public Property shipmentId As String ' <--- NEU End Class Public Class cRelayHubDv1CostAllocation Public Property costType As String Public Property costs As Decimal Public Property currencyCode As String End Class Public Class cRelayHubAgentContact Public Property contactPersonName As String Public Property contactPersonPhoneNumber As String Public Property contactPersonPosition As String Public Property contactPersonEmail As String ' <--- NEU End Class Public Class cRelayHubCost Public Property costAmount As String Public Property costCurrency As String End Class Public Class cRelayHubHeaderData Public Property agentContact As cRelayHubAgentContact ' DAKOSY Public Property declarantIsConsignee As Boolean? Public Property representationRelationshipCode As String Public Property inputTaxDeduction As Boolean? Public Property procedureCodeRequested As String Public Property transportMeansArrivalIdentity As String Public Property transportMeansNationalityCode As String Public Property previousAdministrativeReferenceType As String Public Property previousAdministrativeReferenceNumber As String Public Property destinationFederalState As String Public Property destinationCountry As String Public Property departureCountry As String Public Property addressedCustomsOffice As String Public Property dv1CostAllocation As cRelayHubDv1CostAllocation ' MODALTRANS Public Property serviceCode As String Public Property docType As String Public Property declarationType As String Public Property declarantEori As String Public Property additionalProcedureCode As String End Class Public Class cRelayHubAddress Public Property addressType As String Public Property participantEORI As String Public Property participantSubsidiaryNumber As String Public Property companyName As String Public Property streetAndNumber As String Public Property countryCode As String Public Property postalCode As String Public Property city As String Public Property district As String End Class Public Class cRelayHubDeclaration Public Property objectIdentification As cRelayHubObjectIdentification Public Property headerData As cRelayHubHeaderData Public Property addresses As List(Of cRelayHubAddress) End Class Public Class cRelayHubAdditionalData Public Property transaction As cRelayHubTransaction Public Property micImportShipment As cRelayHubMicImportShipment Public Property declaration As List(Of cRelayHubDeclaration) End Class Public Class cRelayHubMicImportShipment Public Property company As String Public Property plant As String Public Property connectionId As String Public Property partnerId As String Public Property partyId As String Public Property messageId As String Public Property transactionId As String Public Property dateTime As String Public Property sequenceNum As Integer? Public Property actionCode As String Public Property shipmentId As String Public Property shipmentPartial As String Public Property shipmentVersion As Integer? Public Property documentNumber As Integer? Public Property shipmentType As String Public Property customsDate As String End Class Public Class cRelayHubAdditionalSettings Public Property combineOptions As List(Of String) Public Property combineItemsPriorityDocumentType As String Public Property disableOutbound As Boolean? Public Property disableDocumentConverter As Boolean? Public Property disableAiToCommon As Boolean? Public Property disableValidation As Boolean? Public Property targetLang As String End Class Public Class cRelayHubJobOrderRequest Public Property environment As cRelayHubEnvironment = cRelayHubEnvironment.PROD Public Property referenceNo As String Public Property dispatchCountry As String Public Property destinationCountry As String Public Property regimeType As String ' Entfernt: customer (vom Schema abgelehnt) Public Property documents As List(Of cRelayHubDocument) Public Property additionalData As cRelayHubAdditionalData Public Property additionalSettings As cRelayHubAdditionalSettings ' Neu: Pflichtfeld lt. Fehlermeldung Public Property outputApplication As String ' "test" | "dakosy/sftp/vera" | "evrim/excel" | "sec/import/integration" ' "modaltrans/customs/declaration" End Class Public Class cRelayHubOutputApplication Public Const Dakosy As String = "dakosy/sftp/verg" Public Const Modaltrans As String = "modaltrans/customs/declaration" Public Const MIC_NL_IMPORT As String = "mic/import" End Class Public Enum cRelayHubEnvironment TEST PROD End Enum Public Class cRelayHubJobOrderResponse Public Property id As String Public Property status As String Public Property createdAt As String Public Property referenceNo As String Public Property jobOrderProcessingId As String Public Property jobOrderNo As String End Class Public Class cRelayHubApiResult Public Property Success As Boolean Public Property StatusCode As Integer Public Property Message As String ' Zusammenfassung Public Property Details As String ' Volltext/Fehlermeldung Public Property Data As cRelayHubJobOrderResponse ' Nur bei 201 End Class Private Shared ReadOnly ENVIRONMENT As cRelayHubEnvironment = cRelayHubEnvironment.PROD Shared API_URL As String = "https://declaranthub.singlewindow.io/api/v1-0" Private Shared Function GetApiUrl(environment As cRelayHubEnvironment) As String Select Case environment Case cRelayHubEnvironment.TEST Return "https://test-relayhub.singlewindow.io/api/v1-0" Case cRelayHubEnvironment.PROD Return "https://declaranthub.singlewindow.io/api/v1-0" Case Else Throw New ApplicationException("Unbekannte RelayHub-Umgebung.") End Select End Function Public Class cRelayHub_sendToRelayHub_JobOrderRequest Shared API_URL As String = "https://declaranthub.singlewindow.io/api/v1-0" ' Low-level Sender: holt Access-Token aus cRelayHubToken und sendet JSON Private Shared Function SendJobOrder(jsonPayload As String, environment As cRelayHubEnvironment) As Chilkat.HttpResponse Dim http As New Chilkat.Http http.SetRequestHeader("Accept", "application/json") Dim token As String = cRelayHubToken.GetAccessToken(environment) http.AuthToken = token Dim apiUrl As String = GetApiUrl(environment) Dim fullUrl As String = apiUrl & "/job-orders/init" Console.WriteLine("API_URL =: " & apiUrl) Debug.WriteLine("RelayHub Environment: " & environment.ToString()) Debug.WriteLine("RelayHub URL: " & fullUrl) Debug.WriteLine("RelayHub OutputApplication: " & If(JsonConvert.DeserializeObject(Of cRelayHubJobOrderRequest)(jsonPayload).outputApplication, "")) Return http.PostJson2(apiUrl & "/job-orders/init", "application/json", jsonPayload) End Function ' Public API: erstellt Job-Order mit 401-Retry Public Shared Function getJSON(request As cRelayHubJobOrderRequest) As String Try Dim sanitized = Sanitize(request) Dim settings As New JsonSerializerSettings With { .NullValueHandling = NullValueHandling.Ignore, .ContractResolver = New Newtonsoft.Json.Serialization.DefaultContractResolver With { .NamingStrategy = New Newtonsoft.Json.Serialization.CamelCaseNamingStrategy() } } Dim jsonPayload As String = JsonConvert.SerializeObject(sanitized, settings) Console.WriteLine("JSON → " & jsonPayload) Return jsonPayload Catch ex As Exception VERAG_PROG_ALLGEMEIN.cErrorHandler.ERR( ex.Message, ex.StackTrace, System.Reflection.MethodInfo.GetCurrentMethod.Name) Return "Error" End Try End Function Public Shared Function query_declarations(request As cRelayHubJobOrderRequest) As cRelayHubApiResult Dim result As New cRelayHubApiResult() Try VERAG_PROG_ALLGEMEIN.cChilkat_Helper.UnlockCilkat() Dim sanitized = Sanitize(request) Dim settings As New JsonSerializerSettings With { .NullValueHandling = NullValueHandling.Ignore, .ContractResolver = New Newtonsoft.Json.Serialization.DefaultContractResolver With { .NamingStrategy = New Newtonsoft.Json.Serialization.CamelCaseNamingStrategy() } } Dim jsonPayload As String = JsonConvert.SerializeObject(sanitized, settings) Console.WriteLine("JSON → " & jsonPayload) 'Dim jsonPayload As String = JsonConvert.SerializeObject(request) ' 1. Versuch Dim response As Chilkat.HttpResponse = SendJobOrder(jsonPayload, request.environment) If response Is Nothing Then Return New cRelayHubApiResult With { .Success = False, .StatusCode = 0, .Message = "Verbindungsfehler", .Details = "Keine Antwort erhalten." } End If '' 401 → Token-Cache invalidieren und genau 1x erneut probieren 'If response.StatusCode = 401 Then ' ' WICHTIG: ' ' Diese Methode sollte in cRelayHubToken als Public verfügbar sein: ' ' Public Shared Sub ResetTokenCache() : ClearToken() : End Sub ' ' → Falls noch nicht vorhanden, bitte dort ergänzen. ' Try ' cRelayHubToken.ResetTokenCache() ' Catch ' ' Falls die Methode (noch) nicht existiert, kann man als Fallback ' ' hier eine kurze Wartezeit einbauen und anschließend erneut GetValidAccessToken() aufrufen. ' ' Threading.Thread.Sleep(100) ' End Try ' ' Retry ' response = SendJobOrder(jsonPayload) ' If response Is Nothing Then ' Return New cRelayHubApiResult With { ' .Success = False, .StatusCode = 0, .Message = "Verbindungsfehler (nach Refresh)", ' .Details = "Keine Antwort erhalten." ' } ' End If 'End If ' Auswertung result.StatusCode = response.StatusCode Console.WriteLine("result.StatusCode → " & result.StatusCode) Select Case response.StatusCode Case 201 Try Dim jobResponse As cRelayHubJobOrderResponse = JsonConvert.DeserializeObject(Of cRelayHubJobOrderResponse)(response.BodyStr) result.Success = True result.Message = "Job Order erfolgreich erstellt" result.Data = jobResponse result.Details = $"ID: {jobResponse.id}, Referenz: {jobResponse.referenceNo}" Catch ex As Exception result.Success = False result.StatusCode = 999 result.Message = "Antwort konnte nicht gelesen werden" result.Details = ex.Message End Try Case 400 To 499 result.Success = False result.Message = "Client-Fehler" result.Details = response.BodyStr Case 500 To 599 result.Success = False result.Message = "Server-Fehler" result.Details = response.BodyStr Case Else result.Success = False result.Message = $"Unbekannter Fehler (Status {response.StatusCode})" result.Details = response.BodyStr End Select Return result Catch ex As Exception VERAG_PROG_ALLGEMEIN.cErrorHandler.ERR(ex.Message, ex.StackTrace, System.Reflection.MethodInfo.GetCurrentMethod.Name) Return New cRelayHubApiResult With {.Success = False, .StatusCode = 0, .Message = "Exception", .Details = ex.ToString()} End Try End Function 'Private Shared Function Sanitize(req As cRelayHub.cRelayHubJobOrderRequest) As cRelayHub.cRelayHubJobOrderRequest ' req.outputApplication = If(req.outputApplication, "").Trim() ' req.dispatchCountry = If(req.dispatchCountry, "").Trim().ToUpperInvariant() ' req.destinationCountry = If(req.destinationCountry, "").Trim().ToUpperInvariant() ' ' lokale Fail-fast Checks ' If req.dispatchCountry.Length <> 2 Then Throw New ApplicationException("dispatchCountry ISO-2 erforderlich.") ' If req.destinationCountry.Length <> 2 Then Throw New ApplicationException("destinationCountry ISO-2 erforderlich.") ' Return req 'End Function Public Shared Function getRelayHub_AnhDesc(anh_Art) As String Dim fileType = "other" If anh_Art IsNot Nothing AndAlso anh_Art IsNot DBNull.Value Then Select Case anh_Art Case "eFatura", "INVOICE" : fileType = "invoice" Case "Rechnung", "Konsi-Rechnung", "INVOICE" : fileType = "commercial_invoice" Case "CMR" : fileType = "cmr" Case "Ausfuhr", "EXPORT" : fileType = "tr_export_declaration" Case "ATR", "A.TR", "ATR-EUR1" : fileType = "atr" Case "Vorpapier", "T1" : fileType = "transit_declaration" Case "Ursprungszeugnis", "CoO" : fileType = "origin" Case "Lieferschein" : fileType = "delivery_note" Case "Packliste" : fileType = "packing_list" Case "Freihandelszone", "FTZ" : fileType = "ftz" Case "EUR1", "EUR", "EUR.1" : fileType = "eur" Case "GGED_D", "GGED-D" : fileType = "gged_p" Case Else : fileType = "unknown" '"other" End Select End If Return fileType End Function Private Shared Function IsModaltrans(req As cRelayHubJobOrderRequest) As Boolean Return String.Equals(req.outputApplication, "modaltrans/customs/declaration", StringComparison.OrdinalIgnoreCase) End Function Private Shared Function IsMIC_NL_IMPORT(req As cRelayHubJobOrderRequest) As Boolean Return String.Equals(req.outputApplication, cRelayHubOutputApplication.MIC_NL_IMPORT, StringComparison.OrdinalIgnoreCase) End Function Private Shared Sub SetMIC_NL_IMPORT(req As cRelayHubJobOrderRequest) If req.environment = cRelayHubEnvironment.PROD Then Throw New ApplicationException("MIC NL Import ist derzeit nur im RelayHub-Testsystem verfügbar.") req.outputApplication = "mic/import" req.regimeType = "IM" If req.additionalData Is Nothing Then req.additionalData = New cRelayHubAdditionalData If req.additionalData.micImportShipment Is Nothing Then req.additionalData.micImportShipment = New cRelayHubMicImportShipment Dim mic = req.additionalData.micImportShipment mic.company = "NL" mic.plant = "IN" mic.connectionId = "MICWebService" mic.actionCode = "DIF" If String.IsNullOrWhiteSpace(mic.partnerId) Then mic.partnerId = req.referenceNo If String.IsNullOrWhiteSpace(mic.shipmentId) Then mic.shipmentId = req.referenceNo If String.IsNullOrWhiteSpace(mic.messageId) Then mic.messageId = req.referenceNo If String.IsNullOrWhiteSpace(mic.transactionId) Then mic.transactionId = req.referenceNo If String.IsNullOrWhiteSpace(mic.dateTime) Then mic.dateTime = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") If String.IsNullOrWhiteSpace(mic.shipmentType) Then mic.shipmentType = "E" If req.additionalSettings Is Nothing Then req.additionalSettings = New cRelayHubAdditionalSettings req.additionalSettings.disableOutbound = False End Sub Private Shared Function Sanitize(req As cRelayHubJobOrderRequest) As cRelayHubJobOrderRequest If req Is Nothing Then Throw New ApplicationException("RelayHub Request fehlt.") Dim isMicNlImport As Boolean = IsMIC_NL_IMPORT(req) If isMicNlImport Then SetMIC_NL_IMPORT(req) req.outputApplication = If(req.outputApplication, "").Trim() req.dispatchCountry = If(req.dispatchCountry, "").Trim().ToUpperInvariant() req.destinationCountry = If(req.destinationCountry, "").Trim().ToUpperInvariant() If req.dispatchCountry.Length <> 2 Then Throw New ApplicationException("dispatchCountry ISO-2 erforderlich.") If req.destinationCountry.Length <> 2 Then Throw New ApplicationException("destinationCountry ISO-2 erforderlich.") If isMicNlImport Then If req.additionalData Is Nothing OrElse req.additionalData.micImportShipment Is Nothing Then Throw New ApplicationException("micImportShipment für MIC NL Import erforderlich.") Dim mic = req.additionalData.micImportShipment mic.partnerId = If(mic.partnerId, "").Trim() mic.partyId = If(mic.partyId, "").Trim() mic.shipmentId = If(mic.shipmentId, "").Trim() If mic.partnerId = "" Then Throw New ApplicationException("MIC partnerId erforderlich.") If mic.partyId = "" Then Throw New ApplicationException("MIC partyId erforderlich.") If mic.shipmentId = "" Then Throw New ApplicationException("MIC shipmentId erforderlich.") ElseIf IsModaltrans(req) Then If req.additionalData IsNot Nothing Then req.additionalData.transaction = Nothing If req.additionalData.declaration IsNot Nothing Then For Each declaration In req.additionalData.declaration declaration.objectIdentification = Nothing Next End If End If If req.additionalSettings IsNot Nothing AndAlso req.additionalSettings.combineOptions Is Nothing Then req.additionalSettings.combineOptions = New List(Of String) Else Dim allowed As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase) From { "description", "commodity_code", "country_of_origin", "unit_type" } If req.additionalSettings IsNot Nothing Then If req.additionalSettings.combineOptions IsNot Nothing Then req.additionalSettings.combineOptions = req.additionalSettings.combineOptions.Where(Function(o) Not String.IsNullOrWhiteSpace(o)).Select(Function(o) o.Trim().ToLowerInvariant()).Where(Function(o) allowed.Contains(o)).Distinct().ToList() req.additionalSettings.combineItemsPriorityDocumentType = If(req.additionalSettings.combineItemsPriorityDocumentType, "").Trim() If req.additionalSettings.combineItemsPriorityDocumentType = "" Then req.additionalSettings.combineItemsPriorityDocumentType = Nothing End If End If Return req End Function ' Beispielfall Function CreateSampleJobOrderRequest() As cRelayHubJobOrderRequest Dim req As New cRelayHubJobOrderRequest With { .referenceNo = "1001K", .dispatchCountry = "TR", ' ISO-2 .destinationCountry = "DE", ' ISO-2 (nicht "EN") .regimeType = "IMPORT", ' "IMPORT" | "EXPORT" .outputApplication = "test", ' Pflichtfeld lt. Validator .documents = New List(Of cRelayHubDocument) From { New cRelayHubDocument With {.type = "base64", .base64String = "SGVsbG8sIHRoaXMgaXMgYSBzYW1wbGUgYmFzZTY0IHN0cmluZy4=", .filename = "test.txt", .fileType = "invoice"}, New cRelayHubDocument With {.type = "base64", .base64String = "SGVsbG8sIHRoaXMgaXMgYSBzYW1wbGUgYmFzZTY0IHN0cmluZy4=", .filename = "test2.txt", .fileType = "atr"}, New cRelayHubDocument With {.type = "base64", .base64String = "SGVsbG8sIHRoaXMgaXMgYSBzYW1wbGUgYmFzZTY0IHN0cmluZy4=", .filename = "test3.txt", .fileType = "cmr"} }, .additionalData = New cRelayHubAdditionalData With { .transaction = New cRelayHubTransaction With { .ioPartner = "VERA", .ioDivision3 = "SUB", .ioReference = "4803/25001763_1301250935SS/samimx" }, .declaration = New List(Of cRelayHubDeclaration) From { New cRelayHubDeclaration With { .objectIdentification = New cRelayHubObjectIdentification With { .objectName = "4803/25001763", .objectAlias = "1349846", .declarationType = "EZA-D" }, .headerData = New cRelayHubHeaderData With { .agentContact = New cRelayHubAgentContact With { .contactPersonName = "AMANN", .contactPersonPhoneNumber = "+49 123 456 789", .contactPersonPosition = "Manager", .contactPersonEmail = "a@example.com" }, .declarantIsConsignee = True, .representationRelationshipCode = 1, ' Integer .inputTaxDeduction = True, ' Boolean .procedureCodeRequested = "42", ' goodsStatus/costs entfernt .transportMeansArrivalIdentity = "PB1552EC", .transportMeansNationalityCode = "BG", .previousAdministrativeReferenceType = "T1", .previousAdministrativeReferenceNumber = "25TR160100001472M0", .destinationFederalState = "07", .destinationCountry = "DE", .departureCountry = "TR" }, .addresses = New List(Of cRelayHubAddress) From { New cRelayHubAddress With {.addressType = "CZ", .participantEORI = "EORI12345", .participantSubsidiaryNumber = "001", .companyName = "SISECAM DIS TIC.A.S.", .streetAndNumber = "D-100 KARAYOLU CD.YAYLA MH.NO.70/C", .countryCode = "TR", .postalCode = "34949", .city = "TUZLA ISTANBUL"}, New cRelayHubAddress With {.addressType = "CN", .participantEORI = "EORI67890", .participantSubsidiaryNumber = "002", .companyName = "POLYNT COMPOSITES GERMANY GMBH", .streetAndNumber = "KIESELSTRASSE 2", .countryCode = "DE", .postalCode = "56357", .city = "MIEHLEN"} } } } }, .additionalSettings = New cRelayHubAdditionalSettings With { .combineOptions = New List(Of String) From { "description", "commodity_code", "country_of_origin", "unit_type" } } } Return req End Function End Class End Class