Pflichtsegmente IFA, REA und IVA für ASVREC validieren

This commit is contained in:
2026-03-26 18:51:19 +01:00
parent 069a9cf54a
commit 707762579e
11 changed files with 347 additions and 17 deletions

View File

@@ -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<ValidationError> errors) {
private void validateMessageType(Message message, Segment unhSegment, int messagePosition, List<ValidationError> 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<ValidationError> 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.
*

View File

@@ -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");
}
}

View File

@@ -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());
}

View File

@@ -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'

View File

@@ -6,4 +6,7 @@ LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'
UNT+10+12345'
IFA+1'
REA+1'
IVA+1'
UNT+13+12345'

View File

@@ -6,4 +6,7 @@ LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'
UNT+8+12345'
IFA+1'
REA+1'
IVA+1'
UNT+11+12345'

View File

@@ -6,4 +6,7 @@ LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'
UNT+9+1'
IFA+1'
REA+1'
IVA+1'
UNT+12+1'

View File

@@ -6,4 +6,7 @@ LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'
UNT+9+12345'
IFA+1'
REA+1'
IVA+1'
UNT+12+12345'

View File

@@ -6,4 +6,7 @@ LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'
UNT+9+54321'
IFA+1'
REA+1'
IVA+1'
UNT+12+54321'

View File

@@ -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'

View File

@@ -6,4 +6,7 @@ LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'
UNT+9+12345'
IFA+1'
REA+1'
IVA+1'
UNT+12+12345'