diff --git a/WEBSERVICES/ZUGFeRDRESTService/Controllers/ValidationController.cs b/WEBSERVICES/ZUGFeRDRESTService/Controllers/ValidationController.cs index ae616ab0..49ff5c5d 100644 --- a/WEBSERVICES/ZUGFeRDRESTService/Controllers/ValidationController.cs +++ b/WEBSERVICES/ZUGFeRDRESTService/Controllers/ValidationController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; +using System.Data; using System.Data.SqlClient; using System.IO; using System.Linq; @@ -36,14 +37,14 @@ namespace ZUGFeRDRESTService.Controllers public const string REFERENCES_Rejection_30004_3 = "REFERENCES_Rejection_30004_3"; public const string REFERENCES_Rejection_30005_1 = "REFERENCES_Rejection_30005_1"; public const string REFERENCES_Rejection_30005_2 = "REFERENCES_Rejection_30005_2"; - public const string REFERENCES_Rejection_30006 = "REFERENCES_Rejection_30006" ; - public const string REFERENCES_Rejection_30007 = "REFERENCES_Rejection_30007" ; + public const string REFERENCES_Rejection_30006 = "REFERENCES_Rejection_30006"; + public const string REFERENCES_Rejection_30007 = "REFERENCES_Rejection_30007"; public const string REFERENCES_Rejection_30007_1 = "REFERENCES_Rejection_30007_1"; - public const string REFERENCES_Rejection_30008 = "REFERENCES_Rejection_30008" ; - public const string REFERENCES_Rejection_30009 = "REFERENCES_Rejection_30009" ; - public const string REFERENCES_Rejection_30010 = "REFERENCES_Rejection_30010" ; - public const string REFERENCES_Rejection_30011 = "REFERENCES_Rejection_30011" ; - public const string REFERENCES_Rejection_30012 = "REFERENCES_Rejection_30012" ; + public const string REFERENCES_Rejection_30008 = "REFERENCES_Rejection_30008"; + public const string REFERENCES_Rejection_30009 = "REFERENCES_Rejection_30009"; + public const string REFERENCES_Rejection_30010 = "REFERENCES_Rejection_30010"; + public const string REFERENCES_Rejection_30011 = "REFERENCES_Rejection_30011"; + public const string REFERENCES_Rejection_30012 = "REFERENCES_Rejection_30012"; public const string AMOUNT_CALC_REJECTION = "AMOUNT_CALC_REJECTION"; private List _ValidationErrors; @@ -209,7 +210,6 @@ namespace ZUGFeRDRESTService.Controllers if (oFileSizeIsOK == false) { - //throw new ZUGFeRDExecption(ErrorType.FileTooBig, "FileTooBig"); throw new ZUGFeRDExecption(ErrorCodes.FileSizeLimitReachedException, _MaxFileSizeInMegabytes.ToString(), string.Empty, "FileTooBig"); } @@ -218,6 +218,7 @@ namespace ZUGFeRDRESTService.Controllers if (file.FileName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase)) { oZugferdResult = _zugferd.ExtractZUGFeRDFileWithGDPicture(oStream); + oZugferdResult.ReceiptFileType = "PDF"; } else if (file.FileName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) { @@ -229,6 +230,7 @@ namespace ZUGFeRDRESTService.Controllers }; oZugferdResult = _zugferd.SerializeZUGFeRDDocument(oResult); + oZugferdResult.ReceiptFileType = "XML"; } _logger.Info("Detected Specification was: [{0}]", oZugferdResult.Specification); @@ -238,7 +240,6 @@ namespace ZUGFeRDRESTService.Controllers if (oFilteredPropertyMap.Count == 0) { _logger.Warn("No properties found in property map for specification [{0}]", oZugferdResult.Specification); - //throw new ZUGFeRDExecption(ErrorType.UnsupportedFormat, "Unsupported Format"); throw new ZUGFeRDExecption(ErrorCodes.UnsupportedFerdException, "Unsupported Format"); } else @@ -263,15 +264,13 @@ namespace ZUGFeRDRESTService.Controllers if (oPropertyResult.MissingProperties.Count > 0) { - //throw new ZUGFeRDExecption(ErrorType.MissingProperties, "Missing Properties"); throw new ZUGFeRDExecption(ErrorCodes.MissingValueException, "Missing Properties"); } - Tuple oValidateResult = ValidateBuyerOrderReference(oPropertyResult.ValidProperties); + Tuple oValidateResult = ValidateBuyerOrderReference(oPropertyResult.ValidProperties, oZugferdResult); if (oValidateResult.Item1 == false) { - //throw new ZUGFeRDExecption(ErrorType.UnknownError, "Unknown Error"); throw new ZUGFeRDExecption(ErrorCodes.UnhandledException, "Unknown Error"); } @@ -398,20 +397,16 @@ namespace ZUGFeRDRESTService.Controllers /// Hier wird eine externe Prozedur gerufen, PRCUST_INV_CHECK_FROM_PORTAL, /// die das Ergebnis der Referenzpruefung liefert. /// - private Tuple ValidateBuyerOrderReference(List pProperties) + private Tuple ValidateBuyerOrderReference(List pProperties, ZugferdResult pZugferdResult) { var oMessageId = GetMessageId(); _logger.Debug("Created new MessageId: [{0}]", oMessageId); _logger.Debug("Inserting properties into database."); - foreach (var oItem in pProperties) + if (!BulkInsertDataToDatabase(oMessageId, pProperties, pZugferdResult)) { - var oResult = InsertPropertyMap(oItem, oMessageId); - - if (oResult == false) - { - _logger.Warn("Error while inserting the Property [{0}] into the Database!", oItem.Description); - } + _logger.Warn("Error while Inserting properties into database!"); + return new Tuple(false, string.Empty); } _logger.Debug("Calling validation prodecure."); @@ -472,8 +467,13 @@ namespace ZUGFeRDRESTService.Controllers oReturnValue = oDbMessage; } - // Für alle den RejectionCode setzen - oReturnValue = oReturnValue.Replace("@REJECTION_CODE", oRejectionItem); + // Der REJECTION-Code wird in alle Meldungen eingefügt. + if (!string.IsNullOrEmpty(oRejectionItem) && oReturnValue.Contains("@REJECTION_CODE", StringComparison.OrdinalIgnoreCase)) + { + var rejectionCodeNumber = GetRejectionCodeNumber(oRejectionItem); + oReturnValue = Regex.Replace(oReturnValue, "@REJECTION_CODE", "Ablehnungscode: " + rejectionCodeNumber, RegexOptions.IgnoreCase); + } + break; } } @@ -616,60 +616,194 @@ namespace ZUGFeRDRESTService.Controllers return $"{Guid.NewGuid()}@{MESSAGEID_DOMAIN}"; } - public bool InsertPropertyMap(ValidProperty pProperty, string pMessageId) + private bool BulkInsertDataToDatabase(string pMessageId, List pProperties, ZugferdResult pZugferdResult) + { + if (string.IsNullOrEmpty(pMessageId)) + { + return false; + } + + if (!DeleteExistingPropertyValues(pMessageId)) + { + return false; + } + + DataTable oDataTable = PrepareDataTable(pMessageId, pProperties, pZugferdResult); + + // ColumnList initialisieren + List oColumnNames = new List { + "REFERENCE_GUID", + "ITEM_DESCRIPTION", + "ITEM_VALUE", + "GROUP_COUNTER", + "SPEC_NAME", + "IS_REQUIRED" + }; + + bool oBulkResult = BulkInsert(oDataTable, "TBEDMI_ITEM_VALUE", oColumnNames); + + if (!oBulkResult) + { + _logger.Error("Bulk Insert for MessageId [{0}] failed!", pMessageId); + return false; + } + + _logger.Info("Bulk Insert finished. [{0}] rows inserted for MessageId [{1}].", oDataTable.Rows.Count, pMessageId); + return true; + } + + private bool BulkInsert(DataTable pDataTable, string pDestinationTable, List pColumnNames) + { + using (var oSqlBulkCopy = new SqlBulkCopy(_database.MSSQL.GetConnection())) + { + oSqlBulkCopy.DestinationTableName = pDestinationTable; + foreach (var columnItem in pColumnNames) + { + oSqlBulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(columnItem, columnItem)); + } + + try + { + oSqlBulkCopy.WriteToServer(pDataTable); + } + catch (Exception ex) + { + _logger.Error(ex); + return false; + } + } + + return true; + } + + private DataTable PrepareDataTable(string pMessageId, List pProperties, ZugferdResult pDocument) + { + DataTable oDataTable = new DataTable(); + oDataTable.Columns.Add(new DataColumn("REFERENCE_GUID", typeof(string))); + oDataTable.Columns.Add(new DataColumn("ITEM_DESCRIPTION", typeof(string))); + oDataTable.Columns.Add(new DataColumn("ITEM_VALUE", typeof(string))); + oDataTable.Columns.Add(new DataColumn("GROUP_COUNTER", typeof(Int32))); + oDataTable.Columns.Add(new DataColumn("SPEC_NAME", typeof(string))); + oDataTable.Columns.Add(new DataColumn("IS_REQUIRED", typeof(bool))); + + // Erste Zeile enthält die Spezifikation + DataRow oFirstRow = oDataTable.NewRow(); + oFirstRow["REFERENCE_GUID"] = pMessageId; + oFirstRow["ITEM_DESCRIPTION"] = "Verwendete Spezifikation der E-Rechnung"; + oFirstRow["ITEM_VALUE"] = pDocument.Specification; + oFirstRow["GROUP_COUNTER"] = 0; + oFirstRow["SPEC_NAME"] = "ZUGFERD_SPECIFICATION"; + oFirstRow["IS_REQUIRED"] = false; + + _logger.Debug("Mapping Property [ZUGFERD_SPECIFICATION] with value [{0}]", pDocument.Specification); + oDataTable.Rows.Add(oFirstRow); + + //' Zweite Zeile enthält das verwendete XML Schema + DataRow oSecondRow = oDataTable.NewRow(); + oSecondRow["REFERENCE_GUID"] = pMessageId; + oSecondRow["ITEM_DESCRIPTION"] = "Verwendetes XML-Schema (XSD) der E-Rechnung"; + oSecondRow["ITEM_VALUE"] = pDocument.UsedXMLSchema; + oSecondRow["GROUP_COUNTER"] = 0; + oSecondRow["SPEC_NAME"] = "ZUGFERD_XML_SCHEMA"; + oSecondRow["IS_REQUIRED"] = false; + + _logger.Debug("Mapping Property [ZUGFERD_XML_SCHEMA] with value [{0}]", pDocument.UsedXMLSchema); + oDataTable.Rows.Add(oSecondRow); + + //' Dritte Zeile enthält das verwendete Datei-Format des Belegs (PDF/XML) + if (pDocument.ReceiptFileType != null) + { + DataRow oThirdRow = oDataTable.NewRow(); + oThirdRow["REFERENCE_GUID"] = pMessageId; + oThirdRow["ITEM_DESCRIPTION"] = "Dateityp der E-Rechnung"; + oThirdRow["ITEM_VALUE"] = pDocument.ReceiptFileType; + oThirdRow["GROUP_COUNTER"] = 0; + oThirdRow["SPEC_NAME"] = "RECEIPT_FILE_TYPE"; + oThirdRow["IS_REQUIRED"] = false; + + _logger.Debug("Mapping Property [RECEIPT_FILE_TYPE] with value [{0}]", pDocument.ReceiptFileType); + oDataTable.Rows.Add(oThirdRow); + } + + foreach (var propertyItem in pProperties) + { + + // ItemType = 3 => eingebettete Datei, nicht den base64 speichern + if (propertyItem.ItemType == 3) + { + continue; + } + + // ItemType = 0 (normale texte) dürfen nicht leer sein - leere Werte werden überlesen + if (propertyItem.ItemType == 0 && string.IsNullOrEmpty(propertyItem.Value)) + { + continue; + } + + // If GroupCounter is -1, it means this is a default property that can only occur once. + // Set the actual inserted value to 0 + var oGroupCounterValue = propertyItem.GroupCounter < 0 ? 0 : propertyItem.GroupCounter; + + if (propertyItem.Value.Length > 4000) + { + _logger.Warn("Value for field [{0}] is longer than 4000 characters, will be truncated!", propertyItem.TableColumn); + propertyItem.Value = propertyItem.Value.Substring(0, 4000); + } + + // Description mit BT-Feld aufbereiten + var betterDescription = propertyItem.Description; + if (!string.IsNullOrEmpty(propertyItem.EN16931_ID)) + { + betterDescription = propertyItem.EN16931_ID + " (" + propertyItem.Description + ")"; + } + + DataRow newRow = oDataTable.NewRow(); + newRow["REFERENCE_GUID"] = pMessageId; + newRow["ITEM_DESCRIPTION"] = betterDescription; + newRow["ITEM_VALUE"] = propertyItem.Value; + newRow["GROUP_COUNTER"] = oGroupCounterValue; + newRow["SPEC_NAME"] = propertyItem.TableColumn; + newRow["IS_REQUIRED"] = propertyItem.IsRequired; + + _logger.Debug("Mapping Property [{0}] with value [{1}]", propertyItem.TableColumn, propertyItem.Value); + oDataTable.Rows.Add(newRow); + } + + return oDataTable; + } + + public bool DeleteExistingPropertyValues(string pMessageId) { try { - if (pProperty.ItemType == 3) - { - // Wir speichern keine Attachment-Werte in die DB - return true; - } - - if (pProperty.ItemType == 0 && string.IsNullOrEmpty(pProperty.Value.ToString())) - { - // Leere Texte speichern wir nicht in der DB - return true; - } - - var oSql = $"INSERT INTO {pProperty.TableName} " + - "(REFERENCE_GUID, ITEM_DESCRIPTION, ITEM_VALUE, CREATEDWHO, SPEC_NAME, GROUP_COUNTER, IS_REQUIRED) VALUES " + - "(@REFERENCE_GUID, @ITEM_DESCRIPTION, @ITEM_VALUE, @CREATEDWHO, @SPEC_NAME, @GROUP_COUNTER, @IS_REQUIRED)"; - - string itemValue = string.Empty; - if (pProperty.Value.Length > 4000) - { - itemValue = pProperty.Value.Substring(1, 4000); - _logger.Warn("Value for field [{0}] is longer than 4000 characters, will be truncated!", pProperty.TableColumn); - } - else - { - itemValue = pProperty.Value; - } - - var oParams = new SqlParameter[] - { - new SqlParameter("@REFERENCE_GUID", pMessageId), - new SqlParameter("@ITEM_DESCRIPTION", pProperty.Description), - new SqlParameter("@ITEM_VALUE", itemValue.Replace("'", "''")), - new SqlParameter("@CREATEDWHO", ADDED_WHO), - new SqlParameter("@GROUP_COUNTER", pProperty.GroupCounter), - new SqlParameter("@SPEC_NAME", pProperty.TableColumn), - new SqlParameter("@IS_REQUIRED", pProperty.IsRequired) - }; - - var oCommand = new SqlCommand(oSql); - oCommand.Parameters.AddRange(oParams); - - return _database.MSSQL.ExecuteNonQuery(oCommand); + var delItemValueSQL = "DELETE FROM TBEDMI_ITEM_VALUE WHERE REFERENCE_GUID '" + pMessageId + "';"; + var oCommand = new SqlCommand(delItemValueSQL); + _database.MSSQL.ExecuteNonQuery(oCommand); + _logger.Debug("DELETE FROM TBEDMI_ITEM_VALUE successfull"); } catch (Exception ex) { _logger.Error(ex); return false; } + + try + { + var delItemFilesSQL = "DELETE FROM TBEDMI_ITEM_FILES WHERE REFERENCE_GUID '" + pMessageId + "';"; + var oCommand = new SqlCommand(delItemFilesSQL); + _database.MSSQL.ExecuteNonQuery(oCommand); + _logger.Debug("DELETE FROM TBEDMI_ITEM_FILES successfull"); + } + catch (Exception ex) + { + _logger.Error(ex); + return false; + } + + return true; } + /// /// Ermittelt die Ausgabe-nachricht für einen Fehlercode /// @@ -744,5 +878,16 @@ namespace ZUGFeRDRESTService.Controllers return "20010"; } } + + private string GetRejectionCodeNumber(string rejectionCode) + { + if (rejectionCode.StartsWith("REFERENCES_Rejection_")) + { + var retValue = rejectionCode.Replace("REFERENCES_Rejection_", ""); + return retValue; + } + + return rejectionCode; + } } }