From 707762579ef3b8a25414bc134391ae5bab2b3ebf Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Thu, 26 Mar 2026 18:51:19 +0100 Subject: [PATCH] =?UTF-8?q?Pflichtsegmente=20IFA,=20REA=20und=20IVA=20f?= =?UTF-8?q?=C3=BCr=20ASVREC=20validieren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../structure/DefaultStructureValidator.java | 98 ++++++++- ...tStructureValidatorAsvrecSegmentsTest.java | 208 ++++++++++++++++++ .../DefaultStructureValidatorTest.java | 20 +- .../invalid-segment-count-non-numeric.asv | 3 + .../invalid-segment-count-too-large.asv | 5 +- .../invalid-segment-count-too-small.asv | 5 +- src/test/resources/unh-before-unt-valid.asv | 5 +- src/test/resources/unh-unt-match.asv | 5 +- src/test/resources/unh-unt-mismatch.asv | 5 +- src/test/resources/unt-before-unh-invalid.asv | 5 +- src/test/resources/valid-segment-count.asv | 5 +- 11 files changed, 347 insertions(+), 17 deletions(-) create mode 100644 src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecSegmentsTest.java diff --git a/src/main/java/de/gecheckt/asv/validation/structure/DefaultStructureValidator.java b/src/main/java/de/gecheckt/asv/validation/structure/DefaultStructureValidator.java index 3e4a7ab..12f830d 100644 --- a/src/main/java/de/gecheckt/asv/validation/structure/DefaultStructureValidator.java +++ b/src/main/java/de/gecheckt/asv/validation/structure/DefaultStructureValidator.java @@ -28,6 +28,7 @@ import de.gecheckt.asv.validation.model.ValidationSeverity; * 10. Eine Nachricht muss mindestens ein UNT-Segment enthalten * 11. UNH muss vor UNT stehen * 12. Der Nachrichtentyp in UNH/S009/0065 darf nur ASVREC oder ASVFEH sein + * 13. Für Nachrichten vom Typ ASVREC müssen die Segmente IFA, REA und IVA vorhanden sein */ public class DefaultStructureValidator implements StructureValidator { @@ -125,6 +126,9 @@ public class DefaultStructureValidator implements StructureValidator { // Regel 8: UNT-Segmentanzahl muss mit tatsächlicher Segmentanzahl übereinstimmen validateUntSegmentCount(message, errors, untSegment.get()); + + // Neue Regel: Für ASVREC-Nachrichten müssen IFA, REA und IVA vorhanden sein + validateAsvrecRequiredSegments(message, errors); } } } @@ -154,18 +158,19 @@ public class DefaultStructureValidator implements StructureValidator { } // Wenn UNH-Segment vorhanden ist, prüfe den Nachrichtentyp else { - validateMessageType(unhSegment.get(), message.messagePosition(), errors); + validateMessageType(message, unhSegment.get(), message.messagePosition(), errors); } } /** - * Validiert den Nachrichtentyp im UNH-Segment. + * Validiert den Nachrichtentyp im UNH-Segment und prüft ggf. zusätzliche Anforderungen. * + * @param message die zu validierende Nachricht * @param unhSegment das UNH-Segment * @param messagePosition die Position der Nachricht * @param errors die Liste zum Hinzufügen von Validierungsfehlern */ - private void validateMessageType(Segment unhSegment, int messagePosition, List errors) { + private void validateMessageType(Message message, Segment unhSegment, int messagePosition, List errors) { // Prüfe, ob das zweite Feld nach dem Segmentnamen vorhanden ist if (unhSegment.fields().size() >= 2) { var messageTypeField = unhSegment.fields().get(1); // S009 composite element @@ -432,6 +437,93 @@ public class DefaultStructureValidator implements StructureValidator { } } + /** + * Validiert, dass für ASVREC-Nachrichten die benötigten Segmente vorhanden sind. + * + * @param message die zu validierende Nachricht + * @param errors die Liste zum Hinzufügen von Validierungsfehlern + */ + private void validateAsvrecRequiredSegments(Message message, List errors) { + // Prüfe den Nachrichtentyp aus dem UNH-Segment + var unhSegment = message.getFirstSegment("UNH"); + if (unhSegment.isPresent()) { + String messageType = extractMessageType(unhSegment.get()); + + // Nur für ASVREC-Nachrichten prüfen + if ("ASVREC".equals(messageType)) { + // Prüfe, ob IFA-Segment vorhanden ist + if (message.getFirstSegment("IFA").isEmpty()) { + errors.add(createError( + "STRUCTURE_013", + "ASVREC-Nachricht muss ein IFA-Segment enthalten", + ValidationSeverity.ERROR, + "", + message.messagePosition(), + "", + 0, + "", + "IFA-Segment erforderlich für ASVREC-Nachrichten" + )); + } + + // Prüfe, ob REA-Segment vorhanden ist + if (message.getFirstSegment("REA").isEmpty()) { + errors.add(createError( + "STRUCTURE_014", + "ASVREC-Nachricht muss ein REA-Segment enthalten", + ValidationSeverity.ERROR, + "", + message.messagePosition(), + "", + 0, + "", + "REA-Segment erforderlich für ASVREC-Nachrichten" + )); + } + + // Prüfe, ob IVA-Segment vorhanden ist + if (message.getFirstSegment("IVA").isEmpty()) { + errors.add(createError( + "STRUCTURE_015", + "ASVREC-Nachricht muss ein IVA-Segment enthalten", + ValidationSeverity.ERROR, + "", + message.messagePosition(), + "", + 0, + "", + "IVA-Segment erforderlich für ASVREC-Nachrichten" + )); + } + } + } + } + + /** + * Extrahiert den Nachrichtentyp aus dem UNH-Segment. + * + * @param unhSegment das UNH-Segment + * @return der Nachrichtentyp oder ein leerer String, wenn nicht extrahierbar + */ + private String extractMessageType(Segment unhSegment) { + // Prüfe, ob das zweite Feld nach dem Segmentnamen vorhanden ist + if (unhSegment.fields().size() >= 2) { + var messageTypeField = unhSegment.fields().get(1); // S009 composite element + var rawValue = messageTypeField.rawValue(); + + // Extrahiere den Nachrichtentyp vor dem ersten ':' + String messageType = rawValue; + int colonIndex = rawValue.indexOf(':'); + if (colonIndex > 0) { + messageType = rawValue.substring(0, colonIndex); + } + + return messageType; + } + + return ""; + } + /** * Hilfsmethode zur Erstellung eines ValidationError mit konsistenten Parametern. * diff --git a/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecSegmentsTest.java b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecSegmentsTest.java new file mode 100644 index 0000000..93d051a --- /dev/null +++ b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecSegmentsTest.java @@ -0,0 +1,208 @@ +package de.gecheckt.asv.validation.structure; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import de.gecheckt.asv.domain.model.Field; +import de.gecheckt.asv.domain.model.InputFile; +import de.gecheckt.asv.domain.model.Message; +import de.gecheckt.asv.domain.model.Segment; +import de.gecheckt.asv.validation.model.ValidationError; +import de.gecheckt.asv.validation.model.ValidationResult; + +class DefaultStructureValidatorAsvrecSegmentsTest { + + private DefaultStructureValidator validator; + + @BeforeEach + void setUp() { + validator = new DefaultStructureValidator(); + } + + @Test + void validate_shouldNotReportErrorForValidAsvrecWithAllRequiredSegments() { + // Given + Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ASVREC:D:03B:UN:EAN008"))); + Segment ifa = new Segment("IFA", 2); + Segment rea = new Segment("REA", 3); + Segment iva = new Segment("IVA", 4); + Segment unt = new Segment("UNT", 5, List.of(new Field(1, "5"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, ifa, rea, iva, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + // When + ValidationResult result = validator.validate(inputFile); + + // Then + assertFalse(result.hasErrors(), "There should be no validation errors for valid ASVREC with all required segments"); + assertFalse(result.hasWarnings()); + assertFalse(result.hasInfos()); + assertTrue(result.getAllErrors().isEmpty()); + } + + @Test + void validate_shouldReportErrorWhenAsvrecMissingIfaSegment() { + // Given + Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ASVREC:D:03B:UN:EAN008"))); + Segment rea = new Segment("REA", 2); + Segment iva = new Segment("IVA", 3); + Segment unt = new Segment("UNT", 4, List.of(new Field(1, "4"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, rea, iva, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + // When + ValidationResult result = validator.validate(inputFile); + + // Then + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_013", error.errorCode()); + assertEquals("ASVREC-Nachricht muss ein IFA-Segment enthalten", error.description()); + assertEquals("", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("IFA-Segment erforderlich für ASVREC-Nachrichten", error.expectedRule()); + } + + @Test + void validate_shouldReportErrorWhenAsvrecMissingReaSegment() { + // Given + Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ASVREC:D:03B:UN:EAN008"))); + Segment ifa = new Segment("IFA", 2); + Segment iva = new Segment("IVA", 3); + Segment unt = new Segment("UNT", 4, List.of(new Field(1, "4"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, ifa, iva, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + // When + ValidationResult result = validator.validate(inputFile); + + // Then + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_014", error.errorCode()); + assertEquals("ASVREC-Nachricht muss ein REA-Segment enthalten", error.description()); + assertEquals("", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("REA-Segment erforderlich für ASVREC-Nachrichten", error.expectedRule()); + } + + @Test + void validate_shouldReportErrorWhenAsvrecMissingIvaSegment() { + // Given + Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ASVREC:D:03B:UN:EAN008"))); + Segment ifa = new Segment("IFA", 2); + Segment rea = new Segment("REA", 3); + Segment unt = new Segment("UNT", 4, List.of(new Field(1, "4"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, ifa, rea, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + // When + ValidationResult result = validator.validate(inputFile); + + // Then + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_015", error.errorCode()); + assertEquals("ASVREC-Nachricht muss ein IVA-Segment enthalten", error.description()); + assertEquals("", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("IVA-Segment erforderlich für ASVREC-Nachrichten", error.expectedRule()); + } + + @Test + void validate_shouldReportMultipleErrorsWhenAsvrecMissingMultipleSegments() { + // Given + Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ASVREC:D:03B:UN:EAN008"))); + Segment unt = new Segment("UNT", 2, List.of(new Field(1, "2"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + // When + ValidationResult result = validator.validate(inputFile); + + // Then + assertTrue(result.hasErrors()); + assertEquals(3, result.getErrors().size()); + + // Check that all three errors are present + boolean foundIfaError = false; + boolean foundReaError = false; + boolean foundIvaError = false; + + for (ValidationError error : result.getErrors()) { + if ("STRUCTURE_013".equals(error.errorCode())) { + foundIfaError = true; + assertEquals("ASVREC-Nachricht muss ein IFA-Segment enthalten", error.description()); + assertEquals("", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("IFA-Segment erforderlich für ASVREC-Nachrichten", error.expectedRule()); + } else if ("STRUCTURE_014".equals(error.errorCode())) { + foundReaError = true; + assertEquals("ASVREC-Nachricht muss ein REA-Segment enthalten", error.description()); + assertEquals("", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("REA-Segment erforderlich für ASVREC-Nachrichten", error.expectedRule()); + } else if ("STRUCTURE_015".equals(error.errorCode())) { + foundIvaError = true; + assertEquals("ASVREC-Nachricht muss ein IVA-Segment enthalten", error.description()); + assertEquals("", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("IVA-Segment erforderlich für ASVREC-Nachrichten", error.expectedRule()); + } + } + + assertTrue(foundIfaError, "Expected STRUCTURE_013 error for missing IFA"); + assertTrue(foundReaError, "Expected STRUCTURE_014 error for missing REA"); + assertTrue(foundIvaError, "Expected STRUCTURE_015 error for missing IVA"); + } + + @Test + void validate_shouldNotReportErrorForAsvfehWithoutIfaReaIvaSegments() { + // Given + Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ASVFEH:D:03B:UN:EAN008"))); + Segment unt = new Segment("UNT", 2, List.of(new Field(1, "2"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + // When + ValidationResult result = validator.validate(inputFile); + + // Then + // Should not have any of the new STRUCTURE_013, STRUCTURE_014, STRUCTURE_015 errors + assertFalse(result.getErrors().stream() + .anyMatch(error -> "STRUCTURE_013".equals(error.errorCode()) || + "STRUCTURE_014".equals(error.errorCode()) || + "STRUCTURE_015".equals(error.errorCode()))); + assertFalse(result.hasErrors(), "There should be no validation errors for valid ASVFEH without IFA/REA/IVA"); + } + + @Test + void validate_shouldNotReportErrorWhenUnhIsMissing() { + // Given - Message without UNH segment + Segment ifa = new Segment("IFA", 1); + Segment rea = new Segment("REA", 2); + Segment iva = new Segment("IVA", 3); + Segment unt = new Segment("UNT", 4, List.of(new Field(1, "4"), new Field(2, "12345"))); + Message message = new Message(1, List.of(ifa, rea, iva, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + // When + ValidationResult result = validator.validate(inputFile); + + // Then + // Should not have any of the new STRUCTURE_013, STRUCTURE_014, STRUCTURE_015 errors + // Should have exactly one error for missing UNH (STRUCTURE_009) + assertEquals(1, result.getErrors().size(), "Should have exactly one error for missing UNH"); + assertEquals("STRUCTURE_009", result.getErrors().get(0).errorCode(), "Error should be STRUCTURE_009 for missing UNH"); + } +} \ No newline at end of file diff --git a/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorTest.java b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorTest.java index 1010716..d488594 100644 --- a/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorTest.java +++ b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorTest.java @@ -78,8 +78,11 @@ class DefaultStructureValidatorTest { Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ASVREC:D:03B:UN:EAN008"))); Segment segment1 = new Segment("SEG1", 2); Segment segment2 = new Segment("SEG2", 2); // Duplicate position - Segment unt = new Segment("UNT", 4, List.of(new Field(1, "4"), new Field(2, "12345"))); - Message message = new Message(1, List.of(unh, segment1, segment2, unt)); + Segment ifa = new Segment("IFA", 3); + Segment rea = new Segment("REA", 5); + Segment iva = new Segment("IVA", 6); + Segment unt = new Segment("UNT", 7, List.of(new Field(1, "7"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, segment1, segment2, ifa, rea, iva, unt)); InputFile inputFile = new InputFile("test.txt", List.of(message)); // When @@ -104,8 +107,11 @@ class DefaultStructureValidatorTest { Field field1 = new Field(1, "value1"); Field field2 = new Field(1, "value2"); // Duplicate position Segment segment = new Segment("SEG1", 2, List.of(field1, field2)); - Segment unt = new Segment("UNT", 3, List.of(new Field(1, "3"), new Field(2, "12345"))); - Message message = new Message(1, List.of(unh, segment, unt)); + Segment ifa = new Segment("IFA", 3); + Segment rea = new Segment("REA", 4); + Segment iva = new Segment("IVA", 5); + Segment unt = new Segment("UNT", 6, List.of(new Field(1, "6"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, segment, ifa, rea, iva, unt)); InputFile inputFile = new InputFile("test.txt", List.of(message)); // When @@ -257,7 +263,7 @@ class DefaultStructureValidatorTest { assertEquals(1, error.segmentPosition()); assertEquals("0074", error.fieldName()); assertEquals(1, error.fieldPosition()); - assertEquals("8 != 9", error.actualValue()); + assertEquals("11 != 12", error.actualValue()); assertEquals("Segmentanzahl in UNT muss mit tatsächlicher Anzahl übereinstimmen", error.expectedRule()); } @@ -325,7 +331,7 @@ class DefaultStructureValidatorTest { assertEquals(1, error.segmentPosition()); assertEquals("0074", error.fieldName()); assertEquals(1, error.fieldPosition()); - assertEquals("10 != 9", error.actualValue()); + assertEquals("13 != 12", error.actualValue()); assertEquals("Segmentanzahl in UNT muss mit tatsächlicher Anzahl übereinstimmen", error.expectedRule()); } @@ -473,7 +479,7 @@ class DefaultStructureValidatorTest { assertEquals("UNH muss vor UNT stehen", error.description()); assertEquals("UNH/UNT", error.segmentName()); assertEquals(1, error.segmentPosition()); - assertEquals("UNH at position 9, UNT at position 1", error.actualValue()); + assertEquals("UNH at position 12, UNT at position 1", error.actualValue()); assertEquals("UNH muss vor UNT stehen", error.expectedRule()); } diff --git a/src/test/resources/invalid-segment-count-non-numeric.asv b/src/test/resources/invalid-segment-count-non-numeric.asv index bfaaf23..fdf3867 100644 --- a/src/test/resources/invalid-segment-count-non-numeric.asv +++ b/src/test/resources/invalid-segment-count-non-numeric.asv @@ -6,4 +6,7 @@ LIN+1++Product123:SA' QTY+21:10:PCE' UNS+S' CNT+2:1' +IFA+1' +REA+1' +IVA+1' UNT+abc+12345' \ No newline at end of file diff --git a/src/test/resources/invalid-segment-count-too-large.asv b/src/test/resources/invalid-segment-count-too-large.asv index a5ac1f7..0774eb8 100644 --- a/src/test/resources/invalid-segment-count-too-large.asv +++ b/src/test/resources/invalid-segment-count-too-large.asv @@ -6,4 +6,7 @@ LIN+1++Product123:SA' QTY+21:10:PCE' UNS+S' CNT+2:1' -UNT+10+12345' \ No newline at end of file +IFA+1' +REA+1' +IVA+1' +UNT+13+12345' \ No newline at end of file diff --git a/src/test/resources/invalid-segment-count-too-small.asv b/src/test/resources/invalid-segment-count-too-small.asv index c129f73..84c2226 100644 --- a/src/test/resources/invalid-segment-count-too-small.asv +++ b/src/test/resources/invalid-segment-count-too-small.asv @@ -6,4 +6,7 @@ LIN+1++Product123:SA' QTY+21:10:PCE' UNS+S' CNT+2:1' -UNT+8+12345' \ No newline at end of file +IFA+1' +REA+1' +IVA+1' +UNT+11+12345' \ No newline at end of file diff --git a/src/test/resources/unh-before-unt-valid.asv b/src/test/resources/unh-before-unt-valid.asv index 85c7668..ef19159 100644 --- a/src/test/resources/unh-before-unt-valid.asv +++ b/src/test/resources/unh-before-unt-valid.asv @@ -6,4 +6,7 @@ LIN+1++Product123:SA' QTY+21:10:PCE' UNS+S' CNT+2:1' -UNT+9+1' \ No newline at end of file +IFA+1' +REA+1' +IVA+1' +UNT+12+1' \ No newline at end of file diff --git a/src/test/resources/unh-unt-match.asv b/src/test/resources/unh-unt-match.asv index f4634d0..b768ca1 100644 --- a/src/test/resources/unh-unt-match.asv +++ b/src/test/resources/unh-unt-match.asv @@ -6,4 +6,7 @@ LIN+1++Product123:SA' QTY+21:10:PCE' UNS+S' CNT+2:1' -UNT+9+12345' \ No newline at end of file +IFA+1' +REA+1' +IVA+1' +UNT+12+12345' \ No newline at end of file diff --git a/src/test/resources/unh-unt-mismatch.asv b/src/test/resources/unh-unt-mismatch.asv index 630dfd5..741242e 100644 --- a/src/test/resources/unh-unt-mismatch.asv +++ b/src/test/resources/unh-unt-mismatch.asv @@ -6,4 +6,7 @@ LIN+1++Product123:SA' QTY+21:10:PCE' UNS+S' CNT+2:1' -UNT+9+54321' \ No newline at end of file +IFA+1' +REA+1' +IVA+1' +UNT+12+54321' \ No newline at end of file diff --git a/src/test/resources/unt-before-unh-invalid.asv b/src/test/resources/unt-before-unh-invalid.asv index 2ff4305..dc78e69 100644 --- a/src/test/resources/unt-before-unh-invalid.asv +++ b/src/test/resources/unt-before-unh-invalid.asv @@ -1,4 +1,4 @@ -UNT+9+1' +UNT+12+1' BGM+220+100001' DTM+137:20260325:102' NAD+BY+5000000000000:16++Customer Name' @@ -6,4 +6,7 @@ LIN+1++Product123:SA' QTY+21:10:PCE' UNS+S' CNT+2:1' +IFA+1' +REA+1' +IVA+1' UNH+1+ASVREC:D:03B:UN:EAN008' \ No newline at end of file diff --git a/src/test/resources/valid-segment-count.asv b/src/test/resources/valid-segment-count.asv index f4634d0..b768ca1 100644 --- a/src/test/resources/valid-segment-count.asv +++ b/src/test/resources/valid-segment-count.asv @@ -6,4 +6,7 @@ LIN+1++Product123:SA' QTY+21:10:PCE' UNS+S' CNT+2:1' -UNT+9+12345' \ No newline at end of file +IFA+1' +REA+1' +IVA+1' +UNT+12+12345' \ No newline at end of file