Erste fachliche Strukturregel ergänzen: UNH-/UNT-Referenznummer prüfen
This commit is contained in:
@@ -13,15 +13,16 @@ import de.gecheckt.asv.validation.model.ValidationResult;
|
|||||||
import de.gecheckt.asv.validation.model.ValidationSeverity;
|
import de.gecheckt.asv.validation.model.ValidationSeverity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of StructureValidator that checks general structural rules.
|
* Standardimplementierung des StructureValidator, die allgemeine Strukturregeln prüft.
|
||||||
*
|
*
|
||||||
* Rules checked:
|
* Geprüfte Regeln:
|
||||||
* 1. InputFile must contain at least one Message
|
* 1. Die Eingabedatei muss mindestens eine Nachricht enthalten
|
||||||
* 2. Each Message must contain at least one Segment
|
* 2. Jede Nachricht muss mindestens ein Segment enthalten
|
||||||
* 3. Segment names must not be empty
|
* 3. Segmentnamen dürfen nicht leer sein
|
||||||
* 4. Field positions within a Segment must be unique and positive
|
* 4. Feldpositionen innerhalb eines Segments müssen eindeutig und positiv sein
|
||||||
* 5. Segment positions within a Message must be unique and positive
|
* 5. Segmentpositionen innerhalb einer Nachricht müssen eindeutig und positiv sein
|
||||||
* 6. Message positions within an InputFile must be unique and positive
|
* 6. Nachrichtenpositionen innerhalb einer Eingabedatei müssen eindeutig und positiv sein
|
||||||
|
* 7. UNH- und UNT-Referenznummern müssen innerhalb einer Nachricht übereinstimmen
|
||||||
*/
|
*/
|
||||||
public class DefaultStructureValidator implements StructureValidator {
|
public class DefaultStructureValidator implements StructureValidator {
|
||||||
|
|
||||||
@@ -44,10 +45,10 @@ public class DefaultStructureValidator implements StructureValidator {
|
|||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
"",
|
"",
|
||||||
"At least one message required"
|
"Mindestens eine Nachricht erforderlich"
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// Process messages if they exist
|
// Verarbeite Nachrichten, wenn sie vorhanden sind
|
||||||
validateMessages(inputFile.messages(), errors);
|
validateMessages(inputFile.messages(), errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,10 +56,10 @@ public class DefaultStructureValidator implements StructureValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates all messages in the input file.
|
* Validiert alle Nachrichten in der Eingabedatei.
|
||||||
*
|
*
|
||||||
* @param messages the list of messages to validate
|
* @param messages die Liste der zu validierenden Nachrichten
|
||||||
* @param errors the list to add validation errors to
|
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
|
||||||
*/
|
*/
|
||||||
private void validateMessages(List<Message> messages, List<ValidationError> errors) {
|
private void validateMessages(List<Message> messages, List<ValidationError> errors) {
|
||||||
var messagePositions = new HashSet<Integer>();
|
var messagePositions = new HashSet<Integer>();
|
||||||
@@ -66,35 +67,83 @@ public class DefaultStructureValidator implements StructureValidator {
|
|||||||
for (var message : messages) {
|
for (var message : messages) {
|
||||||
var messagePosition = message.messagePosition();
|
var messagePosition = message.messagePosition();
|
||||||
|
|
||||||
// Rule 6: Message positions must be unique and positive
|
// Regel 6: Nachrichtenpositionen müssen eindeutig und positiv sein
|
||||||
if (!messagePositions.add(messagePosition)) {
|
if (!messagePositions.add(messagePosition)) {
|
||||||
errors.add(createError(
|
errors.add(createError(
|
||||||
"STRUCTURE_006",
|
"STRUCTURE_006",
|
||||||
"Duplicate message position: " + messagePosition,
|
"Doppelte Nachrichtenposition: " + messagePosition,
|
||||||
ValidationSeverity.ERROR,
|
ValidationSeverity.ERROR,
|
||||||
"",
|
"",
|
||||||
messagePosition,
|
messagePosition,
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
String.valueOf(messagePosition),
|
String.valueOf(messagePosition),
|
||||||
"Unique positive message positions required"
|
"Einzigartige positive Nachrichtenpositionen erforderlich"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate segments in this message
|
// Validiere Segmente in dieser Nachricht
|
||||||
validateSegments(message.segments(), messagePosition, errors);
|
validateSegments(message.segments(), messagePosition, errors);
|
||||||
|
|
||||||
|
// Regel 7: UNH- und UNT-Referenznummern müssen übereinstimmen
|
||||||
|
validateUnhUntReferenceNumbers(message, errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates all segments in a message.
|
* Validiert, dass die Referenznummern in UNH und UNT übereinstimmen.
|
||||||
*
|
*
|
||||||
* @param segments the list of segments to validate
|
* @param message die zu validierende Nachricht
|
||||||
* @param messagePosition the position of the parent message
|
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
|
||||||
* @param errors the list to add validation errors to
|
*/
|
||||||
|
private void validateUnhUntReferenceNumbers(Message message, List<ValidationError> errors) {
|
||||||
|
var unhSegment = message.getFirstSegment("UNH");
|
||||||
|
var untSegment = message.getFirstSegment("UNT");
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// 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 and UNT reference numbers do not match",
|
||||||
|
ValidationSeverity.ERROR,
|
||||||
|
"UNH/UNT",
|
||||||
|
message.messagePosition(),
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
unhReference + " != " + normalizedUntReference,
|
||||||
|
"Reference numbers in UNH and UNT must match"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validiert alle Segmente einer Nachricht.
|
||||||
|
*
|
||||||
|
* @param segments die Liste der zu validierenden Segmente
|
||||||
|
* @param messagePosition die Position der übergeordneten Nachricht
|
||||||
|
* @param errors die Liste zum Hinzufügen von Validierungsfehlern
|
||||||
*/
|
*/
|
||||||
private void validateSegments(List<Segment> segments, int messagePosition, List<ValidationError> errors) {
|
private void validateSegments(List<Segment> segments, int messagePosition, List<ValidationError> errors) {
|
||||||
// Rule 2: Each Message must contain at least one Segment
|
// Regel 2: Jede Nachricht muss mindestens ein Segment enthalten
|
||||||
if (segments.isEmpty()) {
|
if (segments.isEmpty()) {
|
||||||
errors.add(createError(
|
errors.add(createError(
|
||||||
"STRUCTURE_002",
|
"STRUCTURE_002",
|
||||||
@@ -105,9 +154,9 @@ public class DefaultStructureValidator implements StructureValidator {
|
|||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
"",
|
"",
|
||||||
"At least one segment required per message"
|
"Mindestens ein Segment pro Nachricht erforderlich"
|
||||||
));
|
));
|
||||||
return; // No need to validate segments if there are none
|
return; // Keine Validierung der Segmente notwendig, wenn keine vorhanden sind
|
||||||
}
|
}
|
||||||
|
|
||||||
var segmentPositions = new HashSet<Integer>();
|
var segmentPositions = new HashSet<Integer>();
|
||||||
@@ -116,22 +165,22 @@ public class DefaultStructureValidator implements StructureValidator {
|
|||||||
var segmentName = segment.segmentName();
|
var segmentName = segment.segmentName();
|
||||||
var segmentPosition = segment.segmentPosition();
|
var segmentPosition = segment.segmentPosition();
|
||||||
|
|
||||||
// Rule 3: Segment names must not be empty
|
// Regel 3: Segmentnamen dürfen nicht leer sein
|
||||||
if (segmentName == null || segmentName.isEmpty()) {
|
if (segmentName == null || segmentName.isEmpty()) {
|
||||||
errors.add(createError(
|
errors.add(createError(
|
||||||
"STRUCTURE_003",
|
"STRUCTURE_003",
|
||||||
"Segment name must not be empty",
|
"Segmentname darf nicht leer sein",
|
||||||
ValidationSeverity.ERROR,
|
ValidationSeverity.ERROR,
|
||||||
segmentName != null ? segmentName : "",
|
segmentName != null ? segmentName : "",
|
||||||
segmentPosition,
|
segmentPosition,
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
segmentName != null ? segmentName : "null",
|
segmentName != null ? segmentName : "null",
|
||||||
"Non-empty segment name required"
|
"Nicht-leerer Segmentname erforderlich"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 5: Segment positions must be unique and positive
|
// Regel 5: Segmentpositionen müssen eindeutig und positiv sein
|
||||||
if (!segmentPositions.add(segmentPosition)) {
|
if (!segmentPositions.add(segmentPosition)) {
|
||||||
errors.add(createError(
|
errors.add(createError(
|
||||||
"STRUCTURE_005",
|
"STRUCTURE_005",
|
||||||
@@ -142,7 +191,7 @@ public class DefaultStructureValidator implements StructureValidator {
|
|||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
String.valueOf(segmentPosition),
|
String.valueOf(segmentPosition),
|
||||||
"Unique positive segment positions required"
|
"Einzigartige positive Segmentpositionen erforderlich"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,12 +201,12 @@ public class DefaultStructureValidator implements StructureValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates all fields in a segment.
|
* Validiert alle Felder eines Segments.
|
||||||
*
|
*
|
||||||
* @param fields the list of fields to validate
|
* @param fields die Liste der zu validierenden Felder
|
||||||
* @param segmentName the name of the parent segment
|
* @param segmentName der Name des übergeordneten Segments
|
||||||
* @param segmentPosition the position of the parent segment
|
* @param segmentPosition die Position des übergeordneten Segments
|
||||||
* @param errors the list to add validation errors to
|
* @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) {
|
||||||
var fieldPositions = new HashSet<Integer>();
|
var fieldPositions = new HashSet<Integer>();
|
||||||
@@ -165,7 +214,7 @@ public class DefaultStructureValidator implements StructureValidator {
|
|||||||
for (var field : fields) {
|
for (var field : fields) {
|
||||||
var fieldPosition = field.fieldPosition();
|
var fieldPosition = field.fieldPosition();
|
||||||
|
|
||||||
// Rule 4: Field positions must be unique and positive
|
// Regel 4: Feldpositionen müssen eindeutig und positiv sein
|
||||||
if (!fieldPositions.add(fieldPosition)) {
|
if (!fieldPositions.add(fieldPosition)) {
|
||||||
errors.add(createError(
|
errors.add(createError(
|
||||||
"STRUCTURE_004",
|
"STRUCTURE_004",
|
||||||
@@ -176,25 +225,25 @@ public class DefaultStructureValidator implements StructureValidator {
|
|||||||
field.getFieldName().orElse(""),
|
field.getFieldName().orElse(""),
|
||||||
fieldPosition,
|
fieldPosition,
|
||||||
String.valueOf(fieldPosition),
|
String.valueOf(fieldPosition),
|
||||||
"Unique positive field positions required"
|
"Einzigartige positive Feldpositionen erforderlich"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to create a ValidationError with consistent parameters.
|
* Hilfsmethode zur Erstellung eines ValidationError mit konsistenten Parametern.
|
||||||
*
|
*
|
||||||
* @param errorCode the error code
|
* @param errorCode der Fehlercode
|
||||||
* @param description the error description
|
* @param description die Fehlerbeschreibung
|
||||||
* @param severity the validation severity
|
* @param severity die Validierungsschweregrad
|
||||||
* @param segmentName the segment name
|
* @param segmentName der Segmentname
|
||||||
* @param segmentPosition the segment position
|
* @param segmentPosition die Segmentposition
|
||||||
* @param fieldName the field name
|
* @param fieldName der Feldname
|
||||||
* @param fieldPosition the field position
|
* @param fieldPosition die Feldposition
|
||||||
* @param actualValue the actual value
|
* @param actualValue der tatsächliche Wert
|
||||||
* @param expectedRule the expected rule
|
* @param expectedRule die erwartete Regel
|
||||||
* @return a new ValidationError instance
|
* @return eine neue ValidationError-Instanz
|
||||||
*/
|
*/
|
||||||
private ValidationError createError(String errorCode,
|
private ValidationError createError(String errorCode,
|
||||||
String description,
|
String description,
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package de.gecheckt.asv.validation.structure;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -12,6 +15,10 @@ import de.gecheckt.asv.domain.model.Field;
|
|||||||
import de.gecheckt.asv.domain.model.InputFile;
|
import de.gecheckt.asv.domain.model.InputFile;
|
||||||
import de.gecheckt.asv.domain.model.Message;
|
import de.gecheckt.asv.domain.model.Message;
|
||||||
import de.gecheckt.asv.domain.model.Segment;
|
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.ValidationError;
|
||||||
import de.gecheckt.asv.validation.model.ValidationResult;
|
import de.gecheckt.asv.validation.model.ValidationResult;
|
||||||
|
|
||||||
@@ -119,4 +126,89 @@ class DefaultStructureValidatorTest {
|
|||||||
assertFalse(result.hasInfos());
|
assertFalse(result.hasInfos());
|
||||||
assertTrue(result.getAllErrors().isEmpty());
|
assertTrue(result.getAllErrors().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validate_shouldReportErrorWhenUnhAndUntReferenceNumbersDoNotMatch() throws IOException, InputFileParseException {
|
||||||
|
// Given
|
||||||
|
SegmentLineTokenizer tokenizer = new DefaultSegmentLineTokenizer();
|
||||||
|
DefaultInputFileParser parser = new DefaultInputFileParser(tokenizer);
|
||||||
|
String fileName = "unh-unt-mismatch.asv";
|
||||||
|
Path filePath = Path.of("src/test/resources/unh-unt-mismatch.asv");
|
||||||
|
String fileContent = Files.readString(filePath);
|
||||||
|
|
||||||
|
// When
|
||||||
|
InputFile inputFile = parser.parse(fileName, fileContent);
|
||||||
|
ValidationResult result = validator.validate(inputFile);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.hasErrors());
|
||||||
|
assertEquals(1, result.getErrors().size());
|
||||||
|
|
||||||
|
ValidationError error = result.getErrors().get(0);
|
||||||
|
assertEquals("STRUCTURE_007", error.errorCode());
|
||||||
|
assertEquals("UNH and UNT reference numbers do not match", error.description());
|
||||||
|
assertEquals("UNH/UNT", error.segmentName());
|
||||||
|
assertEquals(1, error.segmentPosition());
|
||||||
|
assertEquals("12345 != 54321", error.actualValue());
|
||||||
|
assertEquals("Reference numbers in UNH and UNT must match", error.expectedRule());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validate_shouldNotReportErrorWhenUnhAndUntReferenceNumbersMatch() throws IOException, InputFileParseException {
|
||||||
|
// Given
|
||||||
|
SegmentLineTokenizer tokenizer = new DefaultSegmentLineTokenizer();
|
||||||
|
DefaultInputFileParser parser = new DefaultInputFileParser(tokenizer);
|
||||||
|
String fileName = "unh-unt-match.asv";
|
||||||
|
Path filePath = Path.of("src/test/resources/unh-unt-match.asv");
|
||||||
|
String fileContent = Files.readString(filePath);
|
||||||
|
|
||||||
|
// When
|
||||||
|
InputFile inputFile = parser.parse(fileName, fileContent);
|
||||||
|
ValidationResult result = validator.validate(inputFile);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
// Check that we don't have the STRUCTURE_007 error
|
||||||
|
assertFalse(result.getErrors().stream()
|
||||||
|
.anyMatch(error -> "STRUCTURE_007".equals(error.errorCode())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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_007 error for mismatch
|
||||||
|
// Any existing structural errors are acceptable but not our new error
|
||||||
|
assertFalse(result.getErrors().stream()
|
||||||
|
.anyMatch(error -> "STRUCTURE_007".equals(error.errorCode())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validate_shouldNotReportAdditionalErrorWhenUntIsMissing() 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_007 error for mismatch
|
||||||
|
// Any existing structural errors are acceptable but not our new error
|
||||||
|
assertFalse(result.getErrors().stream()
|
||||||
|
.anyMatch(error -> "STRUCTURE_007".equals(error.errorCode())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
8
src/test/resources/no-unh.asv
Normal file
8
src/test/resources/no-unh.asv
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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'
|
||||||
|
UNT+9+12345'
|
||||||
8
src/test/resources/no-unt.asv
Normal file
8
src/test/resources/no-unt.asv
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
UNH+12345+ORDERS:D:03B:UN:EAN008'
|
||||||
|
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'
|
||||||
9
src/test/resources/unh-unt-match.asv
Normal file
9
src/test/resources/unh-unt-match.asv
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
UNH+12345+ORDERS:D:03B:UN:EAN008'
|
||||||
|
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'
|
||||||
|
UNT+9+12345'
|
||||||
9
src/test/resources/unh-unt-mismatch.asv
Normal file
9
src/test/resources/unh-unt-mismatch.asv
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
UNH+12345+ORDERS:D:03B:UN:EAN008'
|
||||||
|
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'
|
||||||
|
UNT+6+54321'
|
||||||
Reference in New Issue
Block a user