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

57
AGENTS.md Normal file
View File

@@ -0,0 +1,57 @@
# ASV-Format-Validator
## Projektüberblick
- Java-/Maven-Projekt zur Validierung segmentorientierter Dateien gegen eine fachliche ASV-Spezifikation.
- Bestehenden Projektstand respektieren und darauf aufbauen.
- Keine Neuarchitektur.
## Technik
- Java 21
- Maven
- Log4j2
- kein Spring Boot
## Architektur- und Arbeitsregeln
- Nur kleine, kontrollierte Änderungen.
- Pro Arbeitsschritt genau eine kleine fachliche Regel oder eine eng abgegrenzte Stabilisierung.
- Kein Parser-Umbau ohne ausdrückliche Anweisung.
- Keine EDIFACT-Neuinterpretation auf Verdacht.
- Keine neue Validator-Hierarchie auf Verdacht.
- Keine Refactorings auf Verdacht.
- Bereits vorhandene Regeln und Tests dürfen nicht semantisch verbogen werden.
- Bestehende Funktionalität darf nicht verschlechtert werden.
## Parser-Leitplanken
- Aktuelle Parser-Annahmen respektieren.
- Keine zusätzliche Zerlegung von Feldwerten, außer ein bestehender Test und der aktuelle Projektstand erzwingen eine eng begrenzte lokale Korrektur.
- Keine allgemeine Normalisierung einführen, wenn nur ein lokaler Sonderfall benötigt wird.
## Code-Stil
- Technische Bezeichner auf Englisch.
- Kommentare, JavaDoc und benutzernahe Fehlermeldungen auf Deutsch.
- Vollständigen, kompilierbaren Code liefern.
- Änderungen minimal halten.
## Test- und Qualitätsregeln
- Nach jeder Änderung vollständig testen.
- Standardbefehl: `mvn clean test`
- Wenn Tests fehlschlagen:
- zuerst alle fehlgeschlagenen Tests gemeinsam analysieren
- gemeinsame Ursachen clustern
- dann alle zugehörigen Fehler in einem sauberen Durchgang beheben
- keine Fehler-für-Fehler-Mini-Iterationen
- Wenn neue Tests ergänzt werden:
- Testdaten fachlich isoliert halten
- keine unnötigen Zusatzfehler provozieren
## Änderungsschwerpunkte
- Bevorzugt bestehende Klassen gezielt ergänzen.
- Neue Klassen nur, wenn technisch wirklich nötig und klein.
- Für Validator-Schritte bevorzugt im bestehenden `DefaultStructureValidator` ergänzen, solange kein ausdrücklicher Architekturwechsel gefordert ist.
## Ausgabeerwartung
- Zuerst den ausgeführten Maven-Befehl nennen.
- Danach kurzes Testergebnis.
- Danach kurze Liste der geänderten Dateien.
- Danach vollständigen Code aller tatsächlich geänderten Dateien.
- Keine langen Erklärungen.

View File

@@ -0,0 +1,52 @@
---
name: asv-small-step
description: Verwende diesen Skill, wenn im ASV-Format-Validator ein neuer kleiner fachlicher Spezifikationsschritt umgesetzt werden soll. Der Skill erzwingt minimale Änderungen, Schutz des bestehenden Projektstands, keinen Parser-Umbau und vollständige Tests.
license: Proprietary
metadata:
project: asv-format-validator
language: de
---
# ASV Small Step
## Zweck
Dieser Skill ist für kleine, klar abgegrenzte Implementierungsschritte im Projekt ASV-Format-Validator gedacht.
## Verbindliche Arbeitsweise
- Implementiere genau **eine** neue fachliche Regel oder einen eng abgegrenzten Stabilisierungsschritt.
- Baue strikt auf dem bestehenden Projektstand auf.
- Kein Parser-Umbau.
- Keine EDIFACT-Neuinterpretation auf Verdacht.
- Keine neue Validator-Hierarchie auf Verdacht.
- Keine Refactorings auf Verdacht.
- Bestehende Regeln und Tests dürfen nicht semantisch verbogen werden.
## Architekturregeln
- Bevorzuge minimale Ergänzungen in bestehenden Klassen.
- Für Strukturregeln bevorzuge Ergänzungen im bestehenden `DefaultStructureValidator`, solange keine ausdrückliche Gegenanweisung vorliegt.
- Neue Klassen nur dann, wenn technisch zwingend nötig und sehr klein.
## Sprach- und Stilregeln
- Technische Bezeichner auf Englisch.
- Kommentare, JavaDoc und benutzernahe Fehlermeldungen auf Deutsch.
- Vollständigen, kompilierbaren Code liefern.
## Testregeln
- Für den neuen Schritt gezielte Tests ergänzen.
- Testdaten so gestalten, dass sie die neue Regel isoliert prüfen.
- Keine unnötigen Zusatzfehler in Testressourcen erzeugen.
- Nach der Umsetzung vollständig testen mit:
- `mvn clean test`
## Wenn Tests fehlschlagen
- Nicht nur den ersten Fehler beheben.
- Alle fehlschlagenden Tests gemeinsam analysieren.
- Nach gemeinsamer Ursache gruppieren.
- Alle zugehörigen Korrekturen in einem Durchgang umsetzen.
## Ausgabeformat
1. Ausgeführter Maven-Befehl
2. Kurzes Testergebnis
3. Sehr kurze Liste der geänderten Dateien
4. Vollständiger Code aller tatsächlich geänderten Dateien
5. Keine langen Erklärungen

