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 12f830d..222a444 100644 --- a/src/main/java/de/gecheckt/asv/validation/structure/DefaultStructureValidator.java +++ b/src/main/java/de/gecheckt/asv/validation/structure/DefaultStructureValidator.java @@ -29,6 +29,10 @@ import de.gecheckt.asv.validation.model.ValidationSeverity; * 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 + * 14. Für Nachrichten vom Typ ASVREC muss die Reihenfolge IFA vor REA vor IVA eingehalten werden + * 15. Für ASVREC mit Rechnungskennzeichen "0" in REA müssen DGN und LEA vorhanden sein + * 16. Für ASVREC mit Rechnungskennzeichen "1" in REA dürfen DGN und LEA nicht vorhanden sein + * 17. Für ASVREC mit Rechnungskennzeichen "1" in REA muss der Rechnungsbetrag "0,00" sein */ public class DefaultStructureValidator implements StructureValidator { @@ -129,6 +133,15 @@ public class DefaultStructureValidator implements StructureValidator { // Neue Regel: Für ASVREC-Nachrichten müssen IFA, REA und IVA vorhanden sein validateAsvrecRequiredSegments(message, errors); + + // Neue Regel: Für ASVREC-Nachrichten muss die Reihenfolge IFA vor REA vor IVA eingehalten werden + validateAsvrecSegmentOrder(message, errors); + + // Neue Regel: Für ASVREC abhängig vom Rechnungskennzeichen in REA DGN/LEA prüfen + validateAsvrecRechnungskennzeichen(message, errors); + + // Neue Regel: Für ASVREC mit Kennzeichen "1" muss Rechnungsbetrag "0,00" sein + validateAsvrecRechnungsbetragBeiKennzeichen1(message, errors); } } } @@ -499,6 +512,215 @@ public class DefaultStructureValidator implements StructureValidator { } } + /** + * Validiert, dass für ASVREC-Nachrichten die Reihenfolge IFA vor REA vor IVA eingehalten wird. + * Die Regel greift nur, wenn alle drei Segmente vorhanden sind; fehlt eines, wird kein + * zusätzlicher Reihenfolgefehler erzeugt (die Präsenzregel greift bereits). + * + * @param message die zu validierende Nachricht + * @param errors die Liste zum Hinzufügen von Validierungsfehlern + */ + private void validateAsvrecSegmentOrder(Message message, List errors) { + var unhSegment = message.getFirstSegment("UNH"); + if (unhSegment.isEmpty()) { + return; + } + if (!"ASVREC".equals(extractMessageType(unhSegment.get()))) { + return; + } + + var ifaOpt = message.getFirstSegment("IFA"); + var reaOpt = message.getFirstSegment("REA"); + var ivaOpt = message.getFirstSegment("IVA"); + + // Nur prüfen, wenn alle drei Segmente vorhanden sind + if (ifaOpt.isEmpty() || reaOpt.isEmpty() || ivaOpt.isEmpty()) { + return; + } + + int ifaPos = ifaOpt.get().segmentPosition(); + int reaPos = reaOpt.get().segmentPosition(); + int ivaPos = ivaOpt.get().segmentPosition(); + + // IFA muss vor REA stehen + if (ifaPos > reaPos) { + errors.add(createError( + "STRUCTURE_016", + "In ASVREC-Nachrichten muss IFA vor REA stehen", + ValidationSeverity.ERROR, + "IFA/REA", + message.messagePosition(), + "", + 0, + "IFA an Position " + ifaPos + ", REA an Position " + reaPos, + "Reihenfolge IFA vor REA vor IVA erforderlich" + )); + } + + // REA muss vor IVA stehen + if (reaPos > ivaPos) { + errors.add(createError( + "STRUCTURE_016", + "In ASVREC-Nachrichten muss REA vor IVA stehen", + ValidationSeverity.ERROR, + "REA/IVA", + message.messagePosition(), + "", + 0, + "REA an Position " + reaPos + ", IVA an Position " + ivaPos, + "Reihenfolge IFA vor REA vor IVA erforderlich" + )); + } + } + + /** + * Prüft für ASVREC-Nachrichten, ob DGN und LEA abhängig vom Rechnungskennzeichen in REA + * vorhanden oder abwesend sind. + * + * + * @param message die zu validierende Nachricht + * @param errors die Liste zum Hinzufügen von Validierungsfehlern + */ + private void validateAsvrecRechnungskennzeichen(Message message, List errors) { + var unhSegment = message.getFirstSegment("UNH"); + if (unhSegment.isEmpty()) { + return; + } + if (!"ASVREC".equals(extractMessageType(unhSegment.get()))) { + return; + } + + // Rechnungskennzeichen aus erstem Feld von REA lesen + var reaOpt = message.getFirstSegment("REA"); + if (reaOpt.isEmpty()) { + return; + } + var reaFields = reaOpt.get().fields(); + if (reaFields.isEmpty()) { + return; + } + String kennzeichen = reaFields.get(0).rawValue(); + + if ("0".equals(kennzeichen)) { + // DGN muss vorhanden sein + if (message.getFirstSegment("DGN").isEmpty()) { + errors.add(createError( + "STRUCTURE_017", + "ASVREC-Nachricht mit Rechnungskennzeichen '0' muss ein DGN-Segment enthalten", + ValidationSeverity.ERROR, + "REA", + message.messagePosition(), + "", + 0, + "0", + "DGN-Segment erforderlich bei Rechnungskennzeichen '0'" + )); + } + // LEA muss vorhanden sein + if (message.getFirstSegment("LEA").isEmpty()) { + errors.add(createError( + "STRUCTURE_018", + "ASVREC-Nachricht mit Rechnungskennzeichen '0' muss ein LEA-Segment enthalten", + ValidationSeverity.ERROR, + "REA", + message.messagePosition(), + "", + 0, + "0", + "LEA-Segment erforderlich bei Rechnungskennzeichen '0'" + )); + } + } else if ("1".equals(kennzeichen)) { + // DGN darf nicht vorhanden sein + if (message.getFirstSegment("DGN").isPresent()) { + errors.add(createError( + "STRUCTURE_019", + "ASVREC-Nachricht mit Rechnungskennzeichen '1' darf kein DGN-Segment enthalten", + ValidationSeverity.ERROR, + "REA", + message.messagePosition(), + "", + 0, + "1", + "DGN-Segment verboten bei Rechnungskennzeichen '1'" + )); + } + // LEA darf nicht vorhanden sein + if (message.getFirstSegment("LEA").isPresent()) { + errors.add(createError( + "STRUCTURE_020", + "ASVREC-Nachricht mit Rechnungskennzeichen '1' darf kein LEA-Segment enthalten", + ValidationSeverity.ERROR, + "REA", + message.messagePosition(), + "", + 0, + "1", + "LEA-Segment verboten bei Rechnungskennzeichen '1'" + )); + } + } + // Anderes Kennzeichen: keine Prüfung durch diese Regel + } + + /** + * Prüft für ASVREC-Nachrichten mit Rechnungskennzeichen "1", ob der Rechnungsbetrag + * im zweiten Feld von REA den Wert "0,00" hat. + * + * + * @param message die zu validierende Nachricht + * @param errors die Liste zum Hinzufügen von Validierungsfehlern + */ + private void validateAsvrecRechnungsbetragBeiKennzeichen1(Message message, List errors) { + var unhSegment = message.getFirstSegment("UNH"); + if (unhSegment.isEmpty()) { + return; + } + if (!"ASVREC".equals(extractMessageType(unhSegment.get()))) { + return; + } + + var reaOpt = message.getFirstSegment("REA"); + if (reaOpt.isEmpty()) { + return; + } + var reaFields = reaOpt.get().fields(); + if (reaFields.size() < 1) { + return; + } + String kennzeichen = reaFields.get(0).rawValue(); + if (!"1".equals(kennzeichen)) { + return; + } + + // Rechnungsbetrag ist das zweite Feld von REA + if (reaFields.size() < 2) { + return; + } + String betrag = reaFields.get(1).rawValue(); + if (!"0,00".equals(betrag)) { + errors.add(createError( + "STRUCTURE_021", + "ASVREC-Nachricht mit Rechnungskennzeichen '1' muss Rechnungsbetrag '0,00' haben", + ValidationSeverity.ERROR, + "REA", + message.messagePosition(), + "", + 0, + betrag, + "Rechnungsbetrag muss '0,00' sein bei Rechnungskennzeichen '1'" + )); + } + } + /** * Extrahiert den Nachrichtentyp aus dem UNH-Segment. * diff --git a/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecRechnungsbetragTest.java b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecRechnungsbetragTest.java new file mode 100644 index 0000000..9f759a0 --- /dev/null +++ b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecRechnungsbetragTest.java @@ -0,0 +1,208 @@ +package de.gecheckt.asv.validation.structure; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +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; + +/** + * Tests für die Rechnungsbetrag-Regel bei ASVREC mit Rechnungskennzeichen "1": + * - Kennzeichen "1" + Betrag "0,00" -> kein neuer Fehler + * - Kennzeichen "1" + Betrag ungleich "0,00" -> genau ein Fehler STRUCTURE_021 + * - Kennzeichen "0" -> kein Fehler durch diese Regel + * - ASVFEH -> kein Fehler durch diese Regel + * - REA fehlt oder Betrag nicht auswertbar -> kein zusätzlicher Fehler + */ +class DefaultStructureValidatorAsvrecRechnungsbetragTest { + + private DefaultStructureValidator validator; + + @BeforeEach + void setUp() { + validator = new DefaultStructureValidator(); + } + + // --- Hilfsmethoden --- + + /** + * Erstellt eine minimale ASVREC-Nachricht mit den angegebenen inneren Segmenten. + * UNH an Position 1, UNT an letzter Position; Segmentanzahl wird automatisch gesetzt. + */ + private InputFile buildAsvrec(List innerSegments) { + Segment unh = new Segment("UNH", 1, + List.of(new Field(1, "12345"), new Field(2, "ASVREC:D:03B:UN:EAN008"))); + + int untPos = 2 + innerSegments.size(); + int totalSegments = 1 + innerSegments.size() + 1; + Segment unt = new Segment("UNT", untPos, + List.of(new Field(1, String.valueOf(totalSegments)), new Field(2, "12345"))); + + var allSegments = new ArrayList(); + allSegments.add(unh); + allSegments.addAll(innerSegments); + allSegments.add(unt); + + Message message = new Message(1, allSegments); + return new InputFile("test.txt", List.of(message)); + } + + /** + * Erstellt ein REA-Segment mit Rechnungskennzeichen und Rechnungsbetrag. + */ + private Segment rea(int pos, String kennzeichen, String betrag) { + return new Segment("REA", pos, + List.of(new Field(1, kennzeichen), new Field(2, betrag))); + } + + /** + * Erstellt ein REA-Segment mit nur dem Rechnungskennzeichen (kein Betrag). + */ + private Segment reaOhneBetrag(int pos, String kennzeichen) { + return new Segment("REA", pos, List.of(new Field(1, kennzeichen))); + } + + // --- Test 1: Kennzeichen "1" + Betrag "0,00" -> kein neuer Fehler --- + + @Test + void validate_kennzeichen1_betrag0Komma00_keinFehler() { + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "1", "0,00"), + new Segment("IVA", 4) + )); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.hasErrors(), + "Kein Fehler erwartet bei Kennzeichen '1' und Betrag '0,00'"); + } + + // --- Test 2: Kennzeichen "1" + Betrag ungleich "0,00" -> genau ein Fehler STRUCTURE_021 --- + + @Test + void validate_kennzeichen1_betragUngleich0Komma00_fehlerSTRUCTURE021() { + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "1", "123,45"), + new Segment("IVA", 4) + )); + + ValidationResult result = validator.validate(inputFile); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_021", error.errorCode()); + assertEquals("ASVREC-Nachricht mit Rechnungskennzeichen '1' muss Rechnungsbetrag '0,00' haben", + error.description()); + assertEquals("REA", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("123,45", error.actualValue()); + assertEquals("Rechnungsbetrag muss '0,00' sein bei Rechnungskennzeichen '1'", + error.expectedRule()); + } + + // --- Test 3: Kennzeichen "1" + leerer Betrag -> genau ein Fehler STRUCTURE_021 --- + + @Test + void validate_kennzeichen1_betragLeer_fehlerSTRUCTURE021() { + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "1", ""), + new Segment("IVA", 4) + )); + + ValidationResult result = validator.validate(inputFile); + + assertTrue(result.hasErrors()); + long count021 = result.getErrors().stream() + .filter(e -> "STRUCTURE_021".equals(e.errorCode())) + .count(); + assertEquals(1, count021, "Genau ein STRUCTURE_021-Fehler erwartet"); + } + + // --- Test 4: Kennzeichen "0" -> kein Fehler durch diese Regel --- + + @Test + void validate_kennzeichen0_keinFehlerDurchDieseRegel() { + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "0", "500,00"), + new Segment("IVA", 4), + new Segment("DGN", 5), + new Segment("LEA", 6) + )); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.getErrors().stream() + .anyMatch(e -> "STRUCTURE_021".equals(e.errorCode())), + "Kein STRUCTURE_021-Fehler erwartet bei Kennzeichen '0'"); + } + + // --- Test 5: ASVFEH -> kein Fehler durch diese Regel --- + + @Test + void validate_asvfeh_keinFehlerDurchDieseRegel() { + Segment unh = new Segment("UNH", 1, + List.of(new Field(1, "12345"), new Field(2, "ASVFEH:D:03B:UN:EAN008"))); + Segment rea = new Segment("REA", 2, + List.of(new Field(1, "1"), new Field(2, "999,99"))); + Segment unt = new Segment("UNT", 3, + List.of(new Field(1, "3"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, rea, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.getErrors().stream() + .anyMatch(e -> "STRUCTURE_021".equals(e.errorCode())), + "Kein STRUCTURE_021-Fehler erwartet für ASVFEH"); + } + + // --- Test 6: REA fehlt -> kein zusätzlicher Fehler durch diese Regel --- + + @Test + void validate_reaFehlt_keinFehlerDurchDieseRegel() { + // REA fehlt -> STRUCTURE_014 wird erzeugt, aber kein STRUCTURE_021 + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + new Segment("IVA", 3) + )); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.getErrors().stream() + .anyMatch(e -> "STRUCTURE_021".equals(e.errorCode())), + "Kein STRUCTURE_021-Fehler erwartet wenn REA fehlt"); + } + + // --- Test 7: REA ohne Betragsfeld -> kein zusätzlicher Fehler durch diese Regel --- + + @Test + void validate_reaOhneBetragsfeld_keinFehlerDurchDieseRegel() { + // REA hat nur Kennzeichen "1", kein Betragsfeld -> nicht auswertbar -> kein STRUCTURE_021 + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + reaOhneBetrag(3, "1"), + new Segment("IVA", 4) + )); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.getErrors().stream() + .anyMatch(e -> "STRUCTURE_021".equals(e.errorCode())), + "Kein STRUCTURE_021-Fehler erwartet wenn Betragsfeld fehlt"); + } +} diff --git a/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecRechnungskennzeichenTest.java b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecRechnungskennzeichenTest.java new file mode 100644 index 0000000..6528bdf --- /dev/null +++ b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecRechnungskennzeichenTest.java @@ -0,0 +1,248 @@ +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; + +/** + * Tests für die Rechnungskennzeichen-Regel in ASVREC-Nachrichten: + * - Kennzeichen "0": DGN und LEA müssen vorhanden sein. + * - Kennzeichen "1": DGN und LEA dürfen nicht vorhanden sein. + */ +class DefaultStructureValidatorAsvrecRechnungskennzeichenTest { + + private DefaultStructureValidator validator; + + @BeforeEach + void setUp() { + validator = new DefaultStructureValidator(); + } + + // --- Hilfsmethoden --- + + /** + * Erstellt eine minimale ASVREC-Nachricht mit den angegebenen Segmenten. + * UNH ist an Position 1, UNT an der letzten Position. + * Die Segmentanzahl im UNT wird automatisch gesetzt. + */ + private InputFile buildAsvrec(List innerSegments) { + // UNH an Position 1 + Segment unh = new Segment("UNH", 1, + List.of(new Field(1, "12345"), new Field(2, "ASVREC:D:03B:UN:EAN008"))); + + // UNT an letzter Position + int untPos = 2 + innerSegments.size(); + // Gesamtanzahl: UNH + innerSegments + UNT + int totalSegments = 1 + innerSegments.size() + 1; + Segment unt = new Segment("UNT", untPos, + List.of(new Field(1, String.valueOf(totalSegments)), new Field(2, "12345"))); + + var allSegments = new java.util.ArrayList(); + allSegments.add(unh); + allSegments.addAll(innerSegments); + allSegments.add(unt); + + Message message = new Message(1, allSegments); + return new InputFile("test.txt", List.of(message)); + } + + /** Erstellt ein REA-Segment mit dem angegebenen Rechnungskennzeichen. */ + private Segment rea(int pos, String kennzeichen) { + return new Segment("REA", pos, List.of(new Field(1, kennzeichen))); + } + + // --- Test 1: Kennzeichen "0" + DGN und LEA vorhanden -> kein neuer Fehler --- + + @Test + void validate_kennzeichen0_dgnUndLeaVorhanden_keinFehler() { + // IFA(2), REA(3, "0"), IVA(4), DGN(5), LEA(6) + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "0"), + new Segment("IVA", 4), + new Segment("DGN", 5), + new Segment("LEA", 6) + )); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.hasErrors(), + "Kein Fehler erwartet bei Kennzeichen '0' mit DGN und LEA vorhanden"); + } + + // --- Test 2: Kennzeichen "0" + fehlendes DGN -> genau ein Fehler STRUCTURE_017 --- + + @Test + void validate_kennzeichen0_dgnFehlt_fehlerSTRUCTURE017() { + // IFA(2), REA(3, "0"), IVA(4), LEA(5) – kein DGN + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "0"), + new Segment("IVA", 4), + new Segment("LEA", 5) + )); + + ValidationResult result = validator.validate(inputFile); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_017", error.errorCode()); + assertEquals("ASVREC-Nachricht mit Rechnungskennzeichen '0' muss ein DGN-Segment enthalten", + error.description()); + assertEquals("REA", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("DGN-Segment erforderlich bei Rechnungskennzeichen '0'", error.expectedRule()); + } + + // --- Test 3: Kennzeichen "0" + fehlendes LEA -> genau ein Fehler STRUCTURE_018 --- + + @Test + void validate_kennzeichen0_leaFehlt_fehlerSTRUCTURE018() { + // IFA(2), REA(3, "0"), IVA(4), DGN(5) – kein LEA + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "0"), + new Segment("IVA", 4), + new Segment("DGN", 5) + )); + + ValidationResult result = validator.validate(inputFile); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_018", error.errorCode()); + assertEquals("ASVREC-Nachricht mit Rechnungskennzeichen '0' muss ein LEA-Segment enthalten", + error.description()); + assertEquals("REA", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("LEA-Segment erforderlich bei Rechnungskennzeichen '0'", error.expectedRule()); + } + + // --- Test 4: Kennzeichen "1" + vorhandenes DGN -> genau ein Fehler STRUCTURE_019 --- + + @Test + void validate_kennzeichen1_dgnVorhanden_fehlerSTRUCTURE019() { + // IFA(2), REA(3, "1"), IVA(4), DGN(5) – kein LEA + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "1"), + new Segment("IVA", 4), + new Segment("DGN", 5) + )); + + ValidationResult result = validator.validate(inputFile); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_019", error.errorCode()); + assertEquals("ASVREC-Nachricht mit Rechnungskennzeichen '1' darf kein DGN-Segment enthalten", + error.description()); + assertEquals("REA", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("DGN-Segment verboten bei Rechnungskennzeichen '1'", error.expectedRule()); + } + + // --- Test 5: Kennzeichen "1" + vorhandenes LEA -> genau ein Fehler STRUCTURE_020 --- + + @Test + void validate_kennzeichen1_leaVorhanden_fehlerSTRUCTURE020() { + // IFA(2), REA(3, "1"), IVA(4), LEA(5) – kein DGN + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "1"), + new Segment("IVA", 4), + new Segment("LEA", 5) + )); + + ValidationResult result = validator.validate(inputFile); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_020", error.errorCode()); + assertEquals("ASVREC-Nachricht mit Rechnungskennzeichen '1' darf kein LEA-Segment enthalten", + error.description()); + assertEquals("REA", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("LEA-Segment verboten bei Rechnungskennzeichen '1'", error.expectedRule()); + } + + // --- Test 6: Kennzeichen "1" + weder DGN noch LEA -> kein Fehler --- + + @Test + void validate_kennzeichen1_wederDgnNochLea_keinFehler() { + // IFA(2), REA(3, "1"), IVA(4) – kein DGN, kein LEA + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + rea(3, "1"), + new Segment("IVA", 4) + )); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.hasErrors(), + "Kein Fehler erwartet bei Kennzeichen '1' ohne DGN und LEA"); + } + + // --- Test 7: ASVFEH -> kein Fehler durch diese Regel --- + + @Test + void validate_asvfeh_keineRechnungskennzeichenRegel() { + // ASVFEH mit REA Kennzeichen "0" und ohne DGN/LEA -> kein STRUCTURE_017/018 + Segment unh = new Segment("UNH", 1, + List.of(new Field(1, "12345"), new Field(2, "ASVFEH:D:03B:UN:EAN008"))); + Segment rea = new Segment("REA", 2, List.of(new Field(1, "0"))); + Segment unt = new Segment("UNT", 3, + List.of(new Field(1, "3"), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, rea, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.getErrors().stream() + .anyMatch(e -> "STRUCTURE_017".equals(e.errorCode()) + || "STRUCTURE_018".equals(e.errorCode()) + || "STRUCTURE_019".equals(e.errorCode()) + || "STRUCTURE_020".equals(e.errorCode())), + "Kein Rechnungskennzeichen-Fehler erwartet für ASVFEH"); + assertFalse(result.hasErrors(), "Kein Fehler erwartet für ASVFEH"); + } + + // --- Test 8: REA ohne Felder -> kein neuer Fehler --- + + @Test + void validate_reaOhneFelder_keinNeuerFehler() { + // REA ohne Felder -> Kennzeichen nicht auswertbar -> kein neuer Fehler + InputFile inputFile = buildAsvrec(List.of( + new Segment("IFA", 2), + new Segment("REA", 3), // kein Feld + new Segment("IVA", 4) + )); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.getErrors().stream() + .anyMatch(e -> "STRUCTURE_017".equals(e.errorCode()) + || "STRUCTURE_018".equals(e.errorCode()) + || "STRUCTURE_019".equals(e.errorCode()) + || "STRUCTURE_020".equals(e.errorCode())), + "Kein Rechnungskennzeichen-Fehler erwartet wenn REA keine Felder hat"); + } +} diff --git a/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecSegmentOrderTest.java b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecSegmentOrderTest.java new file mode 100644 index 0000000..d591396 --- /dev/null +++ b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorAsvrecSegmentOrderTest.java @@ -0,0 +1,153 @@ +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; + +/** + * Tests für die Reihenfolgeregel der Pflichtsegmente in ASVREC-Nachrichten: + * IFA muss vor REA stehen, REA muss vor IVA stehen. + */ +class DefaultStructureValidatorAsvrecSegmentOrderTest { + + private DefaultStructureValidator validator; + + @BeforeEach + void setUp() { + validator = new DefaultStructureValidator(); + } + + // --- Hilfsmethode --- + + /** + * Erstellt eine minimale, strukturell gültige ASVREC-Nachricht mit den angegebenen + * Segmentpositionen für IFA, REA und IVA. + */ + private InputFile buildAsvrec(int ifaPos, int reaPos, int ivaPos) { + // UNH an Position 1, UNT an der letzten Position + // Segmentanzahl = 5 (UNH, IFA, REA, IVA, UNT) + int untPos = Math.max(Math.max(ifaPos, reaPos), ivaPos) + 1; + 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", ifaPos); + Segment rea = new Segment("REA", reaPos); + Segment iva = new Segment("IVA", ivaPos); + Segment unt = new Segment("UNT", untPos, + List.of(new Field(1, String.valueOf(untPos)), new Field(2, "12345"))); + Message message = new Message(1, List.of(unh, ifa, rea, iva, unt)); + return new InputFile("test.txt", List.of(message)); + } + + // --- Test 1: gültiger Fall --- + + @Test + void validate_shouldNotReportOrderErrorForAsvrecWithCorrectOrder() { + // IFA(2) < REA(3) < IVA(4) -> kein Fehler + InputFile inputFile = buildAsvrec(2, 3, 4); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.hasErrors(), + "Kein Fehler erwartet bei korrekter Reihenfolge IFA < REA < IVA"); + } + + // --- Test 2: REA vor IFA --- + + @Test + void validate_shouldReportExactlyOneErrorWhenReaBeforeIfa() { + // REA(2) < IFA(3) < IVA(4) -> IFA nicht vor REA + InputFile inputFile = buildAsvrec(3, 2, 4); + + ValidationResult result = validator.validate(inputFile); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_016", error.errorCode()); + assertEquals("In ASVREC-Nachrichten muss IFA vor REA stehen", error.description()); + assertEquals("IFA/REA", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("Reihenfolge IFA vor REA vor IVA erforderlich", error.expectedRule()); + } + + // --- Test 3: IVA vor REA --- + + @Test + void validate_shouldReportExactlyOneErrorWhenIvaBeforeRea() { + // IFA(2) < IVA(3) < REA(4) -> REA nicht vor IVA + InputFile inputFile = buildAsvrec(2, 4, 3); + + ValidationResult result = validator.validate(inputFile); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + + ValidationError error = result.getErrors().get(0); + assertEquals("STRUCTURE_016", error.errorCode()); + assertEquals("In ASVREC-Nachrichten muss REA vor IVA stehen", error.description()); + assertEquals("REA/IVA", error.segmentName()); + assertEquals(1, error.segmentPosition()); + assertEquals("Reihenfolge IFA vor REA vor IVA erforderlich", error.expectedRule()); + } + + // --- Test 4: fehlendes REA -> kein zusätzlicher Reihenfolgefehler --- + + @Test + void validate_shouldNotReportOrderErrorWhenReaIsMissing() { + // REA fehlt -> Präsenzregel (STRUCTURE_014) greift, aber kein STRUCTURE_016 + 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)); + + ValidationResult result = validator.validate(inputFile); + + // Kein STRUCTURE_016-Fehler + assertFalse(result.getErrors().stream() + .anyMatch(e -> "STRUCTURE_016".equals(e.errorCode())), + "Kein Reihenfolgefehler erwartet, wenn REA fehlt"); + + // Genau ein Fehler: STRUCTURE_014 (REA fehlt) + assertEquals(1, result.getErrors().size()); + assertEquals("STRUCTURE_014", result.getErrors().get(0).errorCode()); + } + + // --- Test 5: ASVFEH -> kein Fehler durch diese Regel --- + + @Test + void validate_shouldNotReportOrderErrorForAsvfeh() { + // ASVFEH mit umgekehrter Reihenfolge der Segmente -> kein STRUCTURE_016 + Segment unh = new Segment("UNH", 1, + List.of(new Field(1, "12345"), new Field(2, "ASVFEH:D:03B:UN:EAN008"))); + Segment iva = new Segment("IVA", 2); + Segment rea = new Segment("REA", 3); + Segment ifa = new Segment("IFA", 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, iva, rea, ifa, unt)); + InputFile inputFile = new InputFile("test.txt", List.of(message)); + + ValidationResult result = validator.validate(inputFile); + + assertFalse(result.getErrors().stream() + .anyMatch(e -> "STRUCTURE_016".equals(e.errorCode())), + "Kein Reihenfolgefehler erwartet für ASVFEH"); + assertFalse(result.hasErrors(), + "Kein Fehler erwartet für ASVFEH"); + } +} diff --git a/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorTestAdditional.java b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorTestAdditional.java index 1f05867..71e6716 100644 --- a/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorTestAdditional.java +++ b/src/test/java/de/gecheckt/asv/validation/structure/DefaultStructureValidatorTestAdditional.java @@ -32,20 +32,24 @@ class DefaultStructureValidatorTestAdditional { @Test void validate_shouldNotReportErrorWhenMessageTypeIsASVREC() { - // Given + // Given: vollständige ASVREC-Nachricht mit allen Pflichtsegmenten (IFA, REA, IVA). + // REA-Feld 1 enthält keinen der Sonderwerte "0" oder "1", damit keine + // Rechnungskennzeichen-Regel (STRUCTURE_017–020) greift. 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)); + Segment ifa = new Segment("IFA", 2, List.of(new Field(1, "IFA-Wert"))); + Segment rea = new Segment("REA", 3, List.of(new Field(1, "9"))); + Segment iva = new Segment("IVA", 4, List.of(new Field(1, "IVA-Wert"))); + 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 - // Should not have STRUCTURE_012 error for valid ASVREC message type + // Then: kein STRUCTURE_012-Fehler für gültigen ASVREC-Nachrichtentyp 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"); + assertFalse(result.hasErrors(), "Keine Validierungsfehler für gültige ASVREC-Nachricht erwartet"); } @Test