UNH-Nachrichtentyp in Testressourcen auf ASVREC umstellen

This commit is contained in:
2026-03-26 18:08:54 +01:00
parent f082289a01
commit 069a9cf54a
14 changed files with 273 additions and 125 deletions

View File

@@ -27,6 +27,7 @@ import de.gecheckt.asv.validation.model.ValidationSeverity;
* 9. Eine Nachricht muss mindestens ein UNH-Segment enthalten
* 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
*/
public class DefaultStructureValidator implements StructureValidator {
@@ -95,28 +96,35 @@ public class DefaultStructureValidator implements StructureValidator {
continue;
}
// Hole die UNH- und UNT-Segmente einmalig für alle Validierungen
var unhSegment = message.getFirstSegment("UNH");
var untSegment = message.getFirstSegment("UNT");
// Merke, ob grundlegende Strukturfehler vorliegen
boolean hasBasicStructureErrors = false;
// Regel 9: Nachricht muss mindestens ein UNH-Segment enthalten
validateUnhPresence(message, errors);
validateUnhPresence(message, errors, unhSegment);
// Regel 10: Nachricht muss mindestens ein UNT-Segment enthalten
validateUntPresence(message, errors);
validateUntPresence(message, errors, untSegment);
// Regel 7: UNH- und UNT-Referenznummern müssen übereinstimmen
// Nur prüfen, wenn beide Segmente vorhanden sind
if (message.getFirstSegment("UNH").isPresent() && message.getFirstSegment("UNT").isPresent()) {
validateUnhUntReferenceNumbers(message, errors);
// Prüfe, ob grundlegende Strukturfehler vorliegen
if (unhSegment.isEmpty() || untSegment.isEmpty()) {
hasBasicStructureErrors = true;
}
// Regel 8: UNT-Segmentanzahl muss mit tatsächlicher Segmentanzahl übereinstimmen
// Nur prüfen, wenn UNT-Segment vorhanden ist
if (message.getFirstSegment("UNT").isPresent()) {
validateUntSegmentCount(message, errors);
}
// Regel 11: UNH muss vor UNT stehen
// Nur prüfen, wenn beide Segmente vorhanden sind
if (message.getFirstSegment("UNH").isPresent() && message.getFirstSegment("UNT").isPresent()) {
validateUnhBeforeUnt(message, errors);
// Führe weitere Validierungen nur durch, wenn beide Segmente vorhanden sind
// und keine grundlegenden Strukturfehler vorliegen
if (!hasBasicStructureErrors && unhSegment.isPresent() && untSegment.isPresent()) {
// Regel 7: UNH- und UNT-Referenznummern müssen übereinstimmen
validateUnhUntReferenceNumbers(message, errors, unhSegment.get(), untSegment.get());
// Regel 11: UNH muss vor UNT stehen
validateUnhBeforeUnt(message, errors, unhSegment.get(), untSegment.get());
// Regel 8: UNT-Segmentanzahl muss mit tatsächlicher Segmentanzahl übereinstimmen
validateUntSegmentCount(message, errors, untSegment.get());
}
}
}
@@ -126,10 +134,10 @@ public class DefaultStructureValidator implements StructureValidator {
*
* @param message die zu validierende Nachricht
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
* @param unhSegment das UNH-Segment der Nachricht (kann leer sein)
*/
private void validateUnhPresence(Message message, List<ValidationError> errors) {
var unhSegment = message.getFirstSegment("UNH");
private void validateUnhPresence(Message message, List<ValidationError> errors,
java.util.Optional<Segment> unhSegment) {
// Wenn kein UNH-Segment vorhanden ist, Fehler hinzufügen
if (unhSegment.isEmpty()) {
errors.add(createError(
@@ -144,6 +152,47 @@ public class DefaultStructureValidator implements StructureValidator {
"UNH-Segment erforderlich"
));
}
// Wenn UNH-Segment vorhanden ist, prüfe den Nachrichtentyp
else {
validateMessageType(unhSegment.get(), message.messagePosition(), errors);
}
}
/**
* Validiert den Nachrichtentyp im UNH-Segment.
*
* @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) {
// 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);
}
// Prüfe, ob der Nachrichtentyp gültig ist
if (!"ASVREC".equals(messageType) && !"ASVFEH".equals(messageType)) {
errors.add(createError(
"STRUCTURE_012",
"Ungültiger Nachrichtentyp in UNH/S009/0065: " + messageType,
ValidationSeverity.ERROR,
"UNH",
messagePosition,
"S009/0065",
2,
messageType,
"Nachrichtentyp muss ASVREC oder ASVFEH sein"
));
}
}
}
/**
@@ -151,10 +200,10 @@ public class DefaultStructureValidator implements StructureValidator {
*
* @param message die zu validierende Nachricht
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
* @param untSegment das UNT-Segment der Nachricht (kann leer sein)
*/
private void validateUntPresence(Message message, List<ValidationError> errors) {
var untSegment = message.getFirstSegment("UNT");
private void validateUntPresence(Message message, List<ValidationError> errors,
java.util.Optional<Segment> untSegment) {
// Wenn kein UNT-Segment vorhanden ist, Fehler hinzufügen
if (untSegment.isEmpty()) {
errors.add(createError(
@@ -176,40 +225,36 @@ public class DefaultStructureValidator implements StructureValidator {
*
* @param message die zu validierende Nachricht
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
* @param untSegment das UNT-Segment
*/
private void validateUntSegmentCount(Message message, List<ValidationError> errors) {
var untSegment = message.getFirstSegment("UNT");
private void validateUntSegmentCount(Message message, List<ValidationError> errors, Segment untSegment) {
var fields = untSegment.fields();
// Nur prüfen, wenn UNT-Segment vorhanden ist
if (untSegment.isPresent()) {
var fields = untSegment.get().fields();
// Prüfen, ob das erste Feld (Segmentanzahl) vorhanden ist
if (!fields.isEmpty()) {
var countField = fields.get(0); // 0074 - Anzahl der Segmente
var countValue = countField.rawValue();
// Prüfen, ob das erste Feld (Segmentanzahl) vorhanden ist
if (!fields.isEmpty()) {
var countField = fields.get(0); // 0074 - Anzahl der Segmente
var countValue = countField.rawValue();
// Nur prüfen, wenn der Wert numerisch interpretiert werden kann
try {
int declaredCount = Integer.parseInt(countValue);
int actualCount = message.segments().size();
// Nur prüfen, wenn der Wert numerisch interpretiert werden kann
try {
int declaredCount = Integer.parseInt(countValue);
int actualCount = message.segments().size();
if (declaredCount != actualCount) {
errors.add(createError(
"STRUCTURE_008",
"Die im UNT angegebene Segmentanzahl entspricht nicht der tatsächlichen Anzahl",
ValidationSeverity.ERROR,
"UNT",
message.messagePosition(),
"0074",
1,
declaredCount + " != " + actualCount,
"Segmentanzahl in UNT muss mit tatsächlicher Anzahl übereinstimmen"
));
}
} catch (NumberFormatException e) {
// Nicht-numerischer Wert - keine zusätzliche Fehlermeldung gemäß Anforderung
if (declaredCount != actualCount) {
errors.add(createError(
"STRUCTURE_008",
"Die im UNT angegebene Segmentanzahl entspricht nicht der tatsächlichen Anzahl",
ValidationSeverity.ERROR,
"UNT",
message.messagePosition(),
"0074",
1,
declaredCount + " != " + actualCount,
"Segmentanzahl in UNT muss mit tatsächlicher Anzahl übereinstimmen"
));
}
} catch (NumberFormatException e) {
// Nicht-numerischer Wert - keine zusätzliche Fehlermeldung gemäß Anforderung
}
}
}
@@ -219,30 +264,27 @@ public class DefaultStructureValidator implements StructureValidator {
*
* @param message die zu validierende Nachricht
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
* @param unhSegment das UNH-Segment
* @param untSegment das UNT-Segment
*/
private void validateUnhBeforeUnt(Message message, List<ValidationError> errors) {
var unhSegment = message.getFirstSegment("UNH");
var untSegment = message.getFirstSegment("UNT");
private void validateUnhBeforeUnt(Message message, List<ValidationError> errors,
Segment unhSegment, Segment untSegment) {
int unhPosition = unhSegment.segmentPosition();
int untPosition = untSegment.segmentPosition();
// Prüft, ob beide Segmente vorhanden sind, bevor eine Validierung erfolgt
if (unhSegment.isPresent() && untSegment.isPresent()) {
int unhPosition = unhSegment.get().segmentPosition();
int untPosition = untSegment.get().segmentPosition();
// UNH muss vor UNT stehen
if (unhPosition > untPosition) {
errors.add(createError(
"STRUCTURE_011",
"UNH muss vor UNT stehen",
ValidationSeverity.ERROR,
"UNH/UNT",
message.messagePosition(),
"",
0,
"UNH at position " + unhPosition + ", UNT at position " + untPosition,
"UNH muss vor UNT stehen"
));
}
// UNH muss vor UNT stehen
if (unhPosition > untPosition) {
errors.add(createError(
"STRUCTURE_011",
"UNH muss vor UNT stehen",
ValidationSeverity.ERROR,
"UNH/UNT",
message.messagePosition(),
"",
0,
"UNH at position " + unhPosition + ", UNT at position " + untPosition,
"UNH muss vor UNT stehen"
));
}
}
@@ -251,42 +293,39 @@ public class DefaultStructureValidator implements StructureValidator {
*
* @param message die zu validierende Nachricht
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
* @param unhSegment das UNH-Segment
* @param untSegment das UNT-Segment
*/
private void validateUnhUntReferenceNumbers(Message message, List<ValidationError> errors) {
var unhSegment = message.getFirstSegment("UNH");
var untSegment = message.getFirstSegment("UNT");
private void validateUnhUntReferenceNumbers(Message message, List<ValidationError> errors,
Segment unhSegment, Segment untSegment) {
var unhFields = unhSegment.fields();
var untFields = untSegment.fields();
// Prüft, ob beide Segmente vorhanden sind, bevor eine Validierung erfolgt
if (unhSegment.isPresent() && untSegment.isPresent()) {
var unhFields = unhSegment.get().fields();
var untFields = untSegment.get().fields();
// UNH-Referenznummer ist im 1. Feld (erstes Feld nach dem Segmentnamen)
// UNT-Referenznummer ist im 2. Feld (zweites Feld nach dem Segmentnamen)
if (!unhFields.isEmpty() && untFields.size() >= 2) {
var unhReference = unhFields.get(0).rawValue();
var untReference = untFields.get(1).rawValue();
// UNH-Referenznummer ist im 1. Feld (erstes Feld nach dem Segmentnamen)
// UNT-Referenznummer ist im 2. Feld (zweites Feld nach dem Segmentnamen)
if (!unhFields.isEmpty() && untFields.size() >= 2) {
var unhReference = unhFields.get(0).rawValue();
var untReference = untFields.get(1).rawValue();
// Entferne möglicherweise vorhandenes abschließendes Hochkomma bei untReference
String normalizedUntReference = untReference;
if (untReference.endsWith("'")) {
normalizedUntReference = untReference.substring(0, untReference.length() - 1);
}
// Vergleiche die Referenznummern
if (!unhReference.equals(normalizedUntReference)) {
errors.add(createError(
"STRUCTURE_007",
"UNH- und UNT-Referenznummern stimmen nicht überein",
ValidationSeverity.ERROR,
"UNH/UNT",
message.messagePosition(),
"",
0,
unhReference + " != " + normalizedUntReference,
"Referenznummern in UNH und UNT müssen übereinstimmen"
));
}
// Entferne möglicherweise vorhandenes abschließendes Hochkomma bei untReference
String normalizedUntReference = untReference;
if (untReference.endsWith("'")) {
normalizedUntReference = untReference.substring(0, untReference.length() - 1);
}
// Vergleiche die Referenznummern
if (!unhReference.equals(normalizedUntReference)) {
errors.add(createError(
"STRUCTURE_007",
"UNH- und UNT-Referenznummern stimmen nicht überein",
ValidationSeverity.ERROR,
"UNH/UNT",
message.messagePosition(),
"",
0,
unhReference + " != " + normalizedUntReference,
"Referenznummern in UNH und UNT müssen übereinstimmen"
));
}
}
}

View File

@@ -75,7 +75,7 @@ class DefaultStructureValidatorTest {
void validate_shouldReportErrorWhenSegmentHasDuplicatePositions() {
// 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 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")));
@@ -100,7 +100,7 @@ class DefaultStructureValidatorTest {
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")));
Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "ASVREC:D:03B:UN:EAN008")));
Field field1 = new Field(1, "value1");
Field field2 = new Field(1, "value2"); // Duplicate position
Segment segment = new Segment("SEG1", 2, List.of(field1, field2));

View File

@@ -0,0 +1,116 @@
package de.gecheckt.asv.validation.structure;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
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.parser.DefaultInputFileParser;
import de.gecheckt.asv.parser.DefaultSegmentLineTokenizer;
import de.gecheckt.asv.parser.InputFileParseException;
import de.gecheckt.asv.parser.SegmentLineTokenizer;
import de.gecheckt.asv.validation.model.ValidationError;
import de.gecheckt.asv.validation.model.ValidationResult;
class DefaultStructureValidatorTestAdditional {
private DefaultStructureValidator validator;
@BeforeEach
void setUp() {
validator = new DefaultStructureValidator();
}
@Test
void validate_shouldNotReportErrorWhenMessageTypeIsASVREC() {
// 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
// Should not have STRUCTURE_012 error for valid ASVREC message type
assertFalse(result.getErrors().stream()
.anyMatch(error -> "STRUCTURE_012".equals(error.errorCode())));
assertFalse(result.hasErrors(), "There should be no validation errors for valid ASVREC message type");
}
@Test
void validate_shouldNotReportErrorWhenMessageTypeIsASVFEH() {
// 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 STRUCTURE_012 error for valid ASVFEH message type
assertFalse(result.getErrors().stream()
.anyMatch(error -> "STRUCTURE_012".equals(error.errorCode())));
assertFalse(result.hasErrors(), "There should be no validation errors for valid ASVFEH message type");
}
@Test
void validate_shouldReportErrorWhenMessageTypeIsInvalid() {
// Given
Segment unh = new Segment("UNH", 1, List.of(new Field(1, "12345"), new Field(2, "INVALID: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(1, result.getErrors().size());
ValidationError error = result.getErrors().get(0);
assertEquals("STRUCTURE_012", error.errorCode());
assertEquals("Ungültiger Nachrichtentyp in UNH/S009/0065: INVALID", error.description());
assertEquals("UNH", error.segmentName());
assertEquals(1, error.segmentPosition());
assertEquals("S009/0065", error.fieldName());
assertEquals(2, error.fieldPosition());
assertEquals("INVALID", error.actualValue());
assertEquals("Nachrichtentyp muss ASVREC oder ASVFEH sein", error.expectedRule());
}
@Test
void validate_shouldNotReportAdditionalErrorWhenUnhIsMissing() throws IOException, InputFileParseException {
// Given
SegmentLineTokenizer tokenizer = new DefaultSegmentLineTokenizer();
DefaultInputFileParser parser = new DefaultInputFileParser(tokenizer);
String fileName = "no-unh.asv";
Path filePath = Path.of("src/test/resources/no-unh.asv");
String fileContent = Files.readString(filePath);
// When
InputFile inputFile = parser.parse(fileName, fileContent);
ValidationResult result = validator.validate(inputFile);
// Then
// Should not have STRUCTURE_012 error for missing UNH
assertFalse(result.getErrors().stream()
.anyMatch(error -> "STRUCTURE_012".equals(error.errorCode())));
// 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

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

View File

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

View File

@@ -1,4 +1,4 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
UNH+12345+ASVREC:D:03B:UN:EAN008'
BGM+220+100001'
DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name'

View File

@@ -1,4 +1,4 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
UNH+12345+ASVREC:D:03B:UN:EAN008'
BGM+220+100001'
DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name'

View File

@@ -1,4 +1,4 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
UNH+12345+ASVREC:D:03B:UN:EAN008'
BGM+220+100001'
DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name'

View File

@@ -1,4 +1,4 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
UNH+12345+ASVREC:D:03B:UN:EAN008'
BGM+220+100001'
DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name'

View File

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

View File

@@ -1,4 +1,4 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
UNH+12345+ASVREC:D:03B:UN:EAN008'
BGM+220+100001'
DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name'

View File

@@ -1,4 +1,4 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
UNH+12345+ASVREC:D:03B:UN:EAN008'
BGM+220+100001'
DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name'

View File

@@ -6,4 +6,4 @@ LIN+1++Product123:SA'
QTY+21:10:PCE'
UNS+S'
CNT+2:1'
UNH+1+ORDERS:D:03B:UN:EAN008'
UNH+1+ASVREC:D:03B:UN:EAN008'

View File

@@ -1,4 +1,4 @@
UNH+12345+ORDERS:D:03B:UN:EAN008'
UNH+12345+ASVREC:D:03B:UN:EAN008'
BGM+220+100001'
DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name'