View File

@@ -0,0 +1,51 @@
---
name: asv-test-failure-clustering
description: Verwende diesen Skill, wenn im ASV-Format-Validator Tests fehlschlagen. Der Skill erzwingt Root-Cause-Analyse über alle fehlschlagenden Tests, gruppierte Behebung in einem Durchgang und schützt vor Fehler-für-Fehler-Mini-Iterationen.
license: Proprietary
metadata:
project: asv-format-validator
language: de
---
# ASV Test Failure Clustering
## Zweck
Dieser Skill dient der strukturierten Behebung fehlgeschlagener Tests im Projekt ASV-Format-Validator.
## Verbindliche Vorgehensweise
1. Lies alle fehlgeschlagenen Tests und Fehlermeldungen vollständig.
2. Behebe nicht nur den ersten Fehler.
3. Bestimme gemeinsame Ursachen.
4. Gruppiere die Fehler nach Ursache.
5. Behebe jede Ursache in einem einzigen sauberen Durchgang.
6. Teste danach erneut vollständig.
## Prioritäten bei der Analyse
- Zuerst prüfen, ob Testdaten oder Assertions inkonsistent sind.
- Danach prüfen, ob neue Regeln unbeabsichtigte Folgefehler erzeugen.
- Produktivcode nur ändern, wenn ein echter Logikfehler belegt ist.
- Keine Umbauten auf Verdacht.
## Schutzregeln
- Kein Parser-Umbau.
- Keine neue fachliche Regel.
- Keine Refactorings auf Verdacht.
- Bestehende grüne Funktionalität darf nicht verschlechtert werden.
- Testressourcen müssen fachlich isoliert sein.
## Typische Fragen vor der Änderung
- Lösen mehrere Tests dieselbe gemeinsame Ursache aus?
- Ist die Testressource sauber isoliert?
- Passt die erwartete Assertion zur tatsächlichen Segmentanzahl bzw. zum tatsächlichen Parserverhalten?
- Entsteht ein Folgefehler nur wegen einer inkonsistenten Testdatei?
## Abschluss
- Nach den Korrekturen vollständig testen mit:
- `mvn clean test`
## Ausgabeformat
1. Ausgeführter Maven-Befehl
2. Kurzes Testergebnis
3. Sehr kurze Liste der geänderten Dateien
4. Vollständiger Code aller tatsächlich geänderten Dateien
5. Keine langen Erklärungen

View File

