Pflicht-Präsenz von UNH und UNT pro Nachricht validieren

This commit is contained in:
2026-03-26 10:30:43 +01:00
parent 6152fe58d2
commit f7af574a97
10 changed files with 405 additions and 21 deletions

View File

@@ -24,6 +24,8 @@ import de.gecheckt.asv.validation.model.ValidationSeverity;
* 6. Nachrichtenpositionen innerhalb einer Eingabedatei müssen eindeutig und positiv sein
* 7. UNH- und UNT-Referenznummern müssen innerhalb einer Nachricht übereinstimmen
* 8. Die im UNT angegebene Segmentanzahl muss der tatsächlichen Anzahl der Segmente entsprechen
* 9. Eine Nachricht muss mindestens ein UNH-Segment enthalten
* 10. Eine Nachricht muss mindestens ein UNT-Segment enthalten
*/
public class DefaultStructureValidator implements StructureValidator {
@@ -86,11 +88,79 @@ public class DefaultStructureValidator implements StructureValidator {
// Validiere Segmente in dieser Nachricht
validateSegments(message.segments(), messagePosition, errors);
// Prüfe zuerst, ob grundlegende Strukturfehler vorliegen (keine Segmente)
// In diesem Fall können wir keine UNH/UNT-Prüfung durchführen
if (message.segments().isEmpty()) {
continue;
}
// Regel 9: Nachricht muss mindestens ein UNH-Segment enthalten
validateUnhPresence(message, errors);
// Regel 10: Nachricht muss mindestens ein UNT-Segment enthalten
validateUntPresence(message, errors);
// Regel 7: UNH- und UNT-Referenznummern müssen übereinstimmen
validateUnhUntReferenceNumbers(message, errors);
// Nur prüfen, wenn beide Segmente vorhanden sind
if (message.getFirstSegment("UNH").isPresent() && message.getFirstSegment("UNT").isPresent()) {
validateUnhUntReferenceNumbers(message, errors);
}
// Regel 8: UNT-Segmentanzahl muss mit tatsächlicher Segmentanzahl übereinstimmen
validateUntSegmentCount(message, errors);
// Nur prüfen, wenn UNT-Segment vorhanden ist
if (message.getFirstSegment("UNT").isPresent()) {
validateUntSegmentCount(message, errors);
}
}
}
/**
* Validiert, dass mindestens ein UNH-Segment in der Nachricht vorhanden ist.
*
* @param message die zu validierende Nachricht
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
*/
private void validateUnhPresence(Message message, List<ValidationError> errors) {
var unhSegment = message.getFirstSegment("UNH");
// Wenn kein UNH-Segment vorhanden ist, Fehler hinzufügen
if (unhSegment.isEmpty()) {
errors.add(createError(
"STRUCTURE_009",
"Nachricht muss mindestens ein UNH-Segment enthalten",
ValidationSeverity.ERROR,
"",
message.messagePosition(),
"",
0,
"",
"UNH-Segment erforderlich"
));
}
}
/**
* Validiert, dass mindestens ein UNT-Segment in der Nachricht vorhanden ist.
*
* @param message die zu validierende Nachricht
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
*/
private void validateUntPresence(Message message, List<ValidationError> errors) {
var untSegment = message.getFirstSegment("UNT");
// Wenn kein UNT-Segment vorhanden ist, Fehler hinzufügen
if (untSegment.isEmpty()) {
errors.add(createError(
"STRUCTURE_010",
"Nachricht muss mindestens ein UNT-Segment enthalten",
ValidationSeverity.ERROR,
"",
message.messagePosition(),
"",
0,
"",
"UNT-Segment erforderlich"
));
}
}
@@ -243,7 +313,9 @@ public class DefaultStructureValidator implements StructureValidator {
}
// Validate fields in this segment
validateFields(segment.fields(), segmentName, segmentPosition, errors);
if (!segment.fields().isEmpty()) {
validateFields(segment.fields(), segmentName, segmentPosition, errors);
}
}
}
@@ -256,6 +328,10 @@ public class DefaultStructureValidator implements StructureValidator {
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
*/
private void validateFields(List<Field> fields, String segmentName, int segmentPosition, List<ValidationError> errors) {
if (fields.isEmpty()) {
return;
}
var fieldPositions = new HashSet<Integer>();
for (var field : fields) {

View File

@@ -73,41 +73,53 @@ class DefaultStructureValidatorTest {
@Test
void validate_shouldReportErrorWhenSegmentHasDuplicatePositions() {
Segment segment1 = new Segment("SEG1", 1);
Segment segment2 = new Segment("SEG2", 1); // Duplicate position
Message message = new Message(1, List.of(segment1, segment2));
// Manually create segments with duplicate positions to bypass parser validation
// Also include required UNH and UNT segments
Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ORDERS: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));
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_005", error.errorCode());
assertEquals("Duplicate segment position: 1", error.description());
assertEquals("Duplicate segment position: 2", error.description());
assertEquals("SEG2", error.segmentName());
assertEquals(1, error.segmentPosition());
assertEquals(2, error.segmentPosition());
}
@Test
void validate_shouldReportErrorWhenFieldHasDuplicatePositions() {
// Create segments with duplicate field positions manually to bypass parser validation
// Also include required UNH and UNT segments
Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ORDERS:D:03B:UN:EAN008")));
Field field1 = new Field(1, "value1");
Field field2 = new Field(1, "value2"); // Duplicate position
Segment segment = new Segment("SEG1", 1, List.of(field1, field2));
Message message = new Message(1, List.of(segment));
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));
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_004", error.errorCode());
assertEquals("Duplicate field position: 1", error.description());
assertEquals("SEG1", error.segmentName());
assertEquals(1, error.segmentPosition());
assertEquals(2, error.segmentPosition());
assertEquals(1, error.fieldPosition());
}
@@ -115,8 +127,10 @@ class DefaultStructureValidatorTest {
void validate_shouldReturnNoErrorsForValidStructure() {
Field field1 = new Field(1, "value1");
Field field2 = new Field(2, "value2");
Segment segment = new Segment("SEG1", 1, List.of(field1, field2));
Message message = new Message(1, List.of(segment));
Segment segment = new Segment("UNH", 1, List.of(new Field(1, "12345")));
Segment segment2 = new Segment("SEG1", 2, List.of(field1, field2));
Segment segment3 = new Segment("UNT", 3, List.of(new Field(1, "3"), new Field(2, "12345")));
Message message = new Message(1, List.of(segment, segment2, segment3));
InputFile inputFile = new InputFile("test.txt", List.of(message));
ValidationResult result = validator.validate(inputFile);
@@ -305,4 +319,123 @@ class DefaultStructureValidatorTest {
assertEquals("10 != 9", error.actualValue());
assertEquals("Segmentanzahl in UNT muss mit tatsächlicher Anzahl übereinstimmen", error.expectedRule());
}
@Test
void validate_shouldReportErrorWhenUnhIsMissingInMessage() {
// Manually create a message without UNH segment to ensure only the UNH missing error occurs
Segment bgm = new Segment("BGM", 1, List.of(new Field(1, "220"), new Field(2, "100001")));
Segment dtm = new Segment("DTM", 2, List.of(new Field(1, "137:20260325:102")));
Segment nad = new Segment("NAD", 3, List.of(new Field(1, "BY"), new Field(2, "5000000000000:16"), new Field(3, ""), new Field(4, "Customer Name")));
Segment lin = new Segment("LIN", 4, List.of(new Field(1, "1"), new Field(2, ""), new Field(3, "Product123:SA")));
Segment qty = new Segment("QTY", 5, List.of(new Field(1, "21:10:PCE")));
Segment uns = new Segment("UNS", 6, List.of(new Field(1, "S")));
Segment cnt = new Segment("CNT", 7, List.of(new Field(1, "2:1")));
Segment unt = new Segment("UNT", 8, List.of(new Field(1, "8"), new Field(2, "missing")));
// Create message without UNH
Message message = new Message(1, List.of(bgm, dtm, nad, lin, qty, uns, cnt, unt));
InputFile inputFile = new InputFile("test.txt", List.of(message));
// When
ValidationResult result = validator.validate(inputFile);
// Then
assertTrue(result.hasErrors());
// Expect exactly one error for missing UNH (STRUCTURE_009)
assertEquals(1, result.getErrors().size());
ValidationError error = result.getErrors().get(0);
assertEquals("STRUCTURE_009", error.errorCode());
assertEquals("Nachricht muss mindestens ein UNH-Segment enthalten", error.description());
assertEquals("", error.segmentName());
assertEquals(1, error.segmentPosition());
assertEquals("UNH-Segment erforderlich", error.expectedRule());
}
@Test
void validate_shouldReportErrorWhenUntIsMissingInMessage() throws IOException, InputFileParseException {
// Given
SegmentLineTokenizer tokenizer = new DefaultSegmentLineTokenizer();
DefaultInputFileParser parser = new DefaultInputFileParser(tokenizer);
String fileName = "no-unt.asv";
Path filePath = Path.of("src/test/resources/no-unt.asv");
String fileContent = Files.readString(filePath);
// When
InputFile inputFile = parser.parse(fileName, fileContent);
ValidationResult result = validator.validate(inputFile);
// Then
assertTrue(result.hasErrors());
// Expect exactly one error for missing UNT (STRUCTURE_010)
assertEquals(1, result.getErrors().size());
ValidationError error = result.getErrors().get(0);
assertEquals("STRUCTURE_010", error.errorCode());
assertEquals("Nachricht muss mindestens ein UNT-Segment enthalten", error.description());
assertEquals("", error.segmentName());
assertEquals(1, error.segmentPosition());
assertEquals("UNT-Segment erforderlich", error.expectedRule());
}
@Test
void validate_shouldReportTwoErrorsWhenBothUnhAndUntAreMissing() throws IOException, InputFileParseException {
// Given
SegmentLineTokenizer tokenizer = new DefaultSegmentLineTokenizer();
DefaultInputFileParser parser = new DefaultInputFileParser(tokenizer);
String fileName = "no-unh-unt.asv";
Path filePath = Path.of("src/test/resources/no-unh-unt.asv");
String fileContent = Files.readString(filePath);
// When
InputFile inputFile = parser.parse(fileName, fileContent);
ValidationResult result = validator.validate(inputFile);
// Then
assertTrue(result.hasErrors());
// Expect exactly two errors: one for missing UNH (STRUCTURE_009) and one for missing UNT (STRUCTURE_010)
assertEquals(2, result.getErrors().size());
// Check that both errors are present
boolean foundUnhError = false;
boolean foundUntError = false;
for (ValidationError error : result.getErrors()) {
if ("STRUCTURE_009".equals(error.errorCode())) {
foundUnhError = true;
assertEquals("Nachricht muss mindestens ein UNH-Segment enthalten", error.description());
assertEquals("", error.segmentName());
assertEquals(1, error.segmentPosition());
assertEquals("UNH-Segment erforderlich", error.expectedRule());
} else if ("STRUCTURE_010".equals(error.errorCode())) {
foundUntError = true;
assertEquals("Nachricht muss mindestens ein UNT-Segment enthalten", error.description());
assertEquals("", error.segmentName());
assertEquals(1, error.segmentPosition());
assertEquals("UNT-Segment erforderlich", error.expectedRule());
}
}
assertTrue(foundUnhError, "Expected STRUCTURE_009 error for missing UNH");
assertTrue(foundUntError, "Expected STRUCTURE_010 error for missing UNT");
}
@Test
void validate_shouldNotReportAdditionalErrorsForUntSegmentCountWhenUntIsMissing() throws IOException, InputFileParseException {
// Given
SegmentLineTokenizer tokenizer = new DefaultSegmentLineTokenizer();
DefaultInputFileParser parser = new DefaultInputFileParser(tokenizer);
String fileName = "no-unt.asv";
Path filePath = Path.of("src/test/resources/no-unt.asv");
String fileContent = Files.readString(filePath);
// When
InputFile inputFile = parser.parse(fileName, fileContent);
ValidationResult result = validator.validate(inputFile);
// Then
// Should not have STRUCTURE_008 error for segment count mismatch when UNT is missing
assertFalse(result.getErrors().stream()
.anyMatch(error -> "STRUCTURE_008".equals(error.errorCode())));
}
}

View File

@@ -0,0 +1,3 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
SEG1+value1+value1'
UNT+3+12345'

View File

@@ -0,0 +1,4 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
SEG1+value1+value2'
SEG2+value3+value4'
UNT+3+12345'

View File

@@ -0,0 +1,7 @@
BGM+220+100001'
DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name'
LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'

View File

@@ -1,3 +1,4 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
BGM+220+100001'
DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name'
@@ -5,4 +6,4 @@ LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'
UNT+9+12345'
UNT+9+12345'

View File

@@ -5,4 +5,4 @@ NAD+BY+5000000000000:16++Customer Name'
LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'
CNT+2:1'