@@ -24,6 +24,8 @@ import de.gecheckt.asv.validation.model.ValidationSeverity;
* 6. Nachrichtenpositionen innerhalb einer Eingabedatei müssen eindeutig und positiv sein * 6. Nachrichtenpositionen innerhalb einer Eingabedatei müssen eindeutig und positiv sein
* 7. UNH- und UNT-Referenznummern müssen innerhalb einer Nachricht übereinstimmen * 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 * 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 { public class DefaultStructureValidator implements StructureValidator {
@@ -86,11 +88,79 @@ public class DefaultStructureValidator implements StructureValidator {
// Validiere Segmente in dieser Nachricht // Validiere Segmente in dieser Nachricht
validateSegments(message.segments(), messagePosition, errors); 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 // 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 // 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 // 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 * @param errors die Liste zum Hinzufügen von Validierungsfehlern
*/ */
private void validateFields(List<Field> fields, String segmentName, int segmentPosition, List<ValidationError> errors) { private void validateFields(List<Field> fields, String segmentName, int segmentPosition, List<ValidationError> errors) {
if (fields.isEmpty()) {
return;
}
var fieldPositions = new HashSet<Integer>(); var fieldPositions = new HashSet<Integer>();
for (var field : fields) { for (var field : fields) {

View File

@@ -73,41 +73,53 @@ class DefaultStructureValidatorTest {
@Test @Test
void validate_shouldReportErrorWhenSegmentHasDuplicatePositions() { void validate_shouldReportErrorWhenSegmentHasDuplicatePositions() {
Segment segment1 = new Segment("SEG1", 1); // Manually create segments with duplicate positions to bypass parser validation
Segment segment2 = new Segment("SEG2", 1); // Duplicate position // Also include required UNH and UNT segments
Message message = new Message(1, List.of(segment1, segment2)); 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)); InputFile inputFile = new InputFile("test.txt", List.of(message));
// When
ValidationResult result = validator.validate(inputFile); ValidationResult result = validator.validate(inputFile);
// Then
assertTrue(result.hasErrors()); assertTrue(result.hasErrors());
assertEquals(1, result.getErrors().size()); assertEquals(1, result.getErrors().size());
ValidationError error = result.getErrors().get(0); ValidationError error = result.getErrors().get(0);
assertEquals("STRUCTURE_005", error.errorCode()); assertEquals("STRUCTURE_005", error.errorCode());
assertEquals("Duplicate segment position: 1", error.description()); assertEquals("Duplicate segment position: 2", error.description());
assertEquals("SEG2", error.segmentName()); assertEquals("SEG2", error.segmentName());
assertEquals(1, error.segmentPosition()); assertEquals(2, error.segmentPosition());
} }
@Test @Test
void validate_shouldReportErrorWhenFieldHasDuplicatePositions() { 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 field1 = new Field(1, "value1");
Field field2 = new Field(1, "value2"); // Duplicate position Field field2 = new Field(1, "value2"); // Duplicate position
Segment segment = new Segment("SEG1", 1, List.of(field1, field2)); Segment segment = new Segment("SEG1", 2, List.of(field1, field2));
Message message = new Message(1, List.of(segment)); 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)); InputFile inputFile = new InputFile("test.txt", List.of(message));
// When
ValidationResult result = validator.validate(inputFile); ValidationResult result = validator.validate(inputFile);
// Then
assertTrue(result.hasErrors()); assertTrue(result.hasErrors());
assertEquals(1, result.getErrors().size()); assertEquals(1, result.getErrors().size());
ValidationError error = result.getErrors().get(0); ValidationError error = result.getErrors().get(0);
assertEquals("STRUCTURE_004", error.errorCode()); assertEquals("STRUCTURE_004", error.errorCode());
assertEquals("Duplicate field position: 1", error.description()); assertEquals("Duplicate field position: 1", error.description());
assertEquals("SEG1", error.segmentName()); assertEquals("SEG1", error.segmentName());
assertEquals(1, error.segmentPosition()); assertEquals(2, error.segmentPosition());
assertEquals(1, error.fieldPosition()); assertEquals(1, error.fieldPosition());
} }
@@ -115,8 +127,10 @@ class DefaultStructureValidatorTest {
void validate_shouldReturnNoErrorsForValidStructure() { void validate_shouldReturnNoErrorsForValidStructure() {
Field field1 = new Field(1, "value1"); Field field1 = new Field(1, "value1");
Field field2 = new Field(2, "value2"); Field field2 = new Field(2, "value2");
Segment segment = new Segment("SEG1", 1, List.of(field1, field2)); Segment segment = new Segment("UNH", 1, List.of(new Field(1, "12345")));
Message message = new Message(1, List.of(segment)); 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)); InputFile inputFile = new InputFile("test.txt", List.of(message));
ValidationResult result = validator.validate(inputFile); ValidationResult result = validator.validate(inputFile);
@@ -305,4 +319,123 @@ class DefaultStructureValidatorTest {
assertEquals("10 != 9", error.actualValue()); assertEquals("10 != 9", error.actualValue());
assertEquals("Segmentanzahl in UNT muss mit tatsächlicher Anzahl übereinstimmen", error.expectedRule()); 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' BGM+220+100001'
DTM+137:20260325:102' DTM+137:20260325:102'
NAD+BY+5000000000000:16++Customer Name' NAD+BY+5000000000000:16++Customer Name'
@@ -5,4 +6,4 @@ LIN+1++Product123:SA'
QTY+21:10:PCE' QTY+21:10:PCE'
UNS+S' UNS+S'
CNT+2:1' 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' LIN+1++Product123:SA'
QTY+21:10:PCE' QTY+21:10:PCE'
UNS+S' UNS+S'
CNT+2:1' CNT+2:1'