feat: phase g - EDIFACT #13
No files matched your search
@@ -6,6 +6,10 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic
|
||||
|
||||
### Added
|
||||
|
||||
- Phase g EDIFACT syntax 3/4 envelopes (UNA/UNB, optional UNG/UNE, UNH/UNT, UNZ), release escaping, native envelope findings, UNB identity routing, 14-digit UN control allocation and duplicate detection.
|
||||
- Original D96A/D01B ORDERS, ORDRSP, DESADV, INVOIC and CONTRL guides; ORDERS inbound and retail response outbound sample maps with composite fields and segment groups.
|
||||
- Standard-neutral document validation/mapping, mixed X12/EDIFACT inbox framing, CONTRL generation and reconciliation with overdue resolution and rejection alerts, native outbound envelopes and archived resend-as-new.
|
||||
- Lindqvist Retail AB demo partner with ORDERS application, ORDRSP/DESADV/INVOIC delivery and CONTRL in both directions; EDIFACT map preview and HTTPS media type.
|
||||
- Phase e original HIPAA baselines for 837P/I, 835, 270/271, 276/277 and 834; exact SNIP 3 balancing, occurrence-scoped SNIP 4 rules, SNIP 5 formats and a host `ExternalCodeSetProvider` SPI.
|
||||
- Test-only healthcare maps, classified sample tables, seeded synthetic fixtures, Cedar Health Clearinghouse demo traffic and a PHI canary scanner covering both ingress paths, 999s, apply/replay/resend, widgets, query exports, alerts and logs.
|
||||
- Permission-checked, session-bound Download Raw Document actions for inbox/interchange rows, with a value-free user audit event.
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
# QBit EDI
|
||||
|
||||
EDI for QQQ: trading partners, agreements, X12 validation, encrypted payloads, inbound mapping
|
||||
and application, and TA1/997/999 acknowledgements. See [the design](docs/DESIGN.md).
|
||||
EDI for QQQ: trading partners, agreements, X12 and EDIFACT validation, encrypted payloads, inbound mapping
|
||||
and application, and TA1/997/999/CONTRL acknowledgements. See [the design](docs/DESIGN.md).
|
||||
|
||||
## Status
|
||||
|
||||
Phases a1, a2, b and c implement inbound/outbound retail EDI over gateway, SFTP, HTTPS and AS2. Original baseline guides cover
|
||||
Phases a1 to e and g implement inbound/outbound retail, logistics and healthcare EDI (X12 and EDIFACT) over gateway, SFTP, HTTPS and AS2. Original baseline guides cover
|
||||
850, 855, 856, 810, 846 and 860 in 004010/005010, plus 997/999. Source records and associations
|
||||
produce validated, encrypted outbound interchanges, with control-number tracking, retries,
|
||||
acknowledgement monitoring and operator resend/cancel actions. AS2 (signed, encrypted, compressed, synchronous and
|
||||
asynchronous MDN, certificate rollover) is built in; email sending remains disabled until the qbit-email API ships.
|
||||
A runnable synthetic [demo](docs/DEMO.md) includes Next UI and needs no external services.
|
||||
|
||||
EDIFACT syntax versions 3 and 4 support UNA/UNB, optional UNG groups, UNH messages and their
|
||||
trailers, release-character escaping, mixed-standard payloads, and CONTRL syntax responses.
|
||||
Original D96A/D01B baselines cover ORDERS, ORDRSP, DESADV, INVOIC and CONTRL. ORDERS maps to
|
||||
sample orders and lines; the other three retail documents map outbound. APERAK remains future work.
|
||||
|
||||
Configure `standard=EDIFACT` on identities and agreements. `interchangeId` and
|
||||
`interchangeQualifier` carry the UNB party identifiers and qualifiers. Set `interchangeVersion`
|
||||
to `3` or `4`, `syntaxIdentifier` to `UNOC`, and outbound separators explicitly: element `+`,
|
||||
component `:`, segment `'`, release `?`, decimal `.` (or `,`); syntax 4 also uses the configured
|
||||
repetition separator. Document versions are `D96A` or `D01B`, with `ackExpectation=CONTRL`.
|
||||
UNB11=`1` uses a test agreement and respects `applyTestDocuments`.
|
||||
|
||||
Existing schemas need these additional nullable agreement columns before using this version:
|
||||
|
||||
```sql
|
||||
ALTER TABLE edi_agreement ADD COLUMN release_character VARCHAR(255);
|
||||
ALTER TABLE edi_agreement ADD COLUMN decimal_mark VARCHAR(255);
|
||||
ALTER TABLE edi_agreement ADD COLUMN syntax_identifier VARCHAR(255);
|
||||
```
|
||||
|
||||
## Requirements and installation
|
||||
|
||||
Java 21, QQQ **4.1.0-RC.5**, an RDBMS backend, and `com.kingsrook.qbits:qbit-edi:0.1.0-SNAPSHOT`.
|
||||
@@ -66,10 +86,10 @@ Storage failure requests retry in one minute.
|
||||
|
||||
`InboxWorker.runOnce()` processes up to 20 messages with compare-and-set claims, fenced database
|
||||
checkpoints and five-minute stale-claim recovery. Retry delays are 1m, 5m, 15m, 1h and 6h; the next
|
||||
failure becomes DEAD_LETTER. Each mapped accepted ST applies in its own transaction through QQQ
|
||||
actions with explicit permission enforcement. ISA15=T applies only with `applyTestDocuments`.
|
||||
failure becomes DEAD_LETTER. Each mapped accepted ST or UNH applies in its own transaction through QQQ
|
||||
actions with explicit permission enforcement. ISA15=T or UNB11=1 applies only with `applyTestDocuments`.
|
||||
|
||||
Routing checks ISA identities, qualifiers, standard and usage. Linked gateway usernames must match
|
||||
Routing checks ISA or UNB identities, qualifiers, standard and usage. Linked gateway usernames must match
|
||||
the routed partner. Findings and failures become exceptions with occurrence counts and value-free
|
||||
timeline events. Replies allocate controls and remain QUEUED, recording `ACK_QUEUED`. Incoming
|
||||
acknowledgements reconcile outbound groups and resolve overdue alerts.
|
||||
|
||||
+9
-2
@@ -42,10 +42,17 @@ and sends three valid 940 shipping orders and one with a bad unit; the orders an
|
||||
four 214 status events (one rejected with a bad status code) that build `sample_shipment_status`.
|
||||
The in-memory gateway accepts every send, and the rejected documents produce rejecting 997s.
|
||||
|
||||
Phase g adds a fifth partner, **Lindqvist Retail AB** (LINDQVIST), after the existing scenarios.
|
||||
Its D96A test agreement receives three synthetic ORDERS documents through the durable inbox;
|
||||
orders and lines appear in `sample_order`, and CONTRLs enter the gateway queue. ORDRSP, DESADV
|
||||
and INVOIC are mapped from the retail sample records, delivered through the in-memory gateway,
|
||||
and reconciled by incoming accepted CONTRLs. Look for ORDERS with status APPLIED and the three
|
||||
outbound message types with status ACKNOWLEDGED. Released delimiters in buyer/item identifiers
|
||||
exercise the same parser used for partner input. All contacts remain synthetic `example.invalid`.
|
||||
|
||||
The SFTP/HTTPS partner settings are illustrative `.invalid` destinations. Their agreements have
|
||||
no outbound transport, so they cannot contact a real partner; resulting acknowledgement send
|
||||
retries are visible. Northwind has the retail outbound documents; the logistics and healthcare
|
||||
scenarios add their own outbound configurations. The in-memory gateway
|
||||
retries are visible. Northwind, Harbor, Ridgeline and Lindqvist have configured outbound documents; the healthcare scenario adds its own. The in-memory gateway
|
||||
is restored after the intentional dead-letter scenario. Standard Send As EDI, Test Connection,
|
||||
Resend, Resend As New and Cancel actions are available. The gateway stores bytes in memory;
|
||||
it does not expose a separate partner download UI.
|
||||
|
||||
+27
-6
@@ -420,10 +420,10 @@ in memory; no keystore, key or temporary file is written, and as2-lib's file-bac
|
||||
- **Email in:** through qbit-email's phase 2 inbound (an `InboundEmailHandler` registered by EDI).
|
||||
Designed here, built when qbit-email inbound ships.
|
||||
|
||||
## 6. Parsing (X12 now, EDIFACT later)
|
||||
## 6. Parsing (X12 and EDIFACT)
|
||||
|
||||
- StAEDI (`EDIInputFactory`, `EDIStreamReader`) reads X12 as a stream, with no whole-document tree,
|
||||
so a 50 MB 837 batch is processed in bounded memory per transaction set.
|
||||
- StAEDI (`EDIInputFactory`, `EDIStreamReader`) tokenizes X12 and EDIFACT. Inbox framing buffers
|
||||
one interchange at a time, capped at 50 MiB, and each transaction retains a bounded segment list.
|
||||
- One payload may hold several interchanges; each ISA/IEA becomes an `edi_interchange` row. Each
|
||||
ST/SE is buffered as its own segment list (bounded by the guide's maximum segment count, default
|
||||
100,000) for validation, ack building and mapping.
|
||||
@@ -433,9 +433,15 @@ in memory; no keystore, key or temporary file is written, and as2-lib's file-bac
|
||||
(005010 extended). Unrecognised bytes are an envelope error, never silently replaced.
|
||||
- Writing uses an in-memory serializer with the agreement's separators, fixed-width ISA
|
||||
padding, component/repetition handling, and optional line endings.
|
||||
- EDIFACT: StAEDI reads UNA/UNB/UNG/UNH and validates against EDIFACT schemas. It is designed into
|
||||
the model now (`standard` columns, UNB fields on `edi_identity`, CONTRL as an ack type) and built
|
||||
in phase g.
|
||||
- EDIFACT is built: StAEDI reads syntax 3/4 UNA/UNB/UNG/UNH envelopes. UNA advice and release
|
||||
characters survive round trips; UNT/UNE/UNZ counts and references produce findings. A payload
|
||||
may contain both X12 and EDIFACT interchanges. In-house D96A/D01B ORDERS/ORDRSP/DESADV/INVOIC
|
||||
and CONTRL guides use the shared composite and loop grammar.
|
||||
- EDIFACT routing uses the identity's `interchangeId`/`interchangeQualifier` as UNB identifiers;
|
||||
UNB11=1 selects test usage. The agreement's syntax identifier, version and service characters
|
||||
control output. UNB/UNH control references use the allocator's EDIFACT levels (up to 14 digits).
|
||||
CONTRL reconciles outbound status and resolves overdue alerts; rejected responses open
|
||||
REJECTED_ACK and raise `edi.ack.rejected`. APERAK remains a later document type.
|
||||
|
||||
## 7. Implementation guides as data
|
||||
|
||||
@@ -1014,3 +1020,18 @@ RC.5 adaptations implemented in this worktree:
|
||||
otherwise bypasses field locks inside the already-authorized widget action scope.
|
||||
- MASKED-user response scanning and Quick Search indexing are not available on the pinned RC.5
|
||||
test classpath. FULL and actual HIDDEN-field-lock scans run now; no simulated masking is used.
|
||||
|
||||
## Phase g implementation notes (2026-10-14)
|
||||
|
||||
- Outbound EDIFACT is ungrouped. Readers/writers preserve optional UNG/UNE groups. Ungrouped
|
||||
interchanges have a logical `edi_functional_group` tracking row, keyed by UNB reference, so
|
||||
the existing send-time deadline/overdue monitor also covers CONTRL. `groupCount` remains zero
|
||||
for those wire envelopes. GROUP_ONLY means an interchange-level CONTRL in this case.
|
||||
- Payload framing buffers one interchange (hard cap 50 MiB) before StAEDI; retained message
|
||||
segments respect `maxSegmentsPerTransaction`. It uses no filesystem. This follows the existing
|
||||
buffered storage adapters rather than the original bounded-memory streaming aspiration.
|
||||
- Retail SG labels are local guide identifiers shared by the two baseline versions, as documented
|
||||
in GUIDE-FORMAT.md; the baselines are deliberately small, not complete directory content.
|
||||
- Existing databases require the three agreement columns listed in README.md. Existing X12
|
||||
separator defaults remain unchanged; EDIFACT agreements explicitly select their outbound UNA
|
||||
characters. CONTRL action 8 is receipt-only and does not clear a functional acknowledgement deadline.
|
||||
+28
-1
@@ -1,6 +1,6 @@
|
||||
# EDI guide JSON
|
||||
|
||||
The five files in `src/main/resources/edi/guides/` are original QQQ baseline guides, not licensed
|
||||
The files in `src/main/resources/edi/guides/` are original QQQ baseline guides, not licensed
|
||||
X12/TR3 documentation or complete code sets. JSON loads into `guide.Guide` with
|
||||
`BuiltInGuideLoader.read(InputStream)`. `GuideRepository.load(id)` reconstructs the same model from
|
||||
the normalized `edi_guide*` and code-list tables.
|
||||
@@ -49,3 +49,30 @@ historical rows and their children remain addressable by id. A reverted resource
|
||||
new revision. Built-in guide writes require the loader's security key (`edi.guide.loader`), or a
|
||||
system service session. Configuration editors can receive `CUSTOM`/`IMPORTED` values of the
|
||||
`edi_guide_source` key without receiving permission to edit `BUILT_IN` rows.
|
||||
|
||||
## EDIFACT baselines
|
||||
|
||||
Set `standard` to `EDIFACT`, `transactionSet` to the UNH message type, and `version` to the
|
||||
concatenated UNH version/release (`D96A`, `D01B`). The ten bundled resources are in-house retail
|
||||
subsets with short field names and only the code values needed by the maps. They contain no
|
||||
copied directory descriptions and are not complete UNECE directories or EANCOM certifications.
|
||||
|
||||
UNH and UNT appear as actual guide segments. UNB/UNZ and UNG/UNE are checked by the envelope
|
||||
reader. UNH association codes, such as `EAN008`, remain available as component 5 of UNH02.
|
||||
EDIFACT mandatory (`M`) maps to `REQUIRED`; conditional (`C`) maps to `SITUATIONAL` with
|
||||
`condition` or rules where needed. A component uses the existing `position` plus
|
||||
`componentPosition`, for example NAD02 component 1. EDIFACT decimal values accept `.` or `,`;
|
||||
DECIMAL/UNIT inbound transforms normalize them and outbound DECIMAL uses the agreement's mark.
|
||||
|
||||
Segment groups use the existing loop tree: SG1 starts with RFF, SG2 with NAD, and the retail
|
||||
line loop SG25 starts with LIN. These are stable local guide loop labels, not a claim that every
|
||||
message/directory assigns the same published group numbers. DESADV uses SG10 (CPS) containing
|
||||
SG17 (LIN), matching carton/item associations. Map examples are `NAD[NAD01=BY]/NAD02-1`,
|
||||
`SG25/LIN/LIN03-1` and `SG25[*]/QTY/QTY01-2`. Named loops take precedence over element parsing,
|
||||
so `SG25` is a loop rather than an element of a hypothetical `SG` segment.
|
||||
|
||||
CONTRL baselines include UCI, optional UCF group responses, UCM message responses and nested
|
||||
UCS/UCD positional errors. Shared validator diagnostics are translated to EDIFACT 0085 codes;
|
||||
submitted erroneous values are not copied into the acknowledgement. `GROUP_ONLY` reports the
|
||||
interchange outcome, `ALL_TRANSACTIONS` includes accepted messages, and `ERRORS_ONLY` includes
|
||||
rejected message details. A CONTRL is never itself acknowledged. APERAK is deferred.
|
||||
+16
-3
@@ -52,9 +52,9 @@ for Shipment/Order/Pack/Item loops and parent numbering.
|
||||
DATE/TIME format QQQ values as X12 dates/times; DECIMAL multiplies by the implied `scale`.
|
||||
Other registered transforms operate on the source value. Conditions use the existing EDI rule
|
||||
language over segments already emitted. A missing required mapping input fails mapping;
|
||||
structurally invalid mapped output opens VALIDATION and is never sent. ST/SE are generated by
|
||||
the mapper and group-local controls are assigned by the worker. Component-target paths are
|
||||
currently rejected explicitly; the bundled retail maps use simple elements.
|
||||
structurally invalid mapped output opens VALIDATION and is never sent. ST/SE or UNH/UNT are generated by
|
||||
the mapper; the worker allocates controls. Component-target paths use `-`, for example
|
||||
`LIN03-1` and `LIN03-2`, and retain components separately so the EDIFACT writer can escape data.
|
||||
|
||||
## Warehouse and logistics samples
|
||||
|
||||
@@ -76,3 +76,16 @@ collide with the header N1 loop) and the stop sequence is mapped from S501. A 21
|
||||
events; the sample map records the first (`LX[1]/...`) and a host that wants all of them maps an
|
||||
association instead. Optional outbound qualifiers such as MAN01 and L1102 are constants, so the
|
||||
sample source rows must carry the value they qualify (the carton SSCC, the bill of lading).
|
||||
|
||||
## EDIFACT retail samples
|
||||
|
||||
Each of D96A and D01B includes `ORDERS-<version>-sample.json` inbound to `sample_order` and
|
||||
`lines`, and outbound `ORDRSP` from `sample_order`, `DESADV` from `sample_shipment` with
|
||||
`cartons.items`, and `INVOIC` from `sample_invoice` with `lines`. Test sources define these
|
||||
synthetic tables. ORDERS carries its business key in BGM02, date in qualified DTM01 component 2,
|
||||
buyer in qualified NAD02 component 1, and items in SG25. BGM's document codes are 220, 231, 351
|
||||
and 380 respectively. These subsets intentionally carry only the fields exercised by the samples.
|
||||
|
||||
Composite paths address values, not delimiter-joined strings. An identifier containing `+`, `:`
|
||||
or `'` remains a single value; the writer supplies release escapes. The outbound agreement owns
|
||||
UNA settings. Separate map resources per directory keep future version-specific changes isolated.
|
||||
@@ -3,6 +3,7 @@ CREATE TABLE edi_agreement (
|
||||
component_separator VARCHAR(255),
|
||||
control_number_scope VARCHAR(255),
|
||||
create_date TIMESTAMP,
|
||||
decimal_mark VARCHAR(255),
|
||||
duplicate_window_days INTEGER,
|
||||
element_separator VARCHAR(255),
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
@@ -19,11 +20,13 @@ CREATE TABLE edi_agreement (
|
||||
outbound_transport_id INTEGER,
|
||||
partner_id INTEGER,
|
||||
partner_identity_id INTEGER,
|
||||
release_character VARCHAR(255),
|
||||
repetition_separator VARCHAR(255),
|
||||
request_ta1 BOOLEAN,
|
||||
segment_terminator VARCHAR(255),
|
||||
send_ta1 VARCHAR(255),
|
||||
standard VARCHAR(255),
|
||||
syntax_identifier VARCHAR(255),
|
||||
usage_indicator VARCHAR(255),
|
||||
UNIQUE (local_identity_id, partner_identity_id, standard, usage_indicator)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.document;
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.edi.x12.EnvelopeFinding;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
|
||||
/*******************************************************************************
|
||||
** Standard-neutral document view over the existing immutable segment value model.
|
||||
*******************************************************************************/
|
||||
public interface EdiDocument
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Return the actual message header, including its original composite values.
|
||||
*******************************************************************************/
|
||||
X12Segment header();
|
||||
|
||||
/*******************************************************************************
|
||||
** Return ordered segments including the message envelope.
|
||||
*******************************************************************************/
|
||||
List<X12Segment> segments();
|
||||
|
||||
/*******************************************************************************
|
||||
** Return envelope diagnostics without submitted values.
|
||||
*******************************************************************************/
|
||||
List<EnvelopeFinding> findings();
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.document;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactSeparators;
|
||||
|
||||
/*******************************************************************************
|
||||
** Frame concatenated X12 and EDIFACT envelopes without delimiter guessing in data.
|
||||
*******************************************************************************/
|
||||
public class EnvelopeReader
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Read one envelope, respecting UNA and released terminators; null means EOF.
|
||||
*******************************************************************************/
|
||||
public byte[] next(InputStream input) throws IOException
|
||||
{
|
||||
int first;
|
||||
do
|
||||
{
|
||||
first = input.read();
|
||||
}
|
||||
while(first != -1 && Character.isWhitespace(first));
|
||||
if(first == -1)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
bytes.write(first);
|
||||
bytes.write(input.readNBytes(2));
|
||||
String tag = bytes.toString(StandardCharsets.US_ASCII);
|
||||
char terminator;
|
||||
char release = 0;
|
||||
String end;
|
||||
StringBuilder prefix = new StringBuilder();
|
||||
if("ISA".equals(tag))
|
||||
{
|
||||
bytes.write(input.readNBytes(103));
|
||||
if(bytes.size() != 106)
|
||||
{
|
||||
return (bytes.toByteArray());
|
||||
}
|
||||
terminator = (char) bytes.toByteArray()[105];
|
||||
end = "IEA";
|
||||
}
|
||||
else if("UNA".equals(tag) || "UNB".equals(tag))
|
||||
{
|
||||
EdifactSeparators separators = EdifactSeparators.DEFAULT;
|
||||
if("UNA".equals(tag))
|
||||
{
|
||||
bytes.write(input.readNBytes(6));
|
||||
separators = EdifactSeparators.fromUna(bytes.toString(StandardCharsets.US_ASCII));
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix.append("UNB");
|
||||
}
|
||||
terminator = separators.segment();
|
||||
release = separators.release();
|
||||
end = "UNZ";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Unknown EDI envelope");
|
||||
}
|
||||
boolean escaped = false;
|
||||
int value;
|
||||
while((value = input.read()) != -1)
|
||||
{
|
||||
bytes.write(value);
|
||||
if(bytes.size() > 50 * 1024 * 1024)
|
||||
{
|
||||
throw new IOException("EDI envelope exceeds size limit");
|
||||
}
|
||||
if(escaped)
|
||||
{
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if(value == release)
|
||||
{
|
||||
escaped = true;
|
||||
}
|
||||
else if(value == terminator)
|
||||
{
|
||||
if(prefix.toString().equals(end))
|
||||
{
|
||||
break;
|
||||
}
|
||||
prefix.setLength(0);
|
||||
}
|
||||
else if(prefix.length() < 3 && !(prefix.isEmpty() && Character.isWhitespace(value)))
|
||||
{
|
||||
prefix.append((char) value);
|
||||
}
|
||||
}
|
||||
return (bytes.toByteArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.edi.ack.AckLevel;
|
||||
import com.kingsrook.qbits.edi.guide.ValidationFinding;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
|
||||
/*******************************************************************************
|
||||
** Positional CONTRL syntax responses; business application failures are not syntax errors.
|
||||
*******************************************************************************/
|
||||
public class ContrlBuilder
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Build directory-specific acknowledgement details without echoing data values.
|
||||
*******************************************************************************/
|
||||
public EdifactMessage build(EdifactInterchange input, Map<String, List<ValidationFinding>> findings, AckLevel level, String reference, String version)
|
||||
{
|
||||
List<X12Segment> segments = new ArrayList<>();
|
||||
segments.add(EdifactWriter.segment("UNH", 1, List.of(reference), List.of("CONTRL", version.substring(0, 1), version.substring(1), "UN")));
|
||||
boolean rejected = !input.findings().isEmpty() || level == AckLevel.GROUP_ONLY && (input.groups().stream().anyMatch(g -> !g.findings().isEmpty())
|
||||
|| input.allMessages().stream().anyMatch(m -> rejected(m, findings)));
|
||||
var uci = new ArrayList<List<String>>();
|
||||
uci.add(List.of(input.unb().value(5)));
|
||||
uci.add(input.unb().elements().get(1).getFirst());
|
||||
uci.add(input.unb().elements().get(2).getFirst());
|
||||
uci.add(List.of(rejected ? "4" : "7"));
|
||||
if(!input.findings().isEmpty())
|
||||
{
|
||||
uci.add(List.of(input.findings().getFirst().code()));
|
||||
}
|
||||
segments.add(new X12Segment("UCI", 2, uci.stream().map(List::of).toList()));
|
||||
if(level != AckLevel.GROUP_ONLY && input.findings().isEmpty())
|
||||
{
|
||||
messages(input.messages(), findings, level, segments);
|
||||
for(var group : input.groups())
|
||||
{
|
||||
var response = EdifactWriter.segment("UCF", segments.size() + 1, List.of(group.ung().value(5)), group.ung().elements().get(1).getFirst(),
|
||||
group.ung().elements().get(2).getFirst(), List.of(group.findings().isEmpty() ? "7" : "4"));
|
||||
if(!group.findings().isEmpty())
|
||||
{
|
||||
var fields = new ArrayList<>(response.elements());
|
||||
fields.add(List.of(List.of(group.findings().getFirst().code())));
|
||||
response = new X12Segment("UCF", response.position(), fields);
|
||||
}
|
||||
segments.add(response);
|
||||
messages(group.messages(), findings, level, segments);
|
||||
}
|
||||
}
|
||||
segments.add(X12Segment.of("UNT", segments.size() + 1, String.valueOf(segments.size() + 1), reference));
|
||||
return (new EdifactMessage(segments.getFirst(), segments.getLast(), List.copyOf(segments), List.of()));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Suppress accepted message detail only under ERRORS_ONLY.
|
||||
*******************************************************************************/
|
||||
private void messages(List<EdifactMessage> messages, Map<String, List<ValidationFinding>> findings, AckLevel level, List<X12Segment> segments)
|
||||
{
|
||||
for(var message : messages)
|
||||
{
|
||||
if("CONTRL".equals(message.type()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
boolean rejected = rejected(message, findings);
|
||||
if(level == AckLevel.ERRORS_ONLY && !rejected)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var fields = new ArrayList<List<List<String>>>();
|
||||
fields.add(List.of(List.of(message.reference())));
|
||||
fields.add(message.header().elements().get(1));
|
||||
fields.add(List.of(List.of(rejected ? "4" : "7")));
|
||||
if(!message.findings().isEmpty())
|
||||
{
|
||||
fields.add(List.of(List.of(message.findings().getFirst().code())));
|
||||
}
|
||||
segments.add(new X12Segment("UCM", segments.size() + 1, fields));
|
||||
for(var finding : findings.getOrDefault(message.reference(), List.of()))
|
||||
{
|
||||
if(!"ERROR".equals(finding.severity()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String code = syntaxCode(finding);
|
||||
segments.add(finding.elementPosition() == null
|
||||
? X12Segment.of("UCS", segments.size() + 1, String.valueOf(finding.segmentPosition()), code)
|
||||
: X12Segment.of("UCS", segments.size() + 1, String.valueOf(finding.segmentPosition())));
|
||||
if(finding.elementPosition() != null)
|
||||
{
|
||||
List<String> position = finding.componentPosition() == null ? List.of(finding.elementPosition().toString())
|
||||
: List.of(finding.elementPosition().toString(), finding.componentPosition().toString());
|
||||
segments.add(EdifactWriter.segment("UCD", segments.size() + 1, List.of(code), position));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Envelope and structural errors reject the message, warnings do not.
|
||||
*******************************************************************************/
|
||||
private boolean rejected(EdifactMessage message, Map<String, List<ValidationFinding>> findings)
|
||||
{
|
||||
return (!message.findings().isEmpty() || findings.getOrDefault(message.reference(), List.of()).stream().anyMatch(f -> "ERROR".equals(f.severity())));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Translate shared validator diagnostics to service syntax codes (0085).
|
||||
*******************************************************************************/
|
||||
public static String syntaxCode(ValidationFinding finding)
|
||||
{
|
||||
String code = finding.elementPosition() == null ? finding.ak3Code() : finding.ak4Code();
|
||||
if(code == null)
|
||||
{
|
||||
return ("12");
|
||||
}
|
||||
return (finding.elementPosition() == null ? switch(code)
|
||||
{
|
||||
case "3" -> "13";
|
||||
case "4", "5" -> "35";
|
||||
case "2", "6", "7" -> "33";
|
||||
default -> "12";
|
||||
} : switch(code)
|
||||
{
|
||||
case "1", "2" -> "13";
|
||||
case "4" -> "14";
|
||||
case "5" -> "15";
|
||||
default -> "12";
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.edi.EdiRuntime;
|
||||
import com.kingsrook.qbits.edi.flow.EdiJournal;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSeverity;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSignal;
|
||||
import com.kingsrook.qqq.backend.core.alerts.QAlerts;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** Reconcile addressed CONTRL controls only within the routed outbound agreement.
|
||||
*******************************************************************************/
|
||||
public class ContrlReader
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Apply functional acceptance/rejection; receipt-only code 8 leaves deadlines open.
|
||||
*******************************************************************************/
|
||||
public void reconcile(EdifactMessage message, QRecord agreement, QRecord received) throws Exception
|
||||
{
|
||||
X12Segment uci = message.segments().stream().filter(s -> "UCI".equals(s.id())).findFirst().orElseThrow();
|
||||
if("8".equals(uci.value(4)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
String overall = status(uci.value(4));
|
||||
var candidates = EdiRecords.query("edi_interchange", EdiRecords.equal("agreementId", agreement.getValue("id")).withCriteria("direction", QCriteriaOperator.EQUALS, "OUTBOUND")
|
||||
.withCriteria("standard", QCriteriaOperator.EQUALS, "EDIFACT").withCriteria("controlNumber", QCriteriaOperator.EQUALS, uci.value(1))
|
||||
.withCriteria("senderId", QCriteriaOperator.EQUALS, uci.value(2)).withCriteria("senderQualifier", QCriteriaOperator.EQUALS, uci.value(2, 1, 2))
|
||||
.withCriteria("receiverId", QCriteriaOperator.EQUALS, uci.value(3)).withCriteria("receiverQualifier", QCriteriaOperator.EQUALS, uci.value(3, 1, 2)));
|
||||
if(candidates.size() != 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Acknowledgement reference is unknown or ambiguous");
|
||||
}
|
||||
QRecord interchange = candidates.getFirst();
|
||||
Map<String, X12Segment> details = new LinkedHashMap<>();
|
||||
Map<String, String> groupStatuses = new LinkedHashMap<>();
|
||||
String currentGroup = "";
|
||||
for(var segment : message.segments())
|
||||
{
|
||||
if("UCF".equals(segment.id()))
|
||||
{
|
||||
currentGroup = segment.value(1);
|
||||
groupStatuses.put(currentGroup, status(segment.value(4)));
|
||||
}
|
||||
if("UCM".equals(segment.id()))
|
||||
{
|
||||
String key = currentGroup + ":" + segment.value(1);
|
||||
if(details.put(key, segment) != null)
|
||||
{
|
||||
throw new IllegalArgumentException("Duplicate acknowledgement message reference");
|
||||
}
|
||||
status(segment.value(3));
|
||||
}
|
||||
}
|
||||
boolean rejected = "R".equals(overall);
|
||||
var groups = EdiRecords.query("edi_functional_group", EdiRecords.equal("interchangeId", interchange.getValue("id")));
|
||||
for(var group : groups)
|
||||
{
|
||||
String scope = interchange.getValueInteger("groupCount") == 0 ? "" : group.getValueString("controlNumber");
|
||||
String groupStatus = "R".equals(overall) ? "R" : groupStatuses.getOrDefault(scope, overall);
|
||||
boolean groupRejected = "R".equals(groupStatus);
|
||||
for(var tx : EdiRecords.query("edi_transaction", EdiRecords.equal("functionalGroupId", group.getValue("id"))))
|
||||
{
|
||||
X12Segment detail = details.remove(scope + ":" + tx.getValueString("controlNumber"));
|
||||
if(detail != null && (!detail.value(2).equals(tx.getValueString("transactionSet"))
|
||||
|| !(detail.value(2, 1, 2) + detail.value(2, 1, 3)).equals(tx.getValueString("version"))))
|
||||
{
|
||||
throw new IllegalArgumentException("Acknowledgement message identification mismatch");
|
||||
}
|
||||
String result = "R".equals(groupStatus) || detail != null && "R".equals(status(detail.value(3))) ? "R" : "A";
|
||||
groupRejected |= "R".equals(result);
|
||||
SystemWrites.update("edi_transaction", new QRecord().withValue("id", tx.getValue("id")).withValue("ackStatus", result)
|
||||
.withValue("status", "R".equals(result) ? "REJECTED" : "ACKNOWLEDGED"));
|
||||
}
|
||||
rejected |= groupRejected;
|
||||
SystemWrites.update("edi_functional_group", new QRecord().withValue("id", group.getValue("id")).withValue("ackStatus", groupRejected ? "R" : "A")
|
||||
.withValue("ackReceivedAt", EdiRuntime.current().config().getClock().instant()).withValue("ackTransactionId", received.getValue("id")));
|
||||
}
|
||||
if(!details.isEmpty() || groupStatuses.keySet().stream().anyMatch(c -> groups.stream().noneMatch(g -> c.equals(g.getValueString("controlNumber")))))
|
||||
{
|
||||
throw new IllegalArgumentException("Acknowledgement contains an unknown message or group reference");
|
||||
}
|
||||
SystemWrites.update("edi_interchange", new QRecord().withValue("id", interchange.getValue("id")).withValue("status", rejected ? "REJECTED" : "ACKNOWLEDGED"));
|
||||
SystemWrites.update("edi_transaction", new QRecord().withValue("id", received.getValue("id")).withValue("status", "ACKNOWLEDGED").withValue("ackStatus", rejected ? "R" : "A"));
|
||||
for(var group : groups)
|
||||
{
|
||||
QAlerts.resolve("edi.ack.overdue", "edi_functional_group:" + group.getValue("id"), "CONTRL received");
|
||||
}
|
||||
for(var exception : EdiRecords.query("edi_exception", EdiRecords.equal("interchangeId", interchange.getValue("id")).withCriteria("type", QCriteriaOperator.EQUALS, "OVERDUE_ACK")
|
||||
.withCriteria("status", QCriteriaOperator.IN, "OPEN", "IN_PROGRESS")))
|
||||
{
|
||||
SystemWrites.update("edi_exception", new QRecord().withValue("id", exception.getValue("id")).withValue("status", "RESOLVED")
|
||||
.withValue("resolvedAt", EdiRuntime.current().config().getClock().instant()).withValue("resolution", "CONTRL received"));
|
||||
}
|
||||
if(rejected)
|
||||
{
|
||||
EdiJournal.exception(null, interchange.getValueInteger("id"), null, agreement.getValueInteger("partnerId"), "REJECTED_ACK", "Partner rejected an outbound EDIFACT document");
|
||||
QAlerts.raise(new AlertSignal("edi.ack.rejected").withSeverity(AlertSeverity.ERROR).withDedupKey("edi_interchange:" + interchange.getValue("id"))
|
||||
.withTitle("EDI acknowledgement rejected").withSummary("Review the outbound interchange"));
|
||||
}
|
||||
EdiJournal.event(null, interchange.getValueInteger("id"), received.getValueInteger("id"), "ACK_RECEIVED");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Only functional acceptance and rejection can complete an expected response.
|
||||
*******************************************************************************/
|
||||
private String status(String action)
|
||||
{
|
||||
return (switch(action)
|
||||
{
|
||||
case "7" -> "A";
|
||||
case "4" -> "R";
|
||||
default -> throw new IllegalArgumentException("Unsupported CONTRL action");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.edi.document.EnvelopeReader;
|
||||
import com.kingsrook.qbits.edi.x12.EnvelopeFinding;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
import io.xlate.edi.stream.EDIInputFactory;
|
||||
import io.xlate.edi.stream.EDIStreamEvent;
|
||||
import io.xlate.edi.stream.EDIStreamException;
|
||||
import io.xlate.edi.stream.EDIStreamReader;
|
||||
import io.xlate.edi.stream.EDIStreamValidationError;
|
||||
|
||||
/*******************************************************************************
|
||||
** StAEDI tokenizer with explicit UN envelope checks and bounded message retention.
|
||||
*******************************************************************************/
|
||||
public class EdifactEnvelopeReader
|
||||
{
|
||||
private final int maxSegments;
|
||||
|
||||
/*******************************************************************************
|
||||
** Bound retained message segments, including UNH and UNT.
|
||||
*******************************************************************************/
|
||||
public EdifactEnvelopeReader(int maxSegments)
|
||||
{
|
||||
if(maxSegments < 2)
|
||||
{
|
||||
throw new IllegalArgumentException("At least two segments are required");
|
||||
}
|
||||
this.maxSegments = maxSegments;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Read concatenated EDIFACT interchanges without closing the caller stream.
|
||||
*******************************************************************************/
|
||||
public List<EdifactInterchange> read(InputStream input) throws IOException
|
||||
{
|
||||
List<EdifactInterchange> result = new ArrayList<>();
|
||||
EnvelopeReader framing = new EnvelopeReader();
|
||||
byte[] bytes;
|
||||
while((bytes = framing.next(input)) != null)
|
||||
{
|
||||
result.add(parse(bytes));
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Parse one framed interchange; diagnostics never include submitted values.
|
||||
*******************************************************************************/
|
||||
private EdifactInterchange parse(byte[] bytes) throws IOException
|
||||
{
|
||||
String prefix = new String(bytes, 0, Math.min(9, bytes.length), StandardCharsets.US_ASCII);
|
||||
EdifactSeparators separators = prefix.startsWith("UNA") ? EdifactSeparators.fromUna(prefix) : EdifactSeparators.DEFAULT;
|
||||
List<EnvelopeFinding> errors = new ArrayList<>();
|
||||
List<EnvelopeFinding> groupErrors = new ArrayList<>();
|
||||
List<EnvelopeFinding> messageErrors = new ArrayList<>();
|
||||
List<EdifactGroup> groups = new ArrayList<>();
|
||||
List<EdifactMessage> messages = new ArrayList<>();
|
||||
List<EdifactMessage> grouped = new ArrayList<>();
|
||||
List<X12Segment> segments = new ArrayList<>();
|
||||
var references = new HashSet<String>();
|
||||
X12Segment unb = null;
|
||||
X12Segment unz = null;
|
||||
X12Segment ung = null;
|
||||
X12Segment unh = null;
|
||||
String tag = "";
|
||||
int count = 0;
|
||||
List<List<List<String>>> values = new ArrayList<>();
|
||||
EDIInputFactory factory = EDIInputFactory.newFactory();
|
||||
factory.setProperty(EDIInputFactory.EDI_IGNORE_EXTRANEOUS_CHARACTERS, true);
|
||||
factory.setProperty(EDIInputFactory.EDI_VALIDATE_CONTROL_CODE_VALUES, false);
|
||||
try(EDIStreamReader reader = factory.createEDIStreamReader(new ByteArrayInputStream(bytes)))
|
||||
{
|
||||
while(reader.hasNext())
|
||||
{
|
||||
EDIStreamEvent event = reader.next();
|
||||
if(event == EDIStreamEvent.START_SEGMENT)
|
||||
{
|
||||
tag = reader.getText();
|
||||
values = new ArrayList<>();
|
||||
}
|
||||
else if(event == EDIStreamEvent.ELEMENT_DATA)
|
||||
{
|
||||
int e = reader.getLocation().getElementPosition();
|
||||
int r = Math.max(1, reader.getLocation().getElementOccurrence());
|
||||
int c = Math.max(1, reader.getLocation().getComponentPosition());
|
||||
while(values.size() < e)
|
||||
{
|
||||
values.add(new ArrayList<>());
|
||||
}
|
||||
while(values.get(e - 1).size() < r)
|
||||
{
|
||||
values.get(e - 1).add(new ArrayList<>());
|
||||
}
|
||||
var components = values.get(e - 1).get(r - 1);
|
||||
while(components.size() < c)
|
||||
{
|
||||
components.add("");
|
||||
}
|
||||
components.set(c - 1, reader.getText());
|
||||
}
|
||||
else if(event.isError() && reader.getErrorType() != EDIStreamValidationError.CONTROL_REFERENCE_MISMATCH
|
||||
&& reader.getErrorType() != EDIStreamValidationError.CONTROL_COUNT_DOES_NOT_MATCH_ACTUAL_COUNT)
|
||||
{
|
||||
if(unh != null)
|
||||
{
|
||||
messageErrors.add(error("UNH", Math.max(1, count), "12"));
|
||||
}
|
||||
else if(ung != null)
|
||||
{
|
||||
groupErrors.add(error("UNG", count, "12"));
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.add(error("UNB", count, "12"));
|
||||
}
|
||||
}
|
||||
else if(event == EDIStreamEvent.END_SEGMENT)
|
||||
{
|
||||
if("UNA".equals(tag))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
count = "UNH".equals(tag) ? 1 : count + 1;
|
||||
X12Segment segment = new X12Segment(tag, count, values);
|
||||
switch(tag)
|
||||
{
|
||||
case "UNB" ->
|
||||
{
|
||||
unb = segment;
|
||||
if("4".equals(unb.value(1, 1, 2)) && !prefix.startsWith("UNA"))
|
||||
{
|
||||
separators = new EdifactSeparators(':', '+', '.', '?', '*', '\'');
|
||||
}
|
||||
if(!List.of("3", "4").contains(unb.value(1, 1, 2)) || !List.of("", "1").contains(unb.value(11))
|
||||
|| unb.value(5).isEmpty() || unb.value(5).length() > 14)
|
||||
{
|
||||
errors.add(error("UNB", 1, "12"));
|
||||
}
|
||||
}
|
||||
case "UNG" ->
|
||||
{
|
||||
ung = segment;
|
||||
grouped = new ArrayList<>();
|
||||
groupErrors = new ArrayList<>();
|
||||
}
|
||||
case "UNH" ->
|
||||
{
|
||||
unh = segment;
|
||||
segments = new ArrayList<>();
|
||||
segments.add(segment);
|
||||
messageErrors = new ArrayList<>();
|
||||
if(!references.add(unh.value(1)))
|
||||
{
|
||||
messageErrors.add(error("UNH", 1, "19"));
|
||||
}
|
||||
}
|
||||
case "UNT" ->
|
||||
{
|
||||
if(unh == null)
|
||||
{
|
||||
errors.add(error("UNB", count, "33"));
|
||||
break;
|
||||
}
|
||||
check(messageErrors, "UNH", segment, segment.value(1), String.valueOf(count), "29");
|
||||
check(messageErrors, "UNH", segment, segment.value(2), unh.value(1), "28");
|
||||
if(segments.size() < maxSegments)
|
||||
{
|
||||
segments.add(segment);
|
||||
}
|
||||
(ung == null ? messages : grouped).add(new EdifactMessage(unh, segment, List.copyOf(segments), List.copyOf(messageErrors)));
|
||||
unh = null;
|
||||
}
|
||||
case "UNE" ->
|
||||
{
|
||||
if(ung == null)
|
||||
{
|
||||
errors.add(error("UNB", count, "33"));
|
||||
break;
|
||||
}
|
||||
check(groupErrors, "UNG", segment, segment.value(1), String.valueOf(grouped.size()), "29");
|
||||
check(groupErrors, "UNG", segment, segment.value(2), ung.value(5), "28");
|
||||
groups.add(new EdifactGroup(ung, segment, List.copyOf(grouped), List.copyOf(groupErrors)));
|
||||
ung = null;
|
||||
}
|
||||
case "UNZ" ->
|
||||
{
|
||||
unz = segment;
|
||||
check(errors, "UNB", segment, segment.value(1), String.valueOf(groups.isEmpty() ? messages.size() : groups.size()), "29");
|
||||
check(errors, "UNB", segment, segment.value(2), unb == null ? "" : unb.value(5), "28");
|
||||
}
|
||||
default ->
|
||||
{
|
||||
if(unh == null)
|
||||
{
|
||||
errors.add(error("UNB", count, "33"));
|
||||
}
|
||||
else if(segments.size() < maxSegments - 1)
|
||||
{
|
||||
segments.add(segment);
|
||||
}
|
||||
else if(messageErrors.stream().noneMatch(f -> "35".equals(f.code())))
|
||||
{
|
||||
messageErrors.add(error("UNH", count, "35"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(EDIStreamException e)
|
||||
{
|
||||
errors.add(error("UNB", 0, "12"));
|
||||
}
|
||||
if(unb == null || unz == null || ung != null || unh != null || !groups.isEmpty() && !messages.isEmpty())
|
||||
{
|
||||
errors.add(error("UNB", 0, "13"));
|
||||
}
|
||||
return (new EdifactInterchange(separators, unb, unz, List.copyOf(groups), List.copyOf(messages), List.copyOf(errors)));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Count fields accept zero padding; references retain their exact spelling.
|
||||
*******************************************************************************/
|
||||
private static void check(List<EnvelopeFinding> findings, String level, X12Segment segment, String actual, String expected, String code)
|
||||
{
|
||||
boolean equal = "29".equals(code) ? actual.matches("[0-9]+") && new java.math.BigInteger(actual).toString().equals(expected) : actual.equals(expected);
|
||||
if(!equal)
|
||||
{
|
||||
findings.add(error(level, segment.position(), code));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Service syntax error code 0085 with a value-free diagnostic.
|
||||
*******************************************************************************/
|
||||
private static EnvelopeFinding error(String level, int position, String code)
|
||||
{
|
||||
return (new EnvelopeFinding(level, position, code, "EDIFACT envelope failed validation"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.edi.x12.EnvelopeFinding;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
|
||||
/*******************************************************************************
|
||||
** Optional UNG/UNE functional group; ungrouped messages belong to the interchange.
|
||||
*******************************************************************************/
|
||||
public record EdifactGroup(X12Segment ung, X12Segment une, List<EdifactMessage> messages, List<EnvelopeFinding> findings)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qbits.edi.EdiRuntime;
|
||||
import com.kingsrook.qbits.edi.ack.AckLevel;
|
||||
import com.kingsrook.qbits.edi.flow.EdiJournal;
|
||||
import com.kingsrook.qbits.edi.flow.InboundProcessor;
|
||||
import com.kingsrook.qbits.edi.flow.InboxWorker;
|
||||
import com.kingsrook.qbits.edi.flow.OutboundWorker;
|
||||
import com.kingsrook.qbits.edi.guide.GuideRepository;
|
||||
import com.kingsrook.qbits.edi.guide.ValidationFinding;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.storage.PayloadStore;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** Native UNB routing and CONTRL with the shared revision, validation and apply checkpoints.
|
||||
*******************************************************************************/
|
||||
public class EdifactInbound extends InboundProcessor
|
||||
{
|
||||
private final QRecord inbox;
|
||||
private final Instant lease;
|
||||
|
||||
/*******************************************************************************
|
||||
** Bind every write to the same inbox lease used by the X12 worker.
|
||||
*******************************************************************************/
|
||||
public EdifactInbound(QRecord inbox, Instant lease, PayloadStore store)
|
||||
{
|
||||
super(inbox, lease, store);
|
||||
this.inbox = inbox;
|
||||
this.lease = lease;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Route UNB identities before validating, acknowledging or applying messages.
|
||||
*******************************************************************************/
|
||||
public void process(EdifactInterchange input, int position) throws Exception
|
||||
{
|
||||
if(input.unb() == null)
|
||||
{
|
||||
InboxWorker.fenced(inbox, lease, () ->
|
||||
{
|
||||
EdiJournal.exception(inbox.getValueInteger("id"), null, null, null, "ENVELOPE", "Invalid EDIFACT header");
|
||||
return (null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
QRecord agreement = route(input);
|
||||
QRecord interchange = InboxWorker.fenced(inbox, lease, () -> interchange(input, agreement, position));
|
||||
if(agreement == null)
|
||||
{
|
||||
problem(interchange, "UNKNOWN_PARTNER");
|
||||
return;
|
||||
}
|
||||
if(inbox.getValueString("username") != null && EdiRecords.query("edi_partner", EdiRecords.equal("gatewayUsername", inbox.getValue("username"))).stream()
|
||||
.anyMatch(p -> !p.getValue("id").equals(agreement.getValue("partnerId"))))
|
||||
{
|
||||
problem(interchange, "PARTNER_MISMATCH");
|
||||
return;
|
||||
}
|
||||
if(inbox.getValueLong("sizeBytes") != null && inbox.getValueLong("sizeBytes") > Objects.requireNonNullElse(agreement.getValueLong("maxInterchangeBytes"), 52428800L))
|
||||
{
|
||||
problem(interchange, "ENVELOPE");
|
||||
return;
|
||||
}
|
||||
boolean duplicate = "DUPLICATE".equals(interchange.getValueString("status"));
|
||||
boolean ackInput = !input.allMessages().isEmpty() && input.allMessages().stream().allMatch(m -> "CONTRL".equals(m.type()));
|
||||
if(duplicate)
|
||||
{
|
||||
problem(interchange, "DUPLICATE");
|
||||
if(!"PROCESS".equals(agreement.getValueString("onDuplicate")))
|
||||
{
|
||||
if(!ackInput && "REJECT_TA1".equals(agreement.getValueString("onDuplicate")))
|
||||
{
|
||||
var rejected = new EdifactInterchange(input.separators(), input.unb(), input.unz(), input.groups(), input.messages(),
|
||||
List.of(new com.kingsrook.qbits.edi.x12.EnvelopeFinding("UNB", 1, "19", "Duplicate interchange")));
|
||||
acknowledge(rejected, agreement, interchange, Map.of(), AckLevel.GROUP_ONLY);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(!input.findings().isEmpty())
|
||||
{
|
||||
problem(interchange, "ENVELOPE");
|
||||
if(!ackInput)
|
||||
{
|
||||
acknowledge(input, agreement, interchange, Map.of(), AckLevel.GROUP_ONLY);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Map<String, List<ValidationFinding>> findings = new LinkedHashMap<>();
|
||||
AckLevel level = AckLevel.ERRORS_ONLY;
|
||||
boolean sendAck = false;
|
||||
List<EdifactGroup> batches = input.groups().isEmpty() ? List.of(new EdifactGroup(null, null, input.messages(), List.of())) : input.groups();
|
||||
int groupPosition = 0;
|
||||
for(var batch : batches)
|
||||
{
|
||||
int gp = ++groupPosition;
|
||||
QRecord group = InboxWorker.fenced(inbox, lease, () ->
|
||||
{
|
||||
var prior = EdiRecords.query("edi_functional_group", EdiRecords.equal("interchangeId", interchange.getValue("id")).withCriteria("inboundPosition", QCriteriaOperator.EQUALS, gp));
|
||||
return (prior.isEmpty() ? SystemWrites.insert("edi_functional_group", new QRecord().withValue("interchangeId", interchange.getValue("id")).withValue("inboundPosition", gp)
|
||||
.withValue("senderId", input.unb().value(2)).withValue("controlNumber", batch.ung() == null ? input.unb().value(5) : batch.ung().value(5))
|
||||
.withValue("version", batch.messages().isEmpty() ? null : batch.messages().getFirst().version())
|
||||
.withValue("functionalIdCode", batch.ung() == null ? null : batch.ung().value(1)).withValue("transactionCount", batch.messages().size()).withValue("ackStatus", "NONE")) : prior.getFirst());
|
||||
});
|
||||
int messagePosition = 0;
|
||||
for(var message : batch.messages())
|
||||
{
|
||||
QRecord document = document(agreement, message);
|
||||
int mp = ++messagePosition;
|
||||
QRecord configured = document;
|
||||
QRecord tx = InboxWorker.fenced(inbox, lease, () ->
|
||||
{
|
||||
var prior = EdiRecords.query("edi_transaction", EdiRecords.equal("functionalGroupId", group.getValue("id")).withCriteria("inboundPosition", QCriteriaOperator.EQUALS, mp));
|
||||
return (prior.isEmpty() ? SystemWrites.insert("edi_transaction", new QRecord().withValue("interchangeId", interchange.getValue("id")).withValue("functionalGroupId", group.getValue("id"))
|
||||
.withValue("inboundPosition", mp).withValue("partnerId", agreement.getValue("partnerId")).withValue("direction", "INBOUND").withValue("transactionSet", message.type())
|
||||
.withValue("version", message.version()).withValue("controlNumber", message.reference()).withValue("segmentCount", message.segments().size()).withValue("status", "RECEIVED")
|
||||
.withValue("agreementDocumentId", configured == null ? null : configured.getValue("id"))
|
||||
.withValue("containsPhi", configured != null && Boolean.TRUE.equals(configured.getValueBoolean("containsPhi")))) : prior.getFirst());
|
||||
});
|
||||
document = revisions(document, tx);
|
||||
List<ValidationFinding> result = new ArrayList<>();
|
||||
if(document == null || document.getValue("guideId") == null)
|
||||
{
|
||||
result.add(finding("No implementation guide is configured"));
|
||||
problem(interchange, "NOT_CONFIGURED");
|
||||
}
|
||||
else
|
||||
{
|
||||
result.addAll(EdiRuntime.current().validator().validate(message, new GuideRepository().load(document.getValueInteger("guideId")), Boolean.TRUE.equals(document.getValueBoolean("containsPhi"))));
|
||||
}
|
||||
findings.put(message.reference(), List.copyOf(result));
|
||||
if(!batch.findings().isEmpty() || !message.findings().isEmpty())
|
||||
{
|
||||
result.add(finding("Envelope counts or references failed validation"));
|
||||
}
|
||||
QRecord effective = document;
|
||||
InboxWorker.fenced(inbox, lease, () ->
|
||||
{
|
||||
validation(tx, result, effective);
|
||||
return (null);
|
||||
});
|
||||
if("CONTRL".equals(message.type()))
|
||||
{
|
||||
if(result.isEmpty())
|
||||
{
|
||||
InboxWorker.fenced(inbox, lease, () ->
|
||||
{
|
||||
new ContrlReader().reconcile(message, agreement, tx);
|
||||
return (null);
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(document != null && "CONTRL".equals(document.getValueString("ackExpectation")))
|
||||
{
|
||||
sendAck = true;
|
||||
AckLevel selected = AckLevel.valueOf(Objects.requireNonNullElse(document.getValueString("ackLevel"), "ERRORS_ONLY"));
|
||||
if(selected == AckLevel.ALL_TRANSACTIONS || level != AckLevel.ALL_TRANSACTIONS && selected == AckLevel.GROUP_ONLY)
|
||||
{
|
||||
level = selected;
|
||||
}
|
||||
}
|
||||
if(document != null && result.stream().noneMatch(f -> "ERROR".equals(f.severity()))
|
||||
&& (!"T".equals(input.usage()) || Boolean.TRUE.equals(agreement.getValueBoolean("applyTestDocuments"))))
|
||||
{
|
||||
apply(message, tx, document, agreement);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!ackInput && (sendAck || "1".equals(input.unb().value(9))))
|
||||
{
|
||||
acknowledge(input, agreement, interchange, findings, level);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Match qualifier, identifier, standard, usage and identity ownership exactly.
|
||||
*******************************************************************************/
|
||||
private QRecord route(EdifactInterchange input) throws Exception
|
||||
{
|
||||
for(var agreement : EdiRecords.query("edi_agreement", EdiRecords.equal("isActive", true).withCriteria("standard", QCriteriaOperator.EQUALS, "EDIFACT")
|
||||
.withCriteria("usageIndicator", QCriteriaOperator.EQUALS, input.usage())))
|
||||
{
|
||||
QRecord sender = OutboundWorker.row("edi_identity", agreement.getValue("partnerIdentityId"));
|
||||
QRecord receiver = OutboundWorker.row("edi_identity", agreement.getValue("localIdentityId"));
|
||||
if(Boolean.TRUE.equals(sender.getValueBoolean("isActive")) && Boolean.TRUE.equals(receiver.getValueBoolean("isActive"))
|
||||
&& "EDIFACT".equals(sender.getValueString("standard")) && "EDIFACT".equals(receiver.getValueString("standard"))
|
||||
&& Objects.equals(sender.getValue("partnerId"), agreement.getValue("partnerId")) && receiver.getValue("partnerId") == null
|
||||
&& input.unb().value(2).equals(sender.getValueString("interchangeId")) && input.unb().value(3).equals(receiver.getValueString("interchangeId"))
|
||||
&& input.unb().value(2, 1, 2).equals(Objects.requireNonNullElse(sender.getValueString("interchangeQualifier"), ""))
|
||||
&& input.unb().value(3, 1, 2).equals(Objects.requireNonNullElse(receiver.getValueString("interchangeQualifier"), "")))
|
||||
{
|
||||
return (agreement);
|
||||
}
|
||||
}
|
||||
return (null);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Preserve retries and serialize reference duplicate checks on the agreement.
|
||||
*******************************************************************************/
|
||||
private QRecord interchange(EdifactInterchange input, QRecord agreement, int position) throws Exception
|
||||
{
|
||||
var prior = EdiRecords.query("edi_interchange", EdiRecords.equal("inboxId", inbox.getValue("id")).withCriteria("inboundPosition", QCriteriaOperator.EQUALS, position));
|
||||
if(!prior.isEmpty())
|
||||
{
|
||||
QRecord existing = prior.getFirst();
|
||||
if(agreement != null && existing.getValue("agreementId") == null)
|
||||
{
|
||||
existing.withValue("agreementId", agreement.getValue("id")).withValue("partnerId", agreement.getValue("partnerId"));
|
||||
SystemWrites.update("edi_interchange", existing);
|
||||
}
|
||||
return (existing);
|
||||
}
|
||||
boolean duplicate = false;
|
||||
if(agreement != null)
|
||||
{
|
||||
SystemWrites.guard("UPDATE edi_agreement SET id=id WHERE id=?", agreement.getValue("id"));
|
||||
duplicate = EdiRuntime.current().duplicates().edifact(agreement.getValueInteger("id"), input, Objects.requireNonNullElse(agreement.getValueInteger("duplicateWindowDays"), 365), EdiRuntime.current().config().getClock().instant());
|
||||
SystemWrites.update("edi_inbox", new QRecord().withValue("id", inbox.getValue("id")).withValue("partnerId", agreement.getValue("partnerId")));
|
||||
}
|
||||
return (SystemWrites.insert("edi_interchange", new QRecord().withValue("inboxId", inbox.getValue("id")).withValue("inboundPosition", position).withValue("direction", "INBOUND")
|
||||
.withValue("agreementId", agreement == null ? null : agreement.getValue("id")).withValue("partnerId", agreement == null ? null : agreement.getValue("partnerId"))
|
||||
.withValue("standard", "EDIFACT").withValue("senderId", input.unb().value(2)).withValue("senderQualifier", input.unb().value(2, 1, 2))
|
||||
.withValue("receiverId", input.unb().value(3)).withValue("receiverQualifier", input.unb().value(3, 1, 2)).withValue("controlNumber", input.unb().value(5))
|
||||
.withValue("interchangeVersion", input.unb().value(1, 1, 2)).withValue("usageIndicator", input.usage()).withValue("groupCount", input.groups().size())
|
||||
.withValue("payloadRef", inbox.getValue("payloadRef")).withValue("payloadSha256", inbox.getValue("payloadSha256")).withValue("sizeBytes", inbox.getValue("sizeBytes"))
|
||||
.withValue("status", duplicate ? "DUPLICATE" : input.findings().isEmpty() ? "ACCEPTED" : "REJECTED")));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Match message type and directory independently for every UNH.
|
||||
*******************************************************************************/
|
||||
private QRecord document(QRecord agreement, EdifactMessage message) throws Exception
|
||||
{
|
||||
var rows = EdiRecords.query("edi_agreement_document", EdiRecords.equal("agreementId", agreement.getValue("id")).withCriteria("direction", QCriteriaOperator.EQUALS, "INBOUND")
|
||||
.withCriteria("transactionSet", QCriteriaOperator.EQUALS, message.type()).withCriteria("version", QCriteriaOperator.EQUALS, message.version()).withCriteria("isActive", QCriteriaOperator.EQUALS, true));
|
||||
return (rows.isEmpty() ? null : rows.getFirst());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Queue at most one CONTRL per received interchange, never acknowledge CONTRL.
|
||||
*******************************************************************************/
|
||||
private void acknowledge(EdifactInterchange input, QRecord agreement, QRecord received, Map<String, List<ValidationFinding>> findings, AckLevel level) throws Exception
|
||||
{
|
||||
InboxWorker.fenced(inbox, lease, () ->
|
||||
{
|
||||
if(EdiRecords.query("edi_outbound_message", EdiRecords.equal("ackForInterchangeId", received.getValue("id"))).isEmpty())
|
||||
{
|
||||
String version = input.allMessages().isEmpty() ? "D96A" : input.allMessages().getFirst().version();
|
||||
var message = new ContrlBuilder().build(input, findings, level, EdifactOutbound.allocate(agreement, "UNH"), version);
|
||||
EdifactOutbound.envelope(agreement, null, null, List.of(), List.of(message), null, received.getValueInteger("id"));
|
||||
EdiJournal.event(inbox.getValueInteger("id"), received.getValueInteger("id"), null, "ACK_QUEUED");
|
||||
}
|
||||
return (null);
|
||||
});
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Record a safe positional structural failure.
|
||||
*******************************************************************************/
|
||||
private ValidationFinding finding(String text)
|
||||
{
|
||||
return (new ValidationFinding(1, "UNH", null, null, null, 1, "3", null, "3", null, "ERROR", text, null));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Journal failures only while holding the current inbox claim.
|
||||
*******************************************************************************/
|
||||
private void problem(QRecord interchange, String type) throws Exception
|
||||
{
|
||||
InboxWorker.fenced(inbox, lease, () ->
|
||||
{
|
||||
EdiJournal.exception(inbox.getValueInteger("id"), interchange.getValueInteger("id"), null, interchange.getValueInteger("partnerId"), type, "EDIFACT processing requires attention: " + type);
|
||||
return (null);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import com.kingsrook.qbits.edi.x12.EnvelopeFinding;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
|
||||
/*******************************************************************************
|
||||
** EDIFACT service envelope with optional groups and ungrouped messages.
|
||||
*******************************************************************************/
|
||||
public record EdifactInterchange(EdifactSeparators separators, X12Segment unb, X12Segment unz,
|
||||
List<EdifactGroup> groups, List<EdifactMessage> messages, List<EnvelopeFinding> findings)
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Visit every message without inventing a functional group on the wire.
|
||||
*******************************************************************************/
|
||||
public List<EdifactMessage> allMessages()
|
||||
{
|
||||
return (Stream.concat(messages.stream(), groups.stream().flatMap(g -> g.messages().stream())).toList());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Normalize the UNB test flag to the agreement's usage vocabulary.
|
||||
*******************************************************************************/
|
||||
public String usage()
|
||||
{
|
||||
return ("1".equals(unb.value(11)) ? "T" : "P");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.edi.map.OutboundMapper;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** Reuse mapping transforms and associations with native UNH/UNT envelopes.
|
||||
*******************************************************************************/
|
||||
public class EdifactMapper extends OutboundMapper
|
||||
{
|
||||
private final char decimalMark;
|
||||
|
||||
/*******************************************************************************
|
||||
** Default numeric formatting follows the default UNA decimal mark.
|
||||
*******************************************************************************/
|
||||
public EdifactMapper()
|
||||
{
|
||||
this('.');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Use the agreement's decimal service character for outbound numeric transforms.
|
||||
*******************************************************************************/
|
||||
public EdifactMapper(char decimalMark)
|
||||
{
|
||||
this.decimalMark = decimalMark;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Render numeric values using the negotiated decimal mark.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected String decimal(String value)
|
||||
{
|
||||
return (value.replace('.', decimalMark));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Build one directory-specific message with an allocated UNH reference.
|
||||
*******************************************************************************/
|
||||
public EdifactMessage message(QRecord source, QRecord map, List<QRecord> rules, String reference) throws Exception
|
||||
{
|
||||
if(!"OUTBOUND".equals(map.getValueString("direction")))
|
||||
{
|
||||
throw new IllegalArgumentException("An outbound map is required");
|
||||
}
|
||||
String version = map.getValueString("version");
|
||||
List<X12Segment> segments = new ArrayList<>();
|
||||
segments.add(EdifactWriter.segment("UNH", 1, List.of(reference), List.of(map.getValueString("transactionSet"), version.substring(0, 1), version.substring(1), "UN")));
|
||||
emit(source, rules.stream().sorted(Comparator.comparing(r -> r.getValueInteger("sequence"))).toList(), "", "", 1, "", new int[] {0}, segments);
|
||||
segments.add(X12Segment.of("UNT", segments.size() + 1, String.valueOf(segments.size() + 1), reference));
|
||||
return (new EdifactMessage(segments.getFirst(), segments.getLast(), List.copyOf(segments), List.of()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.edi.document.EdiDocument;
|
||||
import com.kingsrook.qbits.edi.x12.EnvelopeFinding;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
|
||||
/*******************************************************************************
|
||||
** UNH/UNT message with its complete directory and association identification.
|
||||
*******************************************************************************/
|
||||
public record EdifactMessage(X12Segment header, X12Segment unt, List<X12Segment> segments, List<EnvelopeFinding> findings) implements EdiDocument
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Agreement document key, for example ORDERS.
|
||||
*******************************************************************************/
|
||||
public String type()
|
||||
{
|
||||
return (header.value(2));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Agreement directory key, for example D96A.
|
||||
*******************************************************************************/
|
||||
public String version()
|
||||
{
|
||||
return (header.value(2, 1, 2) + header.value(2, 1, 3));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Message reference, unique within its interchange.
|
||||
*******************************************************************************/
|
||||
public String reference()
|
||||
{
|
||||
return (header.value(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qbits.edi.EdiRuntime;
|
||||
import com.kingsrook.qbits.edi.flow.EdiJournal;
|
||||
import com.kingsrook.qbits.edi.flow.OutboundWorker;
|
||||
import com.kingsrook.qbits.edi.guide.RuleEvaluator;
|
||||
import com.kingsrook.qbits.edi.storage.PayloadKind;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** EDIFACT outbound envelopes and durable tracking through the existing send queue.
|
||||
*******************************************************************************/
|
||||
public class EdifactOutbound
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Allocate independent UNB/UNH sequences within the configured control scope.
|
||||
*******************************************************************************/
|
||||
public static String allocate(QRecord agreement, String level) throws Exception
|
||||
{
|
||||
boolean local = "LOCAL_IDENTITY".equals(agreement.getValueString("controlNumberScope"));
|
||||
return (String.valueOf(EdiRuntime.current().controls().allocate(local ? null : agreement.getValueInteger("id"),
|
||||
local ? agreement.getValueInteger("localIdentityId") : null, level, 1, 99999999999999L, true)));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Build an ungrouped interchange; UNZ counts messages when UNG is absent.
|
||||
*******************************************************************************/
|
||||
public static EdifactInterchange wrap(QRecord agreement, QRecord sender, QRecord recipient, List<EdifactMessage> messages, String control, LocalDateTime at)
|
||||
{
|
||||
String syntax = Objects.requireNonNullElse(agreement.getValueString("interchangeVersion"), "3");
|
||||
if(!List.of("3", "4").contains(syntax))
|
||||
{
|
||||
throw new IllegalArgumentException("EDIFACT syntax version must be 3 or 4");
|
||||
}
|
||||
X12Segment unb = EdifactWriter.segment("UNB", 1, List.of(Objects.requireNonNullElse(agreement.getValueString("syntaxIdentifier"), "UNOC"), syntax),
|
||||
List.of(sender.getValueString("interchangeId"), Objects.requireNonNullElse(sender.getValueString("interchangeQualifier"), "")),
|
||||
List.of(recipient.getValueString("interchangeId"), Objects.requireNonNullElse(recipient.getValueString("interchangeQualifier"), "")),
|
||||
List.of(at.format(DateTimeFormatter.ofPattern("4".equals(syntax) ? "yyyyMMdd" : "yyMMdd")), at.format(DateTimeFormatter.ofPattern("HHmm"))),
|
||||
List.of(control), List.of(""), List.of(""), List.of(""), List.of(""), List.of(""), List.of("T".equals(agreement.getValueString("usageIndicator")) ? "1" : ""));
|
||||
return (new EdifactInterchange(EdifactSeparators.fromAgreement(agreement), unb, X12Segment.of("UNZ", 0, String.valueOf(messages.size()), control), List.of(), List.copyOf(messages), List.of()));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Store bytes and record checkpoints atomically with outbound requests or ack fencing.
|
||||
** A logical tracking group carries ack deadlines even when UNG is absent on the wire.
|
||||
*******************************************************************************/
|
||||
public static QRecord envelope(QRecord agreement, QRecord document, QRecord map, List<QRecord> requests, List<EdifactMessage> messages, Integer original, Integer ackFor) throws Exception
|
||||
{
|
||||
var runtime = EdiRuntime.current();
|
||||
var interchange = wrap(agreement, OutboundWorker.row("edi_identity", agreement.getValue("localIdentityId")), OutboundWorker.row("edi_identity", agreement.getValue("partnerIdentityId")),
|
||||
messages, allocate(agreement, "UNB"), LocalDateTime.ofInstant(runtime.config().getClock().instant(), ZoneOffset.UTC));
|
||||
String ending = switch(Objects.requireNonNullElse(agreement.getValueString("lineEnding"), "NONE"))
|
||||
{
|
||||
case "LF" -> "\n";
|
||||
case "CRLF" -> "\r\n";
|
||||
default -> "";
|
||||
};
|
||||
var payload = runtime.payloads().put(PayloadKind.OUTBOUND, new ByteArrayInputStream(new EdifactWriter().write(interchange, ending).getBytes(StandardCharsets.UTF_8)));
|
||||
QRecord persisted = SystemWrites.insert("edi_interchange", new QRecord().withValue("direction", "OUTBOUND").withValue("partnerId", agreement.getValue("partnerId"))
|
||||
.withValue("agreementId", agreement.getValue("id")).withValue("transportId", agreement.getValue("outboundTransportId")).withValue("standard", "EDIFACT")
|
||||
.withValue("senderQualifier", interchange.unb().value(2, 1, 2)).withValue("senderId", interchange.unb().value(2))
|
||||
.withValue("receiverQualifier", interchange.unb().value(3, 1, 2)).withValue("receiverId", interchange.unb().value(3))
|
||||
.withValue("controlNumber", interchange.unb().value(5)).withValue("interchangeVersion", interchange.unb().value(1, 1, 2)).withValue("usageIndicator", interchange.usage())
|
||||
.withValue("interchangeDate", runtime.config().getClock().instant()).withValue("groupCount", 0).withValue("payloadRef", payload.value()).withValue("payloadSha256", payload.sha256())
|
||||
.withValue("sizeBytes", payload.sizeBytes()).withValue("status", "ENVELOPED").withValue("originalInterchangeId", original)
|
||||
.withValue("containsPhi", document != null && Boolean.TRUE.equals(document.getValueBoolean("containsPhi"))));
|
||||
boolean expects = document != null && "CONTRL".equals(document.getValueString("ackExpectation"));
|
||||
QRecord group = SystemWrites.insert("edi_functional_group", new QRecord().withValue("interchangeId", persisted.getValue("id")).withValue("senderId", interchange.unb().value(2))
|
||||
.withValue("controlNumber", interchange.unb().value(5)).withValue("version", messages.getFirst().version()).withValue("transactionCount", messages.size()).withValue("ackStatus", expects ? "PENDING" : "NONE"));
|
||||
for(int i = 0; i < messages.size(); i++)
|
||||
{
|
||||
var message = messages.get(i);
|
||||
String key = map == null || map.getValueString("businessKeyPath") == null ? null : new RuleEvaluator(message.segments(), Map.of()).values(map.getValueString("businessKeyPath")).stream().findFirst().orElse(null);
|
||||
SystemWrites.insert("edi_transaction", new QRecord().withValue("interchangeId", persisted.getValue("id")).withValue("functionalGroupId", group.getValue("id"))
|
||||
.withValue("direction", "OUTBOUND").withValue("partnerId", agreement.getValue("partnerId")).withValue("agreementDocumentId", document == null ? null : document.getValue("id"))
|
||||
.withValue("transactionSet", message.type()).withValue("version", message.version()).withValue("controlNumber", message.reference()).withValue("status", "QUEUED")
|
||||
.withValue("guideId", document == null ? null : document.getValue("guideId"))
|
||||
.withValue("guideRevision", document == null || document.getValue("guideId") == null ? null : OutboundWorker.row("edi_guide", document.getValue("guideId")).getValue("revision")).withValue("mapId", map == null ? null : map.getValue("id"))
|
||||
.withValue("mapRevision", map == null ? null : map.getValue("revision"))
|
||||
.withValue(document != null && Boolean.TRUE.equals(document.getValueBoolean("containsPhi")) ? "phiBusinessKey" : "businessKey", key)
|
||||
.withValue("businessKeyType", map == null ? null : map.getValue("businessKeyType"))
|
||||
.withValue("containsPhi", document != null && Boolean.TRUE.equals(document.getValueBoolean("containsPhi"))).withValue("segmentCount", message.segments().size())
|
||||
.withValue("targetTableName", i < requests.size() ? requests.get(i).getValue("sourceTableName") : null).withValue("targetRecordId", i < requests.size() ? requests.get(i).getValue("sourceRecordId") : null));
|
||||
if(i < requests.size())
|
||||
{
|
||||
SystemWrites.update("edi_outbound_request", new QRecord().withValue("id", requests.get(i).getValue("id")).withValue("status", "ENVELOPED"));
|
||||
}
|
||||
}
|
||||
QRecord queued = SystemWrites.insert("edi_outbound_message", new QRecord().withValue("interchangeId", persisted.getValue("id")).withValue("agreementId", agreement.getValue("id"))
|
||||
.withValue("partnerId", agreement.getValue("partnerId")).withValue("transportId", agreement.getValue("outboundTransportId")).withValue("status", "QUEUED").withValue("attempts", 0)
|
||||
.withValue("ackForInterchangeId", ackFor).withValue("payloadRef", payload.value()).withValue("payloadSha256", payload.sha256()).withValue("sizeBytes", payload.sizeBytes()));
|
||||
SystemWrites.update("edi_interchange", new QRecord().withValue("id", persisted.getValue("id")).withValue("outboundMessageId", queued.getValue("id")));
|
||||
EdiJournal.event(null, persisted.getValueInteger("id"), null, "ENVELOPED");
|
||||
return (queued);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Re-envelope archived outbound messages without reading changed source records.
|
||||
*******************************************************************************/
|
||||
public static void resendAsNew(QRecord original) throws Exception
|
||||
{
|
||||
QRecord agreement = OutboundWorker.row("edi_agreement", original.getValue("agreementId"));
|
||||
QRecord oldInterchange = OutboundWorker.row("edi_interchange", original.getValue("interchangeId"));
|
||||
EdifactInterchange archive;
|
||||
try(var stream = EdiRuntime.current().payloads().open(new com.kingsrook.qbits.edi.storage.PayloadRef(original.getValueString("payloadRef"), null, 0)))
|
||||
{
|
||||
archive = new EdifactEnvelopeReader(EdiRuntime.current().config().getMaxSegmentsPerTransaction()).read(stream).getFirst();
|
||||
}
|
||||
List<EdifactMessage> messages = new java.util.ArrayList<>();
|
||||
for(var message : archive.allMessages())
|
||||
{
|
||||
String reference = allocate(agreement, "UNH");
|
||||
var segments = new java.util.ArrayList<>(message.segments());
|
||||
var elements = new java.util.ArrayList<>(message.header().elements());
|
||||
elements.set(0, List.of(List.of(reference)));
|
||||
segments.set(0, new X12Segment("UNH", 1, elements));
|
||||
segments.set(segments.size() - 1, X12Segment.of("UNT", segments.size(), String.valueOf(segments.size()), reference));
|
||||
messages.add(new EdifactMessage(segments.getFirst(), segments.getLast(), List.copyOf(segments), List.of()));
|
||||
}
|
||||
var oldTransactions = com.kingsrook.qbits.edi.storage.EdiRecords.query("edi_transaction", com.kingsrook.qbits.edi.storage.EdiRecords.equal("interchangeId", oldInterchange.getValue("id")));
|
||||
QRecord first = oldTransactions.getFirst();
|
||||
QRecord document = first.getValue("agreementDocumentId") == null ? null : OutboundWorker.row("edi_agreement_document", first.getValue("agreementDocumentId"));
|
||||
QRecord queued = envelope(agreement, document, null, List.of(), messages, oldInterchange.getValueInteger("id"), null);
|
||||
var newTransactions = com.kingsrook.qbits.edi.storage.EdiRecords.query("edi_transaction", com.kingsrook.qbits.edi.storage.EdiRecords.equal("interchangeId", queued.getValue("interchangeId")));
|
||||
for(int i = 0; i < archive.allMessages().size(); i++)
|
||||
{
|
||||
String reference = archive.allMessages().get(i).reference();
|
||||
QRecord old = oldTransactions.stream().filter(t -> reference.equals(t.getValueString("controlNumber"))).findFirst().orElseThrow();
|
||||
QRecord update = new QRecord().withValue("id", newTransactions.get(i).getValue("id"));
|
||||
for(String field : List.of("guideId", "guideRevision", "mapId", "mapRevision", "businessKey", "phiBusinessKey", "containsPhi", "businessKeyType", "secondaryKey", "targetTableName", "targetRecordId", "agreementDocumentId"))
|
||||
{
|
||||
update.withValue(field, old.getValue(field));
|
||||
}
|
||||
SystemWrites.update("edi_transaction", update);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** UNA service characters; the fifth character is reserved in syntax version 3.
|
||||
*******************************************************************************/
|
||||
public record EdifactSeparators(char component, char element, char decimal, char release, char repetition, char segment)
|
||||
{
|
||||
public static final EdifactSeparators DEFAULT = new EdifactSeparators(':', '+', '.', '?', ' ', '\'');
|
||||
|
||||
/*******************************************************************************
|
||||
** Ambiguous service characters cannot be safely parsed or escaped.
|
||||
*******************************************************************************/
|
||||
public EdifactSeparators
|
||||
{
|
||||
var values = List.of(component, element, decimal, release, repetition, segment);
|
||||
if(!List.of('.', ',').contains(decimal) || new HashSet<>(values).size() != 6 || values.stream().anyMatch(c -> c > 127 || Character.isISOControl(c)))
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid EDIFACT service characters");
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Read exactly six UNA service characters.
|
||||
*******************************************************************************/
|
||||
public static EdifactSeparators fromUna(String una)
|
||||
{
|
||||
if(una.length() != 9 || !una.startsWith("UNA"))
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid UNA");
|
||||
}
|
||||
return (new EdifactSeparators(una.charAt(3), una.charAt(4), una.charAt(5), una.charAt(6), una.charAt(7), una.charAt(8)));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Use the agreement's EDIFACT service characters.
|
||||
*******************************************************************************/
|
||||
public static EdifactSeparators fromAgreement(QRecord agreement)
|
||||
{
|
||||
return (new EdifactSeparators(character(agreement, "componentSeparator", ":"), character(agreement, "elementSeparator", "+"),
|
||||
character(agreement, "decimalMark", "."), character(agreement, "releaseCharacter", "?"),
|
||||
"4".equals(agreement.getValueString("interchangeVersion")) ? character(agreement, "repetitionSeparator", "*") : ' ', character(agreement, "segmentTerminator", "'")));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** A separator setting must contain exactly one character.
|
||||
*******************************************************************************/
|
||||
private static char character(QRecord agreement, String field, String fallback)
|
||||
{
|
||||
String value = Objects.requireNonNullElse(agreement.getValueString(field), fallback);
|
||||
if(value.length() != 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid service character setting");
|
||||
}
|
||||
return (value.charAt(0));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Render service string advice, including its segment terminator.
|
||||
*******************************************************************************/
|
||||
public String una()
|
||||
{
|
||||
return ("UNA" + component + element + decimal + release + repetition + segment);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi.edifact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
|
||||
/*******************************************************************************
|
||||
** In-memory EDIFACT serialization; escape values, never composite structure.
|
||||
*******************************************************************************/
|
||||
public class EdifactWriter
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Render a full interchange with explicit UNA advice.
|
||||
*******************************************************************************/
|
||||
public String write(EdifactInterchange interchange)
|
||||
{
|
||||
return (write(interchange, ""));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Retain optional groups and caller-selected line endings.
|
||||
*******************************************************************************/
|
||||
public String write(EdifactInterchange interchange, String ending)
|
||||
{
|
||||
List<X12Segment> segments = new ArrayList<>();
|
||||
segments.add(interchange.unb());
|
||||
interchange.messages().forEach(m -> segments.addAll(m.segments()));
|
||||
for(var group : interchange.groups())
|
||||
{
|
||||
segments.add(group.ung());
|
||||
group.messages().forEach(m -> segments.addAll(m.segments()));
|
||||
segments.add(group.une());
|
||||
}
|
||||
segments.add(interchange.unz());
|
||||
return (interchange.separators().una() + ending + write(segments, interchange.separators(), ending));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Serialize component and repetition lists without losing released delimiters.
|
||||
*******************************************************************************/
|
||||
public String write(List<X12Segment> segments, EdifactSeparators separators, String ending)
|
||||
{
|
||||
if(!List.of("", "\n", "\r\n").contains(ending))
|
||||
{
|
||||
throw new IllegalArgumentException("Unsupported line ending");
|
||||
}
|
||||
StringBuilder out = new StringBuilder();
|
||||
for(var segment : segments)
|
||||
{
|
||||
out.append(segment.id());
|
||||
for(var element : segment.elements())
|
||||
{
|
||||
out.append(separators.element());
|
||||
for(int r = 0; r < element.size(); r++)
|
||||
{
|
||||
if(r > 0)
|
||||
{
|
||||
if(separators.repetition() == ' ')
|
||||
{
|
||||
throw new IllegalArgumentException("Repetition requires syntax version 4");
|
||||
}
|
||||
out.append(separators.repetition());
|
||||
}
|
||||
for(int c = 0; c < element.get(r).size(); c++)
|
||||
{
|
||||
if(c > 0)
|
||||
{
|
||||
out.append(separators.component());
|
||||
}
|
||||
for(char value : element.get(r).get(c).toCharArray())
|
||||
{
|
||||
if(value == separators.release() || value == separators.element() || value == separators.component()
|
||||
|| value == separators.segment() || separators.repetition() != ' ' && value == separators.repetition())
|
||||
{
|
||||
out.append(separators.release());
|
||||
}
|
||||
out.append(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.append(separators.segment()).append(ending);
|
||||
}
|
||||
return (out.toString());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Construct a segment from explicit component lists, with no delimiter parsing.
|
||||
*******************************************************************************/
|
||||
@SafeVarargs
|
||||
public static X12Segment segment(String tag, int position, List<String>... elements)
|
||||
{
|
||||
return (new X12Segment(tag, position, java.util.Arrays.stream(elements).map(List::of).toList()));
|
||||
}
|
||||
}
|
||||
@@ -315,7 +315,7 @@ public class InboundProcessor
|
||||
/*******************************************************************************
|
||||
** Replays select original persisted definitions or the currently active revision.
|
||||
*******************************************************************************/
|
||||
private QRecord revisions(QRecord document, QRecord transaction) throws Exception
|
||||
protected QRecord revisions(QRecord document, QRecord transaction) throws Exception
|
||||
{
|
||||
if(document == null)
|
||||
{
|
||||
@@ -383,7 +383,7 @@ public class InboundProcessor
|
||||
/*******************************************************************************
|
||||
** Persist findings without repeatedly adding error rows on storage retries.
|
||||
*******************************************************************************/
|
||||
private void validation(QRecord tx, List<ValidationFinding> findings, QRecord document) throws Exception
|
||||
protected void validation(QRecord tx, List<ValidationFinding> findings, QRecord document) throws Exception
|
||||
{
|
||||
boolean rejected = findings.stream().anyMatch(f -> "ERROR".equals(f.severity()) && f.snipLevel() <= 2);
|
||||
if(!"APPLIED".equals(tx.getValueString("status")) || Boolean.TRUE.equals(inbox.getValueBoolean("reprocess")))
|
||||
@@ -421,7 +421,7 @@ public class InboundProcessor
|
||||
/*******************************************************************************
|
||||
** A failed target write rolls back only this set, then records an APPLY exception.
|
||||
*******************************************************************************/
|
||||
private void apply(X12TransactionSet x12, QRecord tx, QRecord document, QRecord agreement) throws Exception
|
||||
protected void apply(com.kingsrook.qbits.edi.document.EdiDocument x12, QRecord tx, QRecord document, QRecord agreement) throws Exception
|
||||
{
|
||||
if("APPLIED".equals(tx.getValueString("status")) && !Boolean.TRUE.equals(inbox.getValueBoolean("reprocess")))
|
||||
{
|
||||
|
||||
@@ -88,11 +88,22 @@ public class InboxWorker
|
||||
}
|
||||
try(InputStream stream = store.open(new PayloadRef(row.getValueString("payloadRef"), row.getValueString("payloadSha256"), row.getValueLong("sizeBytes"))))
|
||||
{
|
||||
var interchanges = EdiRuntime.current().reader().read(stream);
|
||||
var framing = new com.kingsrook.qbits.edi.document.EnvelopeReader();
|
||||
byte[] envelope;
|
||||
int position = 0;
|
||||
for(var interchange : interchanges)
|
||||
while((envelope = framing.next(stream)) != null)
|
||||
{
|
||||
new InboundProcessor(row, lease, store).process(interchange, ++position);
|
||||
position++;
|
||||
if(envelope[0] == 'U')
|
||||
{
|
||||
var parsed = new com.kingsrook.qbits.edi.edifact.EdifactEnvelopeReader(EdiRuntime.current().config().getMaxSegmentsPerTransaction()).read(new java.io.ByteArrayInputStream(envelope)).getFirst();
|
||||
new com.kingsrook.qbits.edi.edifact.EdifactInbound(row, lease, store).process(parsed, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
var parsed = EdiRuntime.current().reader().read(new java.io.ByteArrayInputStream(envelope)).getFirst();
|
||||
new InboundProcessor(row, lease, store).process(parsed, position);
|
||||
}
|
||||
}
|
||||
fenced(row, lease, () ->
|
||||
{
|
||||
|
||||
@@ -73,6 +73,11 @@ public final class OutboundActions
|
||||
*******************************************************************************/
|
||||
private static void reEnvelope(QRecord message) throws Exception
|
||||
{
|
||||
if("EDIFACT".equals(OutboundWorker.row("edi_agreement", message.getValue("agreementId")).getValueString("standard")))
|
||||
{
|
||||
com.kingsrook.qbits.edi.edifact.EdifactOutbound.resendAsNew(message);
|
||||
return;
|
||||
}
|
||||
var runtime = EdiRuntime.current();
|
||||
QRecord originalRow = OutboundWorker.row("edi_interchange", message.getValue("interchangeId"));
|
||||
QRecord agreement = OutboundWorker.row("edi_agreement", message.getValue("agreementId"));
|
||||
|
||||
@@ -93,7 +93,7 @@ public class OutboundWorker
|
||||
return (0);
|
||||
}
|
||||
List<QRecord> accepted = new ArrayList<>();
|
||||
List<X12TransactionSet> sets = new ArrayList<>();
|
||||
List<com.kingsrook.qbits.edi.document.EdiDocument> sets = new ArrayList<>();
|
||||
QRecord map = row("edi_map", document.getValueInteger("mapId"));
|
||||
var rules = EdiRecords.query("edi_map_rule", EdiRecords.equal("mapId", map.getValue("id")));
|
||||
var associations = new java.util.LinkedHashSet<String>();
|
||||
@@ -117,7 +117,7 @@ public class OutboundWorker
|
||||
}
|
||||
try(var logging = com.kingsrook.qbits.edi.security.PhiLogScope.open(Boolean.TRUE.equals(document.getValueBoolean("containsPhi"))))
|
||||
{
|
||||
X12TransactionSet set = InboundApplier.asPartner(EdiRuntime.current().config().getServiceSessionSupplier(), agreement.getValueInteger("partnerId"), () ->
|
||||
com.kingsrook.qbits.edi.document.EdiDocument set = InboundApplier.asPartner(EdiRuntime.current().config().getServiceSessionSupplier(), agreement.getValueInteger("partnerId"), () ->
|
||||
{
|
||||
String table = request.getValueString("sourceTableName");
|
||||
String primaryKey = QContext.getQInstance().getTable(table).getPrimaryKeyField();
|
||||
@@ -129,7 +129,9 @@ public class OutboundWorker
|
||||
{
|
||||
throw new IllegalArgumentException("Source record or active map unavailable");
|
||||
}
|
||||
return (new OutboundMapper().build(records.getFirst(), map, rules, String.format("%04d", sets.size() + 1)));
|
||||
return ("EDIFACT".equals(agreement.getValueString("standard"))
|
||||
? new com.kingsrook.qbits.edi.edifact.EdifactMapper(com.kingsrook.qbits.edi.edifact.EdifactSeparators.fromAgreement(agreement).decimal()).message(records.getFirst(), map, rules, com.kingsrook.qbits.edi.edifact.EdifactOutbound.allocate(agreement, "UNH"))
|
||||
: new OutboundMapper().build(records.getFirst(), map, rules, String.format("%04d", sets.size() + 1)));
|
||||
});
|
||||
if(EdiRuntime.current().validator().validate(set, guide, Boolean.TRUE.equals(document.getValueBoolean("containsPhi"))).stream().anyMatch(f -> "ERROR".equals(f.severity())))
|
||||
{
|
||||
@@ -170,8 +172,13 @@ public class OutboundWorker
|
||||
/*******************************************************************************
|
||||
** Allocate controls once, store exact bytes, then persist all tracking records.
|
||||
*******************************************************************************/
|
||||
public static QRecord envelope(QRecord agreement, QRecord document, QRecord map, List<QRecord> requests, List<X12TransactionSet> sets, Integer original) throws Exception
|
||||
public static QRecord envelope(QRecord agreement, QRecord document, QRecord map, List<QRecord> requests, List<? extends com.kingsrook.qbits.edi.document.EdiDocument> sets, Integer original) throws Exception
|
||||
{
|
||||
if("EDIFACT".equals(agreement.getValueString("standard")))
|
||||
{
|
||||
return (com.kingsrook.qbits.edi.edifact.EdifactOutbound.envelope(agreement, document, map, requests,
|
||||
sets.stream().map(s -> (com.kingsrook.qbits.edi.edifact.EdifactMessage) s).toList(), original, null));
|
||||
}
|
||||
var runtime = EdiRuntime.current();
|
||||
QRecord local = row("edi_identity", agreement.getValueInteger("localIdentityId"));
|
||||
QRecord partner = row("edi_identity", agreement.getValueInteger("partnerIdentityId"));
|
||||
@@ -187,7 +194,7 @@ public class OutboundWorker
|
||||
X12Segment gs = X12Segment.of("GS", 2, document.getValueString("functionalIdCode"), local.getValueString("applicationCode"), partner.getValueString("applicationCode"), at.format(DateTimeFormatter.BASIC_ISO_DATE),
|
||||
at.format(DateTimeFormatter.ofPattern("HHmm")), gsControl, "X", document.getValueString("version"));
|
||||
var interchange = new X12Interchange(new X12Separators(value(agreement, "elementSeparator", "*").charAt(0), value(agreement, "segmentTerminator", "~").charAt(0), component.charAt(0), version.compareTo("00501") >= 0 ? repetition.charAt(0) : null),
|
||||
isa, X12Segment.of("IEA", 0, "1", isaControl), List.of(new X12FunctionalGroup(gs, X12Segment.of("GE", 0, String.valueOf(sets.size()), gsControl), List.copyOf(sets), List.of())), List.of(), List.of());
|
||||
isa, X12Segment.of("IEA", 0, "1", isaControl), List.of(new X12FunctionalGroup(gs, X12Segment.of("GE", 0, String.valueOf(sets.size()), gsControl), sets.stream().map(s -> (X12TransactionSet) s).toList(), List.of())), List.of(), List.of());
|
||||
String ending = switch(value(agreement, "lineEnding", "NONE"))
|
||||
{
|
||||
case "LF" -> "\n";
|
||||
@@ -211,7 +218,7 @@ public class OutboundWorker
|
||||
String businessKey = map.getValueString("businessKeyPath") == null ? null : new com.kingsrook.qbits.edi.guide.RuleEvaluator(set.segments(), java.util.Map.of()).values(map.getValueString("businessKeyPath")).stream().findFirst().orElse(null);
|
||||
SystemWrites.insert("edi_transaction", new QRecord().withValue("interchangeId", persisted.getValue("id")).withValue("functionalGroupId", group.getValue("id"))
|
||||
.withValue("direction", "OUTBOUND").withValue("partnerId", agreement.getValue("partnerId")).withValue("agreementDocumentId", document.getValue("id"))
|
||||
.withValue("transactionSet", set.st().value(1)).withValue("version", gs.value(8)).withValue("controlNumber", set.st().value(2)).withValue("status", "QUEUED")
|
||||
.withValue("transactionSet", set.header().value(1)).withValue("version", gs.value(8)).withValue("controlNumber", set.header().value(2)).withValue("status", "QUEUED")
|
||||
.withValue("guideId", document.getValue("guideId")).withValue("guideRevision", document.getValue("guideId") == null ? null : row("edi_guide", document.getValue("guideId")).getValue("revision"))
|
||||
.withValue("targetTableName", i < requests.size() ? requests.get(i).getValue("sourceTableName") : null).withValue("targetRecordId", i < requests.size() ? requests.get(i).getValue("sourceRecordId") : null).withValue("mapId", map.getValue("id")).withValue("mapRevision", map.getValue("revision"))
|
||||
.withValue("businessKeyType", map.getValue("businessKeyType")).withValue(Boolean.TRUE.equals(document.getValueBoolean("containsPhi")) ? "phiBusinessKey" : "businessKey", businessKey)
|
||||
|
||||
@@ -37,7 +37,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
*******************************************************************************/
|
||||
public class BuiltInGuideLoader
|
||||
{
|
||||
public static final List<String> RESOURCES = List.of("850-004010", "850-005010", "997-004010", "997-005010", "999-005010X231A1", "855-004010", "855-005010", "810-004010", "810-005010", "846-004010", "846-005010", "860-004010", "860-005010", "856-004010", "856-005010", "940-004010", "940-005010", "945-004010", "945-005010", "214-004010", "214-005010", "204-004010", "204-005010", "837P-005010X222A1", "837I-005010X223A2", "835-005010X221A1", "270-005010X279A1", "271-005010X279A1", "276-005010X212", "277-005010X212", "834-005010X220A1");
|
||||
public static final List<String> RESOURCES = List.of("850-004010", "850-005010", "997-004010", "997-005010", "999-005010X231A1", "855-004010", "855-005010", "810-004010", "810-005010", "846-004010", "846-005010", "860-004010", "860-005010", "856-004010", "856-005010", "940-004010", "940-005010", "945-004010", "945-005010", "214-004010", "214-005010", "204-004010", "204-005010", "ORDERS-D96A", "ORDRSP-D96A", "DESADV-D96A", "INVOIC-D96A", "CONTRL-D96A", "ORDERS-D01B", "ORDRSP-D01B", "DESADV-D01B", "INVOIC-D01B", "CONTRL-D01B", "837P-005010X222A1", "837I-005010X223A2", "835-005010X221A1", "270-005010X279A1", "271-005010X279A1", "276-005010X212", "277-005010X212", "834-005010X220A1");
|
||||
|
||||
/*******************************************************************************
|
||||
** Read the documented JSON format in memory.
|
||||
|
||||
@@ -25,8 +25,8 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.edi.document.EdiDocument;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -55,9 +55,9 @@ public class GuideValidator
|
||||
/*******************************************************************************
|
||||
** Validate one transaction; diagnostic text never contains submitted values.
|
||||
*******************************************************************************/
|
||||
public List<ValidationFinding> validate(X12TransactionSet transaction, Guide guide, boolean containsPhi)
|
||||
public List<ValidationFinding> validate(EdiDocument transaction, Guide guide, boolean containsPhi)
|
||||
{
|
||||
State state = new State(transaction.segments(), containsPhi);
|
||||
State state = new State(transaction.segments(), containsPhi, "EDIFACT".equals(guide.standard()));
|
||||
List<Node> roots = tree(guide);
|
||||
consume(roots, state, false);
|
||||
while(state.index < state.segments.size())
|
||||
@@ -86,7 +86,7 @@ public class GuideValidator
|
||||
}
|
||||
for(GuideRule rule : list(guide.rules()))
|
||||
{
|
||||
rule(rule, transaction.st(), null, evaluator, state);
|
||||
rule(rule, transaction.header(), null, evaluator, state);
|
||||
}
|
||||
for(Binding binding : state.bindings)
|
||||
{
|
||||
@@ -109,9 +109,9 @@ public class GuideValidator
|
||||
/*******************************************************************************
|
||||
** Resolve guide-defined loop occurrences for the mapper using the validator grammar.
|
||||
*******************************************************************************/
|
||||
public Map<String, List<List<X12Segment>>> loops(X12TransactionSet transaction, Guide guide)
|
||||
public Map<String, List<List<X12Segment>>> loops(EdiDocument transaction, Guide guide)
|
||||
{
|
||||
State state = new State(transaction.segments(), true);
|
||||
State state = new State(transaction.segments(), true, "EDIFACT".equals(guide.standard()));
|
||||
List<Node> roots = tree(guide);
|
||||
consume(roots, state, false);
|
||||
return (state.loops);
|
||||
@@ -271,7 +271,7 @@ public class GuideValidator
|
||||
{
|
||||
state.add(segment, definition.loopId(), element, "8", "5", "Element too long", value);
|
||||
}
|
||||
if(!validType(element.dataType(), value))
|
||||
if(!validType(element.dataType(), state.edifact && "R".equals(element.dataType()) ? value.replace(',', '.') : value))
|
||||
{
|
||||
String code = "DT".equals(element.dataType()) ? "8" : "TM".equals(element.dataType()) ? "9" : "6";
|
||||
state.add(segment, definition.loopId(), element, "8", code, "Invalid element data type", value);
|
||||
@@ -453,6 +453,7 @@ public class GuideValidator
|
||||
{
|
||||
private final List<X12Segment> segments;
|
||||
private final boolean phi;
|
||||
private final boolean edifact;
|
||||
private final List<ValidationFinding> findings = new ArrayList<>();
|
||||
private final List<Binding> bindings = new ArrayList<>();
|
||||
private final Map<String, List<List<X12Segment>>> loops = new LinkedHashMap<>();
|
||||
@@ -461,10 +462,11 @@ public class GuideValidator
|
||||
/*******************************************************************************
|
||||
** Initialize a transaction validation.
|
||||
*******************************************************************************/
|
||||
private State(List<X12Segment> segments, boolean phi)
|
||||
private State(List<X12Segment> segments, boolean phi, boolean edifact)
|
||||
{
|
||||
this.segments = segments;
|
||||
this.phi = phi;
|
||||
this.edifact = edifact;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
@@ -28,8 +28,8 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.edi.document.EdiDocument;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -40,7 +40,7 @@ public class HealthcareValidation
|
||||
/*******************************************************************************
|
||||
** Exact decimal arithmetic; every diagnostic contains only positions and codes.
|
||||
*******************************************************************************/
|
||||
public List<ValidationFinding> validate(X12TransactionSet transaction, Guide guide, Map<String, List<List<X12Segment>>> loops)
|
||||
public List<ValidationFinding> validate(EdiDocument transaction, Guide guide, Map<String, List<List<X12Segment>>> loops)
|
||||
{
|
||||
List<ValidationFinding> findings = new ArrayList<>();
|
||||
List<X12Segment> segments = transaction.segments();
|
||||
|
||||
@@ -189,7 +189,7 @@ public class RuleEvaluator
|
||||
String name = selector.group(1);
|
||||
String qualifier = selector.group(2);
|
||||
Matcher element = Pattern.compile("([A-Z][A-Z0-9]{1,2}?)([0-9]{2})(?:-([0-9]+))?").matcher(name);
|
||||
if(element.matches())
|
||||
if(element.matches() && !loops.containsKey(name))
|
||||
{
|
||||
List<X12Segment> selected = select(scope, element.group(1), qualifier);
|
||||
int position = Integer.parseInt(element.group(2));
|
||||
|
||||
@@ -25,12 +25,12 @@ import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.edi.document.EdiDocument;
|
||||
import com.kingsrook.qbits.edi.guide.Guide;
|
||||
import com.kingsrook.qbits.edi.guide.GuideValidator;
|
||||
import com.kingsrook.qbits.edi.guide.RuleEvaluator;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -58,7 +58,7 @@ public class InboundMapper
|
||||
/*******************************************************************************
|
||||
** Load ordered rules for a persisted map.
|
||||
*******************************************************************************/
|
||||
public Result map(X12TransactionSet transaction, Guide guide, QRecord map) throws Exception
|
||||
public Result map(EdiDocument transaction, Guide guide, QRecord map) throws Exception
|
||||
{
|
||||
List<QRecord> rules = new ArrayList<>(EdiRecords.query("edi_map_rule", EdiRecords.equal("mapId", map.getValue("id"))));
|
||||
rules.sort(Comparator.comparing(r -> r.getValueInteger("sequence")));
|
||||
@@ -68,7 +68,7 @@ public class InboundMapper
|
||||
/*******************************************************************************
|
||||
** Map in memory; wildcard rules sharing an association use the same loop scope.
|
||||
*******************************************************************************/
|
||||
public Result map(X12TransactionSet transaction, Guide guide, QRecord map, List<QRecord> rules) throws Exception
|
||||
public Result map(EdiDocument transaction, Guide guide, QRecord map, List<QRecord> rules) throws Exception
|
||||
{
|
||||
Map<String, List<List<X12Segment>>> loops = new GuideValidator().loops(transaction, guide);
|
||||
RuleEvaluator evaluator = new RuleEvaluator(transaction.segments(), loops);
|
||||
@@ -86,7 +86,7 @@ public class InboundMapper
|
||||
}
|
||||
if(fields.length == 1)
|
||||
{
|
||||
apply(rule, evaluator, result, fields[0], path);
|
||||
apply(rule, evaluator, result, fields[0], path, "EDIFACT".equals(guide.standard()));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -117,7 +117,7 @@ public class InboundMapper
|
||||
}
|
||||
for(int i = 0; i < occurrences.size(); i++)
|
||||
{
|
||||
apply(rule, new RuleEvaluator(occurrences.get(i), loops), children.get(i), fields[1], relative);
|
||||
apply(rule, new RuleEvaluator(occurrences.get(i), loops), children.get(i), fields[1], relative, "EDIFACT".equals(guide.standard()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ public class InboundMapper
|
||||
/*******************************************************************************
|
||||
** Conditions and defaults are evaluated within the current loop occurrence.
|
||||
*******************************************************************************/
|
||||
private void apply(QRecord rule, RuleEvaluator evaluator, QRecord target, String field, String path) throws Exception
|
||||
private void apply(QRecord rule, RuleEvaluator evaluator, QRecord target, String field, String path, boolean edifact) throws Exception
|
||||
{
|
||||
String condition = rule.getValueString("condition");
|
||||
if(condition != null && !condition.isBlank() && !evaluator.test(condition))
|
||||
@@ -144,6 +144,10 @@ public class InboundMapper
|
||||
{
|
||||
values = List.of(rule.getValueString("defaultValue"));
|
||||
}
|
||||
if(edifact && List.of("DECIMAL", "UNIT").contains(java.util.Objects.requireNonNullElse(rule.getValueString("transform"), "")))
|
||||
{
|
||||
values = values.stream().map(v -> v.replace(',', '.')).toList();
|
||||
}
|
||||
Serializable value = transforms.apply(rule.getValueString("transform"), rule.getValueString("transformArgs"), values);
|
||||
if(Boolean.TRUE.equals(rule.getValueBoolean("required")) && (value == null || value.toString().isBlank()))
|
||||
{
|
||||
|
||||
@@ -53,6 +53,10 @@ public class MapPreview
|
||||
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
|
||||
try(var logging = com.kingsrook.qbits.edi.security.PhiLogScope.open(phi); InputStream stream = runtime.payloads().open(new PayloadRef(sample.getValueString("payloadRef"), null, 0)))
|
||||
{
|
||||
if("EDIFACT".equals(map.getValueString("standard")))
|
||||
{
|
||||
return (edifact(map, sample, stream, phi));
|
||||
}
|
||||
var interchanges = runtime.reader().read(stream);
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, interchanges.size(), "interchanges parsed"));
|
||||
for(var interchange : interchanges)
|
||||
@@ -115,6 +119,47 @@ public class MapPreview
|
||||
return (lines);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Preview native EDIFACT messages and CONTRL without applying target records.
|
||||
*******************************************************************************/
|
||||
private ArrayList<ProcessSummaryLineInterface> edifact(QRecord map, QRecord sample, InputStream stream, boolean phi) throws Exception
|
||||
{
|
||||
var runtime = EdiRuntime.current();
|
||||
var guide = new GuideRepository().load(map.getValueInteger("guideId"));
|
||||
var rules = EdiRecords.query("edi_map_rule", EdiRecords.equal("mapId", map.getValue("id")));
|
||||
rules.sort(java.util.Comparator.comparing(r -> r.getValueInteger("sequence")));
|
||||
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
|
||||
for(var interchange : new com.kingsrook.qbits.edi.edifact.EdifactEnvelopeReader(runtime.config().getMaxSegmentsPerTransaction()).read(stream))
|
||||
{
|
||||
Map<String, List<com.kingsrook.qbits.edi.guide.ValidationFinding>> findings = new java.util.LinkedHashMap<>();
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, interchange.findings().size(), "envelope findings"));
|
||||
for(var message : interchange.allMessages())
|
||||
{
|
||||
var result = runtime.validator().validate(message, guide, phi);
|
||||
findings.put(message.reference(), result);
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, result.size(), "validation findings"));
|
||||
var mapped = InboundApplier.asService(runtime.config().getServiceSessionSupplier(), () -> new InboundMapper(runtime.transforms()).map(message, guide, map, rules));
|
||||
if(phi)
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, "PHI map result suppressed; no target records written"));
|
||||
continue;
|
||||
}
|
||||
String actual = resultJson(mapped.record());
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, actual));
|
||||
if(sample.getValueString("expectedResultJson") != null)
|
||||
{
|
||||
diff(lines, "Expected result", new ObjectMapper().readTree(sample.getValueString("expectedResultJson")), new ObjectMapper().readTree(actual));
|
||||
}
|
||||
}
|
||||
if(interchange.unb() != null && !phi)
|
||||
{
|
||||
var ack = new com.kingsrook.qbits.edi.edifact.ContrlBuilder().build(interchange, findings, AckLevel.ALL_TRANSACTIONS, "1", map.getValueString("version"));
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, new com.kingsrook.qbits.edi.edifact.EdifactWriter().write(ack.segments(), interchange.separators(), "")));
|
||||
}
|
||||
}
|
||||
return (lines);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Stable record JSON excludes QRecord diagnostics and implementation metadata.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -75,7 +75,7 @@ public class OutboundMapper
|
||||
/*******************************************************************************
|
||||
** Walk each loop once per associated record, retaining the nearest HL parent.
|
||||
*******************************************************************************/
|
||||
private void emit(QRecord source, List<QRecord> rules, String prefix, String qPrefix, int sequence, String parent,
|
||||
protected void emit(QRecord source, List<QRecord> rules, String prefix, String qPrefix, int sequence, String parent,
|
||||
int[] hierarchy, List<X12Segment> output) throws Exception
|
||||
{
|
||||
Map<String, List<QRecord>> nodes = new LinkedHashMap<>();
|
||||
@@ -112,13 +112,13 @@ public class OutboundMapper
|
||||
}
|
||||
continue;
|
||||
}
|
||||
List<String> elements = new ArrayList<>();
|
||||
List<List<String>> elements = new ArrayList<>();
|
||||
String segmentId = null;
|
||||
for(QRecord rule : node.getValue())
|
||||
{
|
||||
String path = rule.getValueString("ediPath");
|
||||
var match = ELEMENT.matcher(path.substring(path.lastIndexOf('/') + 1));
|
||||
if(!match.matches() || match.group(3) != null)
|
||||
if(!match.matches())
|
||||
{
|
||||
throw new IllegalArgumentException("Unsupported outbound element path");
|
||||
}
|
||||
@@ -130,7 +130,7 @@ public class OutboundMapper
|
||||
}
|
||||
while(elements.size() < position)
|
||||
{
|
||||
elements.add("");
|
||||
elements.add(new ArrayList<>());
|
||||
}
|
||||
String condition = rule.getValueString("condition");
|
||||
if(condition != null && !condition.isBlank() && !new RuleEvaluator(output, Map.of()).test(condition))
|
||||
@@ -172,7 +172,7 @@ public class OutboundMapper
|
||||
}
|
||||
else if(transform.equals("DECIMAL"))
|
||||
{
|
||||
value = new java.math.BigDecimal(raw.toString()).movePointRight(Integer.parseInt(args.getOrDefault("scale", "0"))).stripTrailingZeros().toPlainString();
|
||||
value = decimal(new java.math.BigDecimal(raw.toString()).movePointRight(Integer.parseInt(args.getOrDefault("scale", "0"))).stripTrailingZeros().toPlainString());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,16 +183,35 @@ public class OutboundMapper
|
||||
{
|
||||
throw new IllegalArgumentException("Required outbound field is missing");
|
||||
}
|
||||
elements.set(position - 1, Objects.requireNonNullElse(value, ""));
|
||||
int component = match.group(3) == null ? 1 : Integer.parseInt(match.group(3));
|
||||
if(component < 1 || component > 99)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid outbound component position");
|
||||
}
|
||||
var components = elements.get(position - 1);
|
||||
while(components.size() < component)
|
||||
{
|
||||
components.add("");
|
||||
}
|
||||
components.set(component - 1, Objects.requireNonNullElse(value, ""));
|
||||
}
|
||||
if(elements.stream().anyMatch(v -> !v.isEmpty()))
|
||||
if(elements.stream().flatMap(List::stream).anyMatch(v -> !v.isEmpty()))
|
||||
{
|
||||
output.add(X12Segment.of(segmentId, output.size() + 1, elements.toArray(String[]::new)));
|
||||
output.add(new X12Segment(segmentId, output.size() + 1, elements.stream().map(e -> List.of(e.isEmpty() ? List.of("") : e)).toList()));
|
||||
if("HL".equals(segmentId))
|
||||
{
|
||||
currentParent = elements.getFirst();
|
||||
currentParent = elements.getFirst().getFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** X12 uses a period; EDIFACT can override the numeric service character.
|
||||
*******************************************************************************/
|
||||
protected String decimal(String value)
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -281,11 +281,11 @@ public final class EdiTables
|
||||
sources.add(enumSource("edi_on_duplicate", "REJECT_TA1", "IGNORE", "PROCESS"));
|
||||
sources.add(enumSource("edi_direction", "INBOUND", "OUTBOUND"));
|
||||
sources.add(enumSource("edi_transport_direction", "INBOUND", "OUTBOUND", "BOTH"));
|
||||
sources.add(enumSource("edi_ack_expectation", "NONE", "997", "999"));
|
||||
sources.add(enumSource("edi_ack_expectation", "NONE", "997", "999", "CONTRL"));
|
||||
sources.add(enumSource("edi_ack_level", "GROUP_ONLY", "ALL_TRANSACTIONS", "ERRORS_ONLY"));
|
||||
sources.add(enumSource("edi_batch_mode", "IMMEDIATE", "BATCH"));
|
||||
sources.add(enumSource("edi_outbound_trigger", "ESB_EVENT", "RECORD_CHANGE", "MANUAL"));
|
||||
sources.add(enumSource("edi_level", "ISA", "GS"));
|
||||
sources.add(enumSource("edi_level", "ISA", "GS", "UNB", "UNG", "UNH"));
|
||||
sources.add(enumSource("edi_transport_type", "AS2", "SFTP", "GATEWAY", "HTTPS", "EMAIL"));
|
||||
sources.add(enumSource("edi_sign_algorithm", "SHA256", "SHA384", "SHA512", "SHA1"));
|
||||
sources.add(enumSource("edi_encrypt_algorithm", "AES128_CBC", "AES256_CBC", "AES128_GCM", "AES256_GCM", "3DES"));
|
||||
|
||||
@@ -33,6 +33,15 @@ public class EdiAgreement extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "edi_agreement";
|
||||
|
||||
@QField(backendName = "release_character", maxLength = 255, defaultValue = "?")
|
||||
private String releaseCharacter;
|
||||
|
||||
@QField(backendName = "decimal_mark", maxLength = 255, defaultValue = ".")
|
||||
private String decimalMark;
|
||||
|
||||
@QField(backendName = "syntax_identifier", maxLength = 255, defaultValue = "UNOC")
|
||||
private String syntaxIdentifier;
|
||||
|
||||
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
|
||||
private Integer id;
|
||||
|
||||
@@ -839,4 +848,79 @@ public class EdiAgreement extends QRecordEntity
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get releaseCharacter.
|
||||
*******************************************************************************/
|
||||
public String getReleaseCharacter()
|
||||
{
|
||||
return (releaseCharacter);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Set releaseCharacter.
|
||||
*******************************************************************************/
|
||||
public void setReleaseCharacter(String releaseCharacter)
|
||||
{
|
||||
this.releaseCharacter = releaseCharacter;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Set releaseCharacter fluently.
|
||||
*******************************************************************************/
|
||||
public EdiAgreement withReleaseCharacter(String releaseCharacter)
|
||||
{
|
||||
this.releaseCharacter = releaseCharacter;
|
||||
return (this);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Get decimalMark.
|
||||
*******************************************************************************/
|
||||
public String getDecimalMark()
|
||||
{
|
||||
return (decimalMark);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Set decimalMark.
|
||||
*******************************************************************************/
|
||||
public void setDecimalMark(String decimalMark)
|
||||
{
|
||||
this.decimalMark = decimalMark;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Set decimalMark fluently.
|
||||
*******************************************************************************/
|
||||
public EdiAgreement withDecimalMark(String decimalMark)
|
||||
{
|
||||
this.decimalMark = decimalMark;
|
||||
return (this);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Get syntaxIdentifier.
|
||||
*******************************************************************************/
|
||||
public String getSyntaxIdentifier()
|
||||
{
|
||||
return (syntaxIdentifier);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Set syntaxIdentifier.
|
||||
*******************************************************************************/
|
||||
public void setSyntaxIdentifier(String syntaxIdentifier)
|
||||
{
|
||||
this.syntaxIdentifier = syntaxIdentifier;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Set syntaxIdentifier fluently.
|
||||
*******************************************************************************/
|
||||
public EdiAgreement withSyntaxIdentifier(String syntaxIdentifier)
|
||||
{
|
||||
this.syntaxIdentifier = syntaxIdentifier;
|
||||
return (this);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ public class HttpsTransport implements EdiTransportPlugin
|
||||
bytes = stream.readAllBytes();
|
||||
}
|
||||
HttpRequest.Builder request = HttpRequest.newBuilder(URI.create(transport.getHttpsUrl())).timeout(Duration.ofSeconds(60))
|
||||
.header("Content-Type", "application/edi-x12").POST(HttpRequest.BodyPublishers.ofByteArray(bytes));
|
||||
.header("Content-Type", new String(bytes, java.nio.charset.StandardCharsets.US_ASCII).stripLeading().startsWith("UN") ? "application/EDIFACT" : "application/edi-x12").POST(HttpRequest.BodyPublishers.ofByteArray(bytes));
|
||||
if(transport.getHttpsHeadersJson() != null)
|
||||
{
|
||||
Map<String, String> headers = new ObjectMapper().readValue(transport.getHttpsHeadersJson(), new TypeReference<Map<String, String>>() {});
|
||||
|
||||
@@ -50,7 +50,8 @@ public class ControlNumberAllocator
|
||||
*******************************************************************************/
|
||||
public long allocate(Integer agreementId, Integer localIdentityId, String level, long min, long max, boolean wrap) throws SQLException
|
||||
{
|
||||
if((agreementId == null) == (localIdentityId == null) || !List.of("ISA", "GS").contains(level) || min < 1 || max < min || max > 999999999)
|
||||
long ceiling = List.of("UNB", "UNG", "UNH").contains(level) ? 99999999999999L : 999999999;
|
||||
if((agreementId == null) == (localIdentityId == null) || !List.of("ISA", "GS", "UNB", "UNG", "UNH").contains(level) || min < 1 || max < min || max > ceiling)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid control number scope or bounds");
|
||||
}
|
||||
@@ -100,7 +101,7 @@ public class ControlNumberAllocator
|
||||
return (min);
|
||||
}
|
||||
}
|
||||
if(minimum < 1 || maximum < minimum || maximum > 999999999 || previous < minimum - 1 || previous > maximum)
|
||||
if(minimum < 1 || maximum < minimum || maximum > ceiling || previous < minimum - 1 || previous > maximum)
|
||||
{
|
||||
throw new IllegalStateException("Stored control number range is invalid");
|
||||
}
|
||||
|
||||
@@ -55,6 +55,18 @@ public class DuplicateDetector
|
||||
return (!EdiRecords.query("edi_interchange", filter).isEmpty());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** EDIFACT duplicates use complete UNB identities, reference and test indicator.
|
||||
*******************************************************************************/
|
||||
public boolean edifact(Integer agreementId, com.kingsrook.qbits.edi.edifact.EdifactInterchange input, int days, Instant now) throws QException
|
||||
{
|
||||
return (!EdiRecords.query("edi_interchange", EdiRecords.equal("agreementId", agreementId).withCriteria("direction", QCriteriaOperator.EQUALS, "INBOUND")
|
||||
.withCriteria("standard", QCriteriaOperator.EQUALS, "EDIFACT").withCriteria("senderId", QCriteriaOperator.EQUALS, input.unb().value(2))
|
||||
.withCriteria("senderQualifier", QCriteriaOperator.EQUALS, input.unb().value(2, 1, 2)).withCriteria("receiverId", QCriteriaOperator.EQUALS, input.unb().value(3))
|
||||
.withCriteria("receiverQualifier", QCriteriaOperator.EQUALS, input.unb().value(3, 1, 2)).withCriteria("controlNumber", QCriteriaOperator.EQUALS, input.unb().value(5))
|
||||
.withCriteria("usageIndicator", QCriteriaOperator.EQUALS, input.usage()).withCriteria("createDate", QCriteriaOperator.GREATER_THAN_OR_EQUALS, now.minus(days, ChronoUnit.DAYS))).isEmpty());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Check GS06 for a sender in the duplicate window.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -27,6 +27,15 @@ import java.util.List;
|
||||
/*******************************************************************************
|
||||
** Buffered transaction set, including its ST and SE.
|
||||
*******************************************************************************/
|
||||
public record X12TransactionSet(X12Segment st, X12Segment se, List<X12Segment> segments, List<EnvelopeFinding> findings)
|
||||
public record X12TransactionSet(X12Segment st, X12Segment se, List<X12Segment> segments, List<EnvelopeFinding> findings) implements com.kingsrook.qbits.edi.document.EdiDocument
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Expose the header to standard-neutral validators and maps.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public X12Segment header()
|
||||
{
|
||||
return (st);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
{
|
||||
"name": "QQQ baseline CONTRL D01B",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D01B",
|
||||
"transactionSet": "CONTRL",
|
||||
"functionalIdCode": "CONTRL",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"CONTRL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"01B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UCI",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 4,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 4,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"4",
|
||||
"7",
|
||||
"8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 5,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UCM",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"4",
|
||||
"7",
|
||||
"8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "UCS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG1"
|
||||
},
|
||||
{
|
||||
"segmentId": "UCD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 99,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
"15"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG1"
|
||||
},
|
||||
{
|
||||
"segmentId": "UCF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"4",
|
||||
"7",
|
||||
"8"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG3",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "UCM",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"4",
|
||||
"7",
|
||||
"8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
],
|
||||
"loopId": "SG4",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG3"
|
||||
},
|
||||
{
|
||||
"segmentId": "UCS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
],
|
||||
"loopId": "SG5",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG4"
|
||||
},
|
||||
{
|
||||
"segmentId": "UCD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 99,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
"15"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG5",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG4"
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
{
|
||||
"name": "QQQ baseline CONTRL D96A",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D96A",
|
||||
"transactionSet": "CONTRL",
|
||||
"functionalIdCode": "CONTRL",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"CONTRL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"96A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UCI",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 4,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 4,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"4",
|
||||
"7",
|
||||
"8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 5,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UCM",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"4",
|
||||
"7",
|
||||
"8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "UCS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG1"
|
||||
},
|
||||
{
|
||||
"segmentId": "UCD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 99,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
"15"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG1"
|
||||
},
|
||||
{
|
||||
"segmentId": "UCF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"4",
|
||||
"7",
|
||||
"8"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG3",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "UCM",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"4",
|
||||
"7",
|
||||
"8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
],
|
||||
"loopId": "SG4",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG3"
|
||||
},
|
||||
{
|
||||
"segmentId": "UCS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL"
|
||||
}
|
||||
],
|
||||
"loopId": "SG5",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG4"
|
||||
},
|
||||
{
|
||||
"segmentId": "UCD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 99,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
"15"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG5",
|
||||
"loopMaxRepeat": 999,
|
||||
"parentLoopId": "SG4"
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
{
|
||||
"name": "QQQ baseline DESADV D01B",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D01B",
|
||||
"transactionSet": "DESADV",
|
||||
"functionalIdCode": "DESADV",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"DESADV"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"01B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BGM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"351"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"9",
|
||||
"29"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 5,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"137",
|
||||
"11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "DT",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"102"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "RFF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "NAD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"BY",
|
||||
"SU",
|
||||
"DP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"9"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "CPS",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 12,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
],
|
||||
"loopId": "SG10",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "PAC",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
],
|
||||
"loopId": "SG10",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "LIN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"EN",
|
||||
"SA"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG17",
|
||||
"loopMaxRepeat": 9999,
|
||||
"parentLoopId": "SG10"
|
||||
},
|
||||
{
|
||||
"segmentId": "QTY",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"12"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"PCE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG17",
|
||||
"loopMaxRepeat": 9999,
|
||||
"parentLoopId": "SG10"
|
||||
},
|
||||
{
|
||||
"segmentId": "UNS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"S"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CNT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
{
|
||||
"name": "QQQ baseline DESADV D96A",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D96A",
|
||||
"transactionSet": "DESADV",
|
||||
"functionalIdCode": "DESADV",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"DESADV"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"96A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BGM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"351"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"9",
|
||||
"29"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 5,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"137",
|
||||
"11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "DT",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"102"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "RFF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "NAD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"BY",
|
||||
"SU",
|
||||
"DP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"9"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "CPS",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 12,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
],
|
||||
"loopId": "SG10",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "PAC",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
],
|
||||
"loopId": "SG10",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "LIN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"EN",
|
||||
"SA"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG17",
|
||||
"loopMaxRepeat": 9999,
|
||||
"parentLoopId": "SG10"
|
||||
},
|
||||
{
|
||||
"segmentId": "QTY",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"12"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"PCE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG17",
|
||||
"loopMaxRepeat": 9999,
|
||||
"parentLoopId": "SG10"
|
||||
},
|
||||
{
|
||||
"segmentId": "UNS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"S"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CNT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
{
|
||||
"name": "QQQ baseline INVOIC D01B",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D01B",
|
||||
"transactionSet": "INVOIC",
|
||||
"functionalIdCode": "INVOIC",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"INVOIC"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"01B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BGM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"380"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"9",
|
||||
"29"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 5,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"137",
|
||||
"11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "DT",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"102"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "RFF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "NAD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"BY",
|
||||
"SU",
|
||||
"DP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"9"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "LIN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"EN",
|
||||
"SA"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "QTY",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"47"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"PCE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "PRI",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"AAA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "UNS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"S"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CNT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "MOA",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"77"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 18,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
{
|
||||
"name": "QQQ baseline INVOIC D96A",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D96A",
|
||||
"transactionSet": "INVOIC",
|
||||
"functionalIdCode": "INVOIC",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"INVOIC"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"96A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BGM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"380"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"9",
|
||||
"29"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 5,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"137",
|
||||
"11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "DT",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"102"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "RFF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "NAD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"BY",
|
||||
"SU",
|
||||
"DP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"9"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "LIN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"EN",
|
||||
"SA"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "QTY",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"47"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"PCE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "PRI",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"AAA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "UNS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"S"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CNT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "MOA",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"77"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 18,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
{
|
||||
"name": "QQQ baseline ORDERS D01B",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D01B",
|
||||
"transactionSet": "ORDERS",
|
||||
"functionalIdCode": "ORDERS",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ORDERS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"01B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BGM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"220"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"9",
|
||||
"29"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 5,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"137",
|
||||
"11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "DT",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"102"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "RFF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "NAD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"BY",
|
||||
"SU",
|
||||
"DP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"9"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "LIN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"EN",
|
||||
"SA"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "QTY",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"21"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"PCE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "PRI",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"AAA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "UNS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"S"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CNT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
{
|
||||
"name": "QQQ baseline ORDERS D96A",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D96A",
|
||||
"transactionSet": "ORDERS",
|
||||
"functionalIdCode": "ORDERS",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ORDERS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"96A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BGM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"220"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"9",
|
||||
"29"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 5,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"137",
|
||||
"11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "DT",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"102"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "RFF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "NAD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"BY",
|
||||
"SU",
|
||||
"DP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"9"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "LIN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"EN",
|
||||
"SA"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "QTY",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"21"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"PCE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "PRI",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"AAA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "UNS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"S"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CNT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
{
|
||||
"name": "QQQ baseline ORDRSP D01B",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D01B",
|
||||
"transactionSet": "ORDRSP",
|
||||
"functionalIdCode": "ORDRSP",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ORDRSP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"01B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BGM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"231"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"9",
|
||||
"29"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 5,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"137",
|
||||
"11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "DT",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"102"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "RFF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "NAD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"BY",
|
||||
"SU",
|
||||
"DP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"9"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "LIN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"EN",
|
||||
"SA"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "QTY",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"21"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"PCE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "PRI",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"AAA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "UNS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"S"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CNT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
{
|
||||
"name": "QQQ baseline ORDRSP D96A",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D96A",
|
||||
"transactionSet": "ORDRSP",
|
||||
"functionalIdCode": "ORDRSP",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "UNH",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ORDRSP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"96A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 4,
|
||||
"codes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BGM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"231"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"9",
|
||||
"29"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 5,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"137",
|
||||
"11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "DT",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"102"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "RFF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG1",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "NAD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"BY",
|
||||
"SU",
|
||||
"DP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "SITUATIONAL",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"9"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG2",
|
||||
"loopMaxRepeat": 10
|
||||
},
|
||||
{
|
||||
"segmentId": "LIN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 6,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2,
|
||||
"codes": [
|
||||
"EN",
|
||||
"SA"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "QTY",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"21"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 3,
|
||||
"codes": [
|
||||
"PCE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "PRI",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"AAA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "R",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
],
|
||||
"loopId": "SG25",
|
||||
"loopMaxRepeat": 9999
|
||||
},
|
||||
{
|
||||
"segmentId": "UNS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"usage": "REQUIRED",
|
||||
"codes": [
|
||||
"S"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CNT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 1,
|
||||
"codes": [
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED",
|
||||
"componentPosition": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "UNT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"usage": "REQUIRED"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"minLength": 1,
|
||||
"maxLength": 14,
|
||||
"usage": "REQUIRED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"map": {
|
||||
"name": "DESADV D01B retail sample",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D01B",
|
||||
"transactionSet": "DESADV",
|
||||
"direction": "OUTBOUND",
|
||||
"status": "ACTIVE",
|
||||
"revision": 1,
|
||||
"tableName": "sample_shipment",
|
||||
"businessKeyPath": "BGM02",
|
||||
"businessKeyType": "SHIPMENT"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"ediPath": "BGM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"351\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"ediPath": "BGM02",
|
||||
"qqqPath": "shipmentNumber",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"ediPath": "BGM03",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"9\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"ediPath": "DTM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"137\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"ediPath": "DTM01-2",
|
||||
"qqqPath": "shipDate",
|
||||
"required": true,
|
||||
"transform": "DATE"
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"ediPath": "DTM01-3",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"102\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 7,
|
||||
"ediPath": "SG10/CPS01",
|
||||
"qqqPath": "cartons.sscc",
|
||||
"required": true,
|
||||
"transform": "SEQUENCE"
|
||||
},
|
||||
{
|
||||
"sequence": 8,
|
||||
"ediPath": "SG10/PAC01",
|
||||
"qqqPath": "cartons.sscc",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"1\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 9,
|
||||
"ediPath": "SG10/SG17/LIN01",
|
||||
"qqqPath": "cartons.items.sku",
|
||||
"required": true,
|
||||
"transform": "SEQUENCE"
|
||||
},
|
||||
{
|
||||
"sequence": 10,
|
||||
"ediPath": "SG10/SG17/LIN03-1",
|
||||
"qqqPath": "cartons.items.sku",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 11,
|
||||
"ediPath": "SG10/SG17/LIN03-2",
|
||||
"qqqPath": "cartons.items.sku",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"SA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 12,
|
||||
"ediPath": "SG10/SG17/QTY01-1",
|
||||
"qqqPath": "cartons.items.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"12\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 13,
|
||||
"ediPath": "SG10/SG17/QTY01-2",
|
||||
"qqqPath": "cartons.items.quantity",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 14,
|
||||
"ediPath": "SG10/SG17/QTY01-3",
|
||||
"qqqPath": "cartons.items.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"PCE\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 15,
|
||||
"ediPath": "UNS01",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"S\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"map": {
|
||||
"name": "DESADV D96A retail sample",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D96A",
|
||||
"transactionSet": "DESADV",
|
||||
"direction": "OUTBOUND",
|
||||
"status": "ACTIVE",
|
||||
"revision": 1,
|
||||
"tableName": "sample_shipment",
|
||||
"businessKeyPath": "BGM02",
|
||||
"businessKeyType": "SHIPMENT"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"ediPath": "BGM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"351\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"ediPath": "BGM02",
|
||||
"qqqPath": "shipmentNumber",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"ediPath": "BGM03",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"9\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"ediPath": "DTM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"137\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"ediPath": "DTM01-2",
|
||||
"qqqPath": "shipDate",
|
||||
"required": true,
|
||||
"transform": "DATE"
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"ediPath": "DTM01-3",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"102\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 7,
|
||||
"ediPath": "SG10/CPS01",
|
||||
"qqqPath": "cartons.sscc",
|
||||
"required": true,
|
||||
"transform": "SEQUENCE"
|
||||
},
|
||||
{
|
||||
"sequence": 8,
|
||||
"ediPath": "SG10/PAC01",
|
||||
"qqqPath": "cartons.sscc",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"1\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 9,
|
||||
"ediPath": "SG10/SG17/LIN01",
|
||||
"qqqPath": "cartons.items.sku",
|
||||
"required": true,
|
||||
"transform": "SEQUENCE"
|
||||
},
|
||||
{
|
||||
"sequence": 10,
|
||||
"ediPath": "SG10/SG17/LIN03-1",
|
||||
"qqqPath": "cartons.items.sku",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 11,
|
||||
"ediPath": "SG10/SG17/LIN03-2",
|
||||
"qqqPath": "cartons.items.sku",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"SA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 12,
|
||||
"ediPath": "SG10/SG17/QTY01-1",
|
||||
"qqqPath": "cartons.items.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"12\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 13,
|
||||
"ediPath": "SG10/SG17/QTY01-2",
|
||||
"qqqPath": "cartons.items.quantity",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 14,
|
||||
"ediPath": "SG10/SG17/QTY01-3",
|
||||
"qqqPath": "cartons.items.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"PCE\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 15,
|
||||
"ediPath": "UNS01",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"S\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"map": {
|
||||
"name": "INVOIC D01B retail sample",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D01B",
|
||||
"transactionSet": "INVOIC",
|
||||
"direction": "OUTBOUND",
|
||||
"status": "ACTIVE",
|
||||
"revision": 1,
|
||||
"tableName": "sample_invoice",
|
||||
"businessKeyPath": "BGM02",
|
||||
"businessKeyType": "INVOICE"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"ediPath": "BGM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"380\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"ediPath": "BGM02",
|
||||
"qqqPath": "invoiceNumber",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"ediPath": "BGM03",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"9\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"ediPath": "DTM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"137\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"ediPath": "DTM01-2",
|
||||
"qqqPath": "invoiceDate",
|
||||
"required": true,
|
||||
"transform": "DATE"
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"ediPath": "DTM01-3",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"102\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 7,
|
||||
"ediPath": "SG25/LIN01",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true,
|
||||
"transform": "SEQUENCE"
|
||||
},
|
||||
{
|
||||
"sequence": 8,
|
||||
"ediPath": "SG25/LIN03-1",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 9,
|
||||
"ediPath": "SG25/LIN03-2",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"SA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 10,
|
||||
"ediPath": "SG25/QTY01-1",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"47\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 11,
|
||||
"ediPath": "SG25/QTY01-2",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 12,
|
||||
"ediPath": "SG25/QTY01-3",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"PCE\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 13,
|
||||
"ediPath": "SG25/PRI01-1",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"AAA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 14,
|
||||
"ediPath": "SG25/PRI01-2",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 15,
|
||||
"ediPath": "UNS01",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"S\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 16,
|
||||
"ediPath": "MOA01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"77\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 17,
|
||||
"ediPath": "MOA01-2",
|
||||
"qqqPath": "total",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"map": {
|
||||
"name": "INVOIC D96A retail sample",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D96A",
|
||||
"transactionSet": "INVOIC",
|
||||
"direction": "OUTBOUND",
|
||||
"status": "ACTIVE",
|
||||
"revision": 1,
|
||||
"tableName": "sample_invoice",
|
||||
"businessKeyPath": "BGM02",
|
||||
"businessKeyType": "INVOICE"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"ediPath": "BGM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"380\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"ediPath": "BGM02",
|
||||
"qqqPath": "invoiceNumber",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"ediPath": "BGM03",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"9\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"ediPath": "DTM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"137\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"ediPath": "DTM01-2",
|
||||
"qqqPath": "invoiceDate",
|
||||
"required": true,
|
||||
"transform": "DATE"
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"ediPath": "DTM01-3",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"102\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 7,
|
||||
"ediPath": "SG25/LIN01",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true,
|
||||
"transform": "SEQUENCE"
|
||||
},
|
||||
{
|
||||
"sequence": 8,
|
||||
"ediPath": "SG25/LIN03-1",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 9,
|
||||
"ediPath": "SG25/LIN03-2",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"SA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 10,
|
||||
"ediPath": "SG25/QTY01-1",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"47\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 11,
|
||||
"ediPath": "SG25/QTY01-2",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 12,
|
||||
"ediPath": "SG25/QTY01-3",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"PCE\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 13,
|
||||
"ediPath": "SG25/PRI01-1",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"AAA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 14,
|
||||
"ediPath": "SG25/PRI01-2",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 15,
|
||||
"ediPath": "UNS01",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"S\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 16,
|
||||
"ediPath": "MOA01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"77\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 17,
|
||||
"ediPath": "MOA01-2",
|
||||
"qqqPath": "total",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"map": {
|
||||
"name": "ORDERS D01B retail sample",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D01B",
|
||||
"transactionSet": "ORDERS",
|
||||
"direction": "INBOUND",
|
||||
"status": "ACTIVE",
|
||||
"revision": 1,
|
||||
"tableName": "sample_order",
|
||||
"businessKeyPath": "BGM02",
|
||||
"businessKeyType": "PO",
|
||||
"matchKeyFields": "poNumber"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"ediPath": "BGM02",
|
||||
"qqqPath": "poNumber",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"ediPath": "DTM[DTM01-1=137]/DTM01-2",
|
||||
"qqqPath": "orderDate",
|
||||
"required": true,
|
||||
"transform": "DATE"
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"ediPath": "NAD[NAD01=BY]/NAD02-1",
|
||||
"qqqPath": "buyerId",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"ediPath": "SG25[*]/LIN/LIN03-1",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"ediPath": "SG25[*]/QTY/QTY01-2",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"ediPath": "SG25[*]/PRI/PRI01-2",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": false,
|
||||
"transform": "DECIMAL",
|
||||
"condition": "present(PRI01-2)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"map": {
|
||||
"name": "ORDERS D96A retail sample",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D96A",
|
||||
"transactionSet": "ORDERS",
|
||||
"direction": "INBOUND",
|
||||
"status": "ACTIVE",
|
||||
"revision": 1,
|
||||
"tableName": "sample_order",
|
||||
"businessKeyPath": "BGM02",
|
||||
"businessKeyType": "PO",
|
||||
"matchKeyFields": "poNumber"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"ediPath": "BGM02",
|
||||
"qqqPath": "poNumber",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"ediPath": "DTM[DTM01-1=137]/DTM01-2",
|
||||
"qqqPath": "orderDate",
|
||||
"required": true,
|
||||
"transform": "DATE"
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"ediPath": "NAD[NAD01=BY]/NAD02-1",
|
||||
"qqqPath": "buyerId",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"ediPath": "SG25[*]/LIN/LIN03-1",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"ediPath": "SG25[*]/QTY/QTY01-2",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"ediPath": "SG25[*]/PRI/PRI01-2",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": false,
|
||||
"transform": "DECIMAL",
|
||||
"condition": "present(PRI01-2)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"map": {
|
||||
"name": "ORDRSP D01B retail sample",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D01B",
|
||||
"transactionSet": "ORDRSP",
|
||||
"direction": "OUTBOUND",
|
||||
"status": "ACTIVE",
|
||||
"revision": 1,
|
||||
"tableName": "sample_order",
|
||||
"businessKeyPath": "BGM02",
|
||||
"businessKeyType": "PO"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"ediPath": "BGM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"231\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"ediPath": "BGM02",
|
||||
"qqqPath": "poNumber",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"ediPath": "BGM03",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"29\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"ediPath": "DTM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"137\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"ediPath": "DTM01-2",
|
||||
"qqqPath": "orderDate",
|
||||
"required": true,
|
||||
"transform": "DATE"
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"ediPath": "DTM01-3",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"102\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 7,
|
||||
"ediPath": "SG25/LIN01",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true,
|
||||
"transform": "SEQUENCE"
|
||||
},
|
||||
{
|
||||
"sequence": 8,
|
||||
"ediPath": "SG25/LIN03-1",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 9,
|
||||
"ediPath": "SG25/LIN03-2",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"SA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 10,
|
||||
"ediPath": "SG25/QTY01-1",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"21\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 11,
|
||||
"ediPath": "SG25/QTY01-2",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 12,
|
||||
"ediPath": "SG25/QTY01-3",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"PCE\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 13,
|
||||
"ediPath": "SG25/PRI01-1",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"AAA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 14,
|
||||
"ediPath": "SG25/PRI01-2",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 15,
|
||||
"ediPath": "UNS01",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"S\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"map": {
|
||||
"name": "ORDRSP D96A retail sample",
|
||||
"standard": "EDIFACT",
|
||||
"version": "D96A",
|
||||
"transactionSet": "ORDRSP",
|
||||
"direction": "OUTBOUND",
|
||||
"status": "ACTIVE",
|
||||
"revision": 1,
|
||||
"tableName": "sample_order",
|
||||
"businessKeyPath": "BGM02",
|
||||
"businessKeyType": "PO"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"ediPath": "BGM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"231\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"ediPath": "BGM02",
|
||||
"qqqPath": "poNumber",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"ediPath": "BGM03",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"29\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"ediPath": "DTM01-1",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"137\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"ediPath": "DTM01-2",
|
||||
"qqqPath": "orderDate",
|
||||
"required": true,
|
||||
"transform": "DATE"
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"ediPath": "DTM01-3",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"102\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 7,
|
||||
"ediPath": "SG25/LIN01",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true,
|
||||
"transform": "SEQUENCE"
|
||||
},
|
||||
{
|
||||
"sequence": 8,
|
||||
"ediPath": "SG25/LIN03-1",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"sequence": 9,
|
||||
"ediPath": "SG25/LIN03-2",
|
||||
"qqqPath": "lines.sku",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"SA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 10,
|
||||
"ediPath": "SG25/QTY01-1",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"21\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 11,
|
||||
"ediPath": "SG25/QTY01-2",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 12,
|
||||
"ediPath": "SG25/QTY01-3",
|
||||
"qqqPath": "lines.quantity",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"PCE\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 13,
|
||||
"ediPath": "SG25/PRI01-1",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"AAA\"}"
|
||||
},
|
||||
{
|
||||
"sequence": 14,
|
||||
"ediPath": "SG25/PRI01-2",
|
||||
"qqqPath": "lines.unitPrice",
|
||||
"required": true,
|
||||
"transform": "DECIMAL"
|
||||
},
|
||||
{
|
||||
"sequence": 15,
|
||||
"ediPath": "UNS01",
|
||||
"qqqPath": null,
|
||||
"required": true,
|
||||
"transform": "CONSTANT",
|
||||
"transformArgs": "{\"value\": \"S\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactEnvelopeReader;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactInterchange;
|
||||
import com.kingsrook.qbits.edi.flow.InboundPayload;
|
||||
import com.kingsrook.qbits.edi.flow.InboundSink;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** Synthetic Lindqvist retail partner shared by tests and the standalone demo.
|
||||
*******************************************************************************/
|
||||
public class EdifactSupport
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Configure both CONTRL directions and the four retail documents.
|
||||
*******************************************************************************/
|
||||
public static QRecord install(String version) throws Exception
|
||||
{
|
||||
QRecord partner = SystemWrites.insert("edi_partner", new QRecord().withValue("name", "Lindqvist Retail AB").withValue("code", "LINDQVIST")
|
||||
.withValue("status", "ACTIVE").withValue("contactEmail", "lindqvist@example.invalid").withValue("defaultTimezone", "Europe/Stockholm"));
|
||||
QRecord local = SystemWrites.insert("edi_identity", new QRecord().withValue("standard", "EDIFACT").withValue("interchangeQualifier", "14").withValue("interchangeId", "QQQTEST"));
|
||||
QRecord remote = SystemWrites.insert("edi_identity", new QRecord().withValue("partnerId", partner.getValue("id")).withValue("standard", "EDIFACT")
|
||||
.withValue("interchangeQualifier", "14").withValue("interchangeId", "LINDQVIST"));
|
||||
QRecord transport = SystemWrites.insert("edi_transport", new QRecord().withValue("name", "lindqvist-outbox").withValue("type", "GATEWAY")
|
||||
.withValue("direction", "BOTH").withValue("gatewayEndpointName", "lindqvist-outbox").withValue("isActive", true));
|
||||
QRecord agreement = SystemWrites.insert("edi_agreement", new QRecord().withValue("partnerId", partner.getValue("id")).withValue("localIdentityId", local.getValue("id"))
|
||||
.withValue("partnerIdentityId", remote.getValue("id")).withValue("standard", "EDIFACT").withValue("usageIndicator", "T").withValue("interchangeVersion", "3")
|
||||
.withValue("elementSeparator", "+").withValue("componentSeparator", ":").withValue("segmentTerminator", "'").withValue("releaseCharacter", "?")
|
||||
.withValue("applyTestDocuments", true).withValue("outboundTransportId", transport.getValue("id")));
|
||||
for(String doc : List.of("ORDERS", "ORDRSP", "DESADV", "INVOIC", "CONTRL"))
|
||||
{
|
||||
QRecord map = doc.equals("CONTRL") ? null : RetailSupport.installMap(doc, version);
|
||||
QRecord guide = EdiRecords.query("edi_guide", EdiRecords.equal("transactionSet", doc).withCriteria("version", QCriteriaOperator.EQUALS, version)).getFirst();
|
||||
for(String direction : doc.equals("CONTRL") ? List.of("INBOUND", "OUTBOUND") : List.of(doc.equals("ORDERS") ? "INBOUND" : "OUTBOUND"))
|
||||
{
|
||||
SystemWrites.insert("edi_agreement_document", new QRecord().withValue("agreementId", agreement.getValue("id")).withValue("direction", direction)
|
||||
.withValue("transactionSet", doc).withValue("version", version).withValue("guideId", guide.getValue("id")).withValue("mapId", map == null ? null : map.getValue("id"))
|
||||
.withValue("sourceTableName", map == null ? null : map.getValue("tableName")).withValue("targetTableName", map == null ? null : map.getValue("tableName"))
|
||||
.withValue("ackExpectation", doc.equals("CONTRL") ? "NONE" : "CONTRL").withValue("ackLevel", "ALL_TRANSACTIONS").withValue("ackDueMinutes", 1)
|
||||
.withValue("outboundTrigger", "MANUAL").withValue("batchMode", "IMMEDIATE").withValue("isActive", true));
|
||||
}
|
||||
}
|
||||
return (agreement);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Two synthetic lines, released delimiters and an EANCOM association identifier.
|
||||
*******************************************************************************/
|
||||
public static String orders(String reference, String version)
|
||||
{
|
||||
return ("UNA:+.? 'UNB+UNOC:3+LINDQVIST:14+QQQTEST:14+261014:1200+" + reference + "++++++1'"
|
||||
+ "UNH+M1+ORDERS:D:" + version.substring(1) + ":UN:EAN008'BGM+220+TEST-" + reference + "+9'DTM+137:20261014:102'RFF+ON:PO-1'NAD+BY+BUY?+ER::9'"
|
||||
+ "LIN+1++SKU?:ONE:SA'QTY+21:2:PCE'PRI+AAA:7.5'LIN+2++SKU?':SA'QTY+21:3:PCE'PRI+AAA:2'UNS+S'CNT+2:2'UNT+14+M1'UNZ+1+" + reference + "'");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Parse one synthetic interchange in memory.
|
||||
*******************************************************************************/
|
||||
public static EdifactInterchange read(String text) throws Exception
|
||||
{
|
||||
return (new EdifactEnvelopeReader(100000).read(new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8))).getFirst());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Exercise the durable receive boundary without a real network transport.
|
||||
*******************************************************************************/
|
||||
public static void receive(String text, String external) throws Exception
|
||||
{
|
||||
new InboundSink(EdiRuntime.current().payloads()).receive(new InboundPayload("GATEWAY", external, "synthetic.edi", null, new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.edi;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.edi.ack.AckLevel;
|
||||
import com.kingsrook.qbits.edi.edifact.ContrlBuilder;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactEnvelopeReader;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactInterchange;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactMapper;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactOutbound;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactSeparators;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactWriter;
|
||||
import com.kingsrook.qbits.edi.flow.InboxWorker;
|
||||
import com.kingsrook.qbits.edi.flow.OutboundEnqueue;
|
||||
import com.kingsrook.qbits.edi.flow.OutboundWorker;
|
||||
import com.kingsrook.qbits.edi.guide.BuiltInGuideLoader;
|
||||
import com.kingsrook.qbits.edi.guide.GuideValidator;
|
||||
import com.kingsrook.qbits.edi.guide.RuleEvaluator;
|
||||
import com.kingsrook.qbits.edi.guide.ValidationFinding;
|
||||
import com.kingsrook.qbits.edi.map.InboundMapper;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.storage.PayloadRef;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qbits.edi.x12.EnvelopeFinding;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/*******************************************************************************
|
||||
** Native envelope, guide, map and durable pipeline acceptance with synthetic data.
|
||||
*******************************************************************************/
|
||||
class EdifactTest
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Restore test context.
|
||||
*******************************************************************************/
|
||||
@AfterEach
|
||||
void clear()
|
||||
{
|
||||
QContext.clear();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Directory baselines validate and mapping retains segment groups and components.
|
||||
*******************************************************************************/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"D96A", "D01B"})
|
||||
void orders(String version) throws Exception
|
||||
{
|
||||
H2Support.create();
|
||||
var input = EdifactSupport.read(EdifactSupport.orders("100", version));
|
||||
assertThat(input.findings()).isEmpty();
|
||||
var message = input.allMessages().getFirst();
|
||||
assertThat(message.findings()).isEmpty();
|
||||
var guide = new BuiltInGuideLoader().resource("ORDERS-" + version);
|
||||
assertThat(new GuideValidator().validate(message, guide, false)).isEmpty();
|
||||
var sample = RetailSupport.sample("ORDERS", version);
|
||||
List<QRecord> rules = new ArrayList<>();
|
||||
sample.get("rules").forEach(r -> rules.add(RetailSupport.record(r)));
|
||||
var mapped = new InboundMapper(EdiRuntime.current().transforms()).map(message, guide, RetailSupport.record(sample.get("map")), rules);
|
||||
assertThat(mapped.record().getValueString("buyerId")).isEqualTo("BUY+ER");
|
||||
assertThat(mapped.record().getAssociatedRecords().get("lines")).extracting(r -> r.getValueString("sku")).containsExactly("SKU:ONE", "SKU'");
|
||||
assertThat(new RuleEvaluator(message.segments(), new GuideValidator().loops(message, guide)).values("SG25/LIN/LIN03-1")).containsExactly("SKU:ONE", "SKU'");
|
||||
assertThat(EdifactSupport.read(new EdifactWriter().write(input))).isEqualTo(input);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Each interchange can change UNA; released terminators never split the payload.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void multiInterchangeAndSyntaxFour() throws Exception
|
||||
{
|
||||
var first = EdifactSupport.read(EdifactSupport.orders("100", "D96A"));
|
||||
var custom = new EdifactInterchange(new EdifactSeparators('>', '|', '.', '!', ' ', '~'), first.unb(), first.unz(), first.groups(), first.messages(), List.of());
|
||||
String text = new EdifactWriter().write(first) + "\r\n" + new EdifactWriter().write(custom, "\r\n");
|
||||
var parsed = new EdifactEnvelopeReader(1000).read(new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)));
|
||||
assertThat(parsed).containsExactly(first, custom);
|
||||
var four = EdifactSupport.read(EdifactSupport.orders("101", "D01B").replace("UNA:+.? '", "UNA:+.?*'").replace("UNOC:3", "UNOC:4").replace("261014:1200", "20261014:1200"));
|
||||
assertThat(four.findings()).isEmpty();
|
||||
assertThat(four.allMessages().getFirst().findings()).isEmpty();
|
||||
assertThat(EdifactSupport.read(new EdifactWriter().write(four))).isEqualTo(four);
|
||||
assertThat(EdifactSupport.read(EdifactSupport.orders("100", "D96A").substring(9)).findings()).isEmpty();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Native group counts, references, message limits and incomplete envelopes fail safely.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void findingsAndGroups() throws Exception
|
||||
{
|
||||
String grouped = EdifactSupport.orders("100", "D96A").replace("UNH+", "UNG+ORDERS+LINDQVIST+QQQTEST+261014:1200+G1+UN+D:96A'UNH+").replace("UNZ+", "UNE+1+G1'UNZ+");
|
||||
var valid = EdifactSupport.read(grouped);
|
||||
assertThat(valid.findings()).isEmpty();
|
||||
assertThat(valid.groups()).hasSize(1);
|
||||
assertThat(valid.groups().getFirst().findings()).isEmpty();
|
||||
assertThat(EdifactSupport.read(new EdifactWriter().write(valid))).isEqualTo(valid);
|
||||
var groupAck = new ContrlBuilder().build(valid, Map.of(), AckLevel.ALL_TRANSACTIONS, "A1", "D96A");
|
||||
assertThat(new EdifactWriter().write(groupAck.segments(), EdifactSeparators.DEFAULT, "")).contains("UCF+G1+LINDQVIST+QQQTEST+7'");
|
||||
assertThat(new GuideValidator().validate(groupAck, new BuiltInGuideLoader().resource("CONTRL-D96A"), false)).isEmpty();
|
||||
var invalid = EdifactSupport.read(grouped.replace("UNT+14+M1", "UNT+13+M2").replace("UNE+1+G1", "UNE+2+G2").replace("UNZ+1+100", "UNZ+2+101"));
|
||||
assertThat(invalid.findings()).extracting(EnvelopeFinding::code).contains("29", "28");
|
||||
assertThat(invalid.groups().getFirst().findings()).extracting(EnvelopeFinding::code).containsExactly("29", "28");
|
||||
assertThat(invalid.allMessages().getFirst().findings()).extracting(EnvelopeFinding::code).containsExactly("29", "28");
|
||||
assertThat(EdifactSupport.read(grouped.replace("UNZ+1+100'", "")).findings()).isNotEmpty();
|
||||
var bounded = new EdifactEnvelopeReader(3).read(new ByteArrayInputStream(grouped.getBytes(StandardCharsets.UTF_8))).getFirst();
|
||||
assertThat(bounded.allMessages().getFirst().segments()).hasSize(3);
|
||||
assertThat(bounded.allMessages().getFirst().findings()).extracting(EnvelopeFinding::code).contains("35");
|
||||
assertThatThrownBy(() -> new EdifactEnvelopeReader(1)).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(() -> EdifactSeparators.fromUna("bad")).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Exact CONTRL output for acceptance, rejected data and envelope syntax errors.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void acknowledgements() throws Exception
|
||||
{
|
||||
var input = EdifactSupport.read(EdifactSupport.orders("100", "D96A"));
|
||||
var builder = new ContrlBuilder();
|
||||
var accepted = builder.build(input, Map.of(), AckLevel.ALL_TRANSACTIONS, "A1", "D96A");
|
||||
assertThat(new EdifactWriter().write(accepted.segments(), EdifactSeparators.DEFAULT, "")).isEqualTo("UNH+A1+CONTRL:D:96A:UN'UCI+100+LINDQVIST:14+QQQTEST:14+7'UCM+M1+ORDERS:D:96A:UN:EAN008+7'UNT+4+A1'");
|
||||
var error = new ValidationFinding(7, "QTY", "SG25", 1, 2, 2, "8", "6", "8", "6", "ERROR", "Invalid number", null);
|
||||
var rejected = builder.build(input, Map.of("M1", List.of(error)), AckLevel.ERRORS_ONLY, "A2", "D96A");
|
||||
assertThat(new EdifactWriter().write(rejected.segments(), EdifactSeparators.DEFAULT, "")).isEqualTo("UNH+A2+CONTRL:D:96A:UN'UCI+100+LINDQVIST:14+QQQTEST:14+7'UCM+M1+ORDERS:D:96A:UN:EAN008+4'UCS+7'UCD+12+1:2'UNT+6+A2'");
|
||||
var broken = EdifactSupport.read(EdifactSupport.orders("100", "D96A").replace("UNZ+1+100", "UNZ+2+100"));
|
||||
assertThat(new EdifactWriter().write(builder.build(broken, Map.of(), AckLevel.GROUP_ONLY, "A3", "D96A").segments(), EdifactSeparators.DEFAULT, ""))
|
||||
.isEqualTo("UNH+A3+CONTRL:D:96A:UN'UCI+100+LINDQVIST:14+QQQTEST:14+4+29'UNT+3+A3'");
|
||||
for(var ack : List.of(accepted, rejected))
|
||||
{
|
||||
assertThat(new GuideValidator().validate(ack, new BuiltInGuideLoader().resource("CONTRL-D96A"), false)).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** All outbound retail documents serialize, validate and parse with two line occurrences.
|
||||
*******************************************************************************/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"D96A", "D01B"})
|
||||
void outboundRoundTrips(String version) throws Exception
|
||||
{
|
||||
H2Support.create();
|
||||
for(String doc : List.of("ORDRSP", "DESADV", "INVOIC"))
|
||||
{
|
||||
var sample = RetailSupport.sample(doc, version);
|
||||
List<QRecord> rules = new ArrayList<>();
|
||||
sample.get("rules").forEach(r -> rules.add(RetailSupport.record(r)));
|
||||
var message = new EdifactMapper().message(RetailSupport.source(), RetailSupport.record(sample.get("map")), rules, "1");
|
||||
assertThat(new GuideValidator().validate(message, new BuiltInGuideLoader().resource(doc + "-" + version), false)).as(doc).isEmpty();
|
||||
var envelope = EdifactOutbound.wrap(new QRecord().withValue("usageIndicator", "T"), new QRecord().withValue("interchangeId", "LOCAL").withValue("interchangeQualifier", "14"),
|
||||
new QRecord().withValue("interchangeId", "REMOTE").withValue("interchangeQualifier", "14"), List.of(message), "100", java.time.LocalDateTime.of(2026, 10, 14, 12, 0));
|
||||
var parsed = EdifactSupport.read(new EdifactWriter().write(envelope));
|
||||
assertThat(parsed.findings()).isEmpty();
|
||||
assertThat(parsed.allMessages().getFirst().findings()).isEmpty();
|
||||
assertThat(parsed.allMessages().getFirst().segments()).isEqualTo(message.segments());
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** ORDERS is applied through the durable inbox; repeat controls cannot reapply it.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void inboxAndDuplicates() throws Exception
|
||||
{
|
||||
setup();
|
||||
EdifactSupport.receive(EdifactSupport.orders("100", "D96A"), "orders-1");
|
||||
assertThat(new InboxWorker().runOnce()).isEqualTo(1);
|
||||
assertThat(rows("edi_inbox")).extracting(r -> r.getValueString("status")).containsExactly("PROCESSED");
|
||||
assertThat(rows("edi_transaction")).anyMatch(r -> "ORDERS".equals(r.getValueString("transactionSet")) && "APPLIED".equals(r.getValueString("status")));
|
||||
assertThat(rows("sample_order")).hasSize(1);
|
||||
assertThat(rows("sample_order_line")).hasSize(2);
|
||||
assertThat(rows("edi_outbound_message")).hasSize(1);
|
||||
EdifactSupport.receive(EdifactSupport.orders("100", "D96A").replace("TEST-100", "TEST-OTHER"), "orders-2");
|
||||
new InboxWorker().runOnce();
|
||||
assertThat(rows("sample_order")).hasSize(1);
|
||||
assertThat(rows("edi_exception")).anyMatch(r -> "DUPLICATE".equals(r.getValueString("type")));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Worker maps persisted retail records, including nested shipment associations.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void outboundWorker() throws Exception
|
||||
{
|
||||
setup();
|
||||
SystemWrites.call(() ->
|
||||
{
|
||||
for(String table : List.of("sample_order", "sample_shipment", "sample_invoice"))
|
||||
{
|
||||
QRecord source = SystemWrites.transaction(() -> RetailSupport.saveSource(table, RetailSupport.source()));
|
||||
OutboundEnqueue.enqueue(table, source, "MANUAL", null);
|
||||
}
|
||||
return (null);
|
||||
});
|
||||
assertThat(new OutboundWorker().runOnce()).isEqualTo(3);
|
||||
assertThat(rows("edi_outbound_message")).hasSize(3);
|
||||
for(var row : rows("edi_outbound_message"))
|
||||
{
|
||||
try(var stream = EdiRuntime.current().payloads().open(new PayloadRef(row.getValueString("payloadRef"), null, 0)))
|
||||
{
|
||||
var parsed = new EdifactEnvelopeReader(10000).read(stream).getFirst();
|
||||
assertThat(parsed.findings()).isEmpty();
|
||||
assertThat(parsed.allMessages().getFirst().findings()).isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** CONTRL resolves deadlines and reconciles accepted and rejected outbound messages.
|
||||
*******************************************************************************/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void reconcile(boolean reject) throws Exception
|
||||
{
|
||||
QRecord agreement = setup();
|
||||
SystemWrites.call(() ->
|
||||
{
|
||||
QRecord source = SystemWrites.transaction(() -> RetailSupport.saveSource("sample_order", RetailSupport.source()));
|
||||
OutboundEnqueue.enqueue("sample_order", source, "MANUAL", null);
|
||||
return (null);
|
||||
});
|
||||
new OutboundWorker().runOnce();
|
||||
QRecord outgoing = rows("edi_outbound_message").getFirst();
|
||||
com.kingsrook.qbits.edi.transport.gateway.standin.FileGatewayOutbox.register((endpoint, name, stream, size) ->
|
||||
new com.kingsrook.qbits.edi.transport.gateway.standin.OutboxFileRef(1, endpoint, name, name, size, null, java.time.Instant.now()));
|
||||
try
|
||||
{
|
||||
new com.kingsrook.qbits.edi.flow.SendWorker().runOnce();
|
||||
assertThat(rows("edi_functional_group").getFirst().getValueInstant("ackDueAt")).isNotNull();
|
||||
EdiRuntime.current().config().withClock(java.time.Clock.fixed(java.time.Instant.now().plusSeconds(120), java.time.ZoneOffset.UTC));
|
||||
assertThat(new com.kingsrook.qbits.edi.flow.AckMonitor().runOnce()).isEqualTo(1);
|
||||
EdifactInterchange original;
|
||||
try(var stream = EdiRuntime.current().payloads().open(new PayloadRef(outgoing.getValueString("payloadRef"), null, 0)))
|
||||
{
|
||||
original = new EdifactEnvelopeReader(10000).read(stream).getFirst();
|
||||
}
|
||||
var message = original.allMessages().getFirst();
|
||||
var error = new ValidationFinding(2, "BGM", null, 2, null, 2, "8", "1", "8", "1", "ERROR", "Missing number", null);
|
||||
var ack = new ContrlBuilder().build(original, reject ? Map.of(message.reference(), List.of(error)) : Map.of(), AckLevel.ALL_TRANSACTIONS, "A1", "D96A");
|
||||
var reply = EdifactOutbound.wrap(agreement, SystemWrites.call(() -> OutboundWorker.row("edi_identity", agreement.getValue("partnerIdentityId"))),
|
||||
SystemWrites.call(() -> OutboundWorker.row("edi_identity", agreement.getValue("localIdentityId"))), List.of(ack), "ACK100", java.time.LocalDateTime.of(2026, 10, 14, 12, 0));
|
||||
EdifactSupport.receive(new EdifactWriter().write(reply), "ack-1");
|
||||
assertThat(new InboxWorker().runOnce()).isEqualTo(1);
|
||||
assertThat(rows("edi_interchange").stream().filter(r -> r.getValue("id").equals(outgoing.getValue("interchangeId"))).findFirst().orElseThrow().getValueString("status"))
|
||||
.isEqualTo(reject ? "REJECTED" : "ACKNOWLEDGED");
|
||||
assertThat(rows("edi_transaction").stream().filter(r -> "OUTBOUND".equals(r.getValueString("direction"))).findFirst().orElseThrow().getValueString("ackStatus")).isEqualTo(reject ? "R" : "A");
|
||||
assertThat(rows("edi_exception")).anyMatch(r -> "OVERDUE_ACK".equals(r.getValueString("type")) && "RESOLVED".equals(r.getValueString("status")));
|
||||
assertThat(rows("edi_exception").stream().anyMatch(r -> "REJECTED_ACK".equals(r.getValueString("type")))).isEqualTo(reject);
|
||||
assertThat(rows("edi_outbound_message")).hasSize(1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
com.kingsrook.qbits.edi.transport.gateway.standin.FileGatewayOutbox.register(null);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** A single payload can contain X12 and EDIFACT and route each independently.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void mixedStandards() throws Exception
|
||||
{
|
||||
setup();
|
||||
SystemWrites.call(() ->
|
||||
{
|
||||
QRecord partner = SystemWrites.insert("edi_partner", new QRecord().withValue("name", "X12 mixed").withValue("code", "MIXED"));
|
||||
QRecord local = SystemWrites.insert("edi_identity", new QRecord().withValue("standard", "X12").withValue("interchangeQualifier", "ZZ").withValue("interchangeId", "QQQTEST"));
|
||||
QRecord remote = SystemWrites.insert("edi_identity", new QRecord().withValue("standard", "X12").withValue("interchangeQualifier", "ZZ").withValue("interchangeId", "SYNTHETIC").withValue("partnerId", partner.getValue("id")));
|
||||
QRecord agreement = SystemWrites.insert("edi_agreement", new QRecord().withValue("partnerId", partner.getValue("id")).withValue("localIdentityId", local.getValue("id"))
|
||||
.withValue("partnerIdentityId", remote.getValue("id")).withValue("standard", "X12").withValue("usageIndicator", "T").withValue("applyTestDocuments", true));
|
||||
QRecord map = RetailSupport.installMap("850", "004010");
|
||||
SystemWrites.insert("edi_agreement_document", new QRecord().withValue("agreementId", agreement.getValue("id")).withValue("direction", "INBOUND").withValue("transactionSet", "850")
|
||||
.withValue("version", "004010").withValue("guideId", map.getValue("guideId")).withValue("mapId", map.getValue("id")).withValue("ackExpectation", "NONE"));
|
||||
return (null);
|
||||
});
|
||||
String x12 = Synthetic850.generate(1, "004010", com.kingsrook.qbits.edi.x12.X12Separators.DEFAULT);
|
||||
EdifactSupport.receive(x12 + "\n" + EdifactSupport.orders("200", "D96A"), "mixed");
|
||||
assertThat(new InboxWorker().runOnce()).isEqualTo(1);
|
||||
assertThat(rows("edi_transaction").stream().filter(r -> "APPLIED".equals(r.getValueString("status"))).map(r -> r.getValueString("transactionSet"))).containsExactlyInAnyOrder("850", "ORDERS");
|
||||
assertThat(rows("sample_order")).hasSize(2);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** All service characters in data survive custom UNA and release escaping.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void releasedCharactersAndRepetition() throws Exception
|
||||
{
|
||||
var input = EdifactSupport.read(EdifactSupport.orders("100", "D96A"));
|
||||
var message = input.allMessages().getFirst();
|
||||
var segments = new ArrayList<>(message.segments());
|
||||
segments.set(1, com.kingsrook.qbits.edi.x12.X12Segment.of("BGM", 2, "220", "A|B>C!D~E?F", "9"));
|
||||
var changed = new com.kingsrook.qbits.edi.edifact.EdifactMessage(message.header(), message.unt(), segments, List.of());
|
||||
var custom = new EdifactInterchange(new EdifactSeparators('>', '|', '.', '!', ' ', '~'), input.unb(), input.unz(), List.of(), List.of(changed), List.of());
|
||||
String encoded = new EdifactWriter().write(custom);
|
||||
assertThat(encoded).contains("A!|B!>C!!D!~E?F");
|
||||
assertThat(EdifactSupport.read(encoded).allMessages().getFirst().segments()).isEqualTo(segments);
|
||||
var standard = new EdifactInterchange(EdifactSeparators.DEFAULT, input.unb(), input.unz(), List.of(), List.of(changed), List.of());
|
||||
assertThat(new EdifactWriter().write(standard)).contains("E??F");
|
||||
String four = EdifactSupport.orders("100", "D01B").replace("UNA:+.? '", "UNA:+.?*'").replace("UNOC:3", "UNOC:4").replace("261014:1200", "20261014:1200").replace("TEST-100", "FIRST*SECOND");
|
||||
var parsed = EdifactSupport.read(four);
|
||||
assertThat(parsed.allMessages().getFirst().segments().get(1).value(2, 2, 1)).isEqualTo("SECOND");
|
||||
assertThat(EdifactSupport.read(new EdifactWriter().write(parsed))).isEqualTo(parsed);
|
||||
var withoutAdvice = EdifactSupport.read(four.substring(9));
|
||||
assertThat(withoutAdvice.separators().repetition()).isEqualTo('*');
|
||||
assertThat(EdifactSupport.read(new EdifactWriter().write(withoutAdvice))).isEqualTo(withoutAdvice);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Resend retains bytes, while resend-as-new preserves archived content and changes controls.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void archivedResendAndPreview() throws Exception
|
||||
{
|
||||
setup();
|
||||
SystemWrites.call(() ->
|
||||
{
|
||||
var source = SystemWrites.transaction(() -> RetailSupport.saveSource("sample_order", RetailSupport.source()));
|
||||
OutboundEnqueue.enqueue("sample_order", source, "MANUAL", null);
|
||||
return (null);
|
||||
});
|
||||
new OutboundWorker().runOnce();
|
||||
QRecord original = rows("edi_outbound_message").getFirst();
|
||||
SystemWrites.call(() -> SystemWrites.transaction(() ->
|
||||
{
|
||||
com.kingsrook.qbits.edi.flow.OutboundActions.execute("edi_outbound_message", original, "resend");
|
||||
SystemWrites.update("sample_order", new QRecord().withValue("id", rows("sample_order").getFirst().getValue("id")).withValue("poNumber", "CHANGED-AFTER-SEND"));
|
||||
com.kingsrook.qbits.edi.flow.OutboundActions.execute("edi_outbound_message", original, "new");
|
||||
return (null);
|
||||
}));
|
||||
assertThat(rows("edi_outbound_message")).hasSize(2);
|
||||
assertThat(rows("edi_outbound_message").getFirst().getValueString("payloadRef")).isEqualTo(original.getValueString("payloadRef"));
|
||||
var rebuilt = rows("edi_outbound_message").getLast();
|
||||
try(var stream = EdiRuntime.current().payloads().open(new PayloadRef(rebuilt.getValueString("payloadRef"), null, 0)))
|
||||
{
|
||||
var parsed = new EdifactEnvelopeReader(10000).read(stream).getFirst();
|
||||
assertThat(parsed.allMessages().getFirst().segments().get(1).value(2)).isEqualTo("DEMO-1001");
|
||||
assertThat(parsed.allMessages().getFirst().reference()).isEqualTo("2");
|
||||
assertThat(parsed.unb().value(5)).isEqualTo("2");
|
||||
}
|
||||
SystemWrites.call(() ->
|
||||
{
|
||||
QRecord map = rows("edi_map").stream().filter(r -> "ORDERS".equals(r.getValueString("transactionSet"))).findFirst().orElseThrow();
|
||||
var payload = EdiRuntime.current().payloads().put(com.kingsrook.qbits.edi.storage.PayloadKind.SAMPLE, new ByteArrayInputStream(EdifactSupport.orders("300", "D96A").getBytes(StandardCharsets.UTF_8)));
|
||||
var lines = new com.kingsrook.qbits.edi.map.MapPreview().preview(map, new QRecord().withValue("payloadRef", payload.value()));
|
||||
assertThat(lines).isNotEmpty();
|
||||
assertThat(rows("sample_order")).hasSize(1);
|
||||
return (null);
|
||||
});
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** UN control levels allow fourteen digits without relaxing X12 bounds.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void controlLevels() throws Exception
|
||||
{
|
||||
QRecord agreement = setup();
|
||||
var allocator = EdiRuntime.current().controls();
|
||||
assertThat(allocator.allocate(agreement.getValueInteger("id"), null, "UNB", 99999999999998L, 99999999999999L, false)).isEqualTo(99999999999998L);
|
||||
assertThat(allocator.allocate(agreement.getValueInteger("id"), null, "UNB", 1, 99999999999999L, false)).isEqualTo(99999999999999L);
|
||||
assertThatThrownBy(() -> allocator.allocate(agreement.getValueInteger("id"), null, "UNB", 1, 99999999999999L, false)).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(allocator.allocate(agreement.getValueInteger("id"), null, "UNH", 1, 99999999999999L, true)).isEqualTo(1);
|
||||
assertThatThrownBy(() -> allocator.allocate(agreement.getValueInteger("id"), null, "ISA", 1, 99999999999999L, true)).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Decimal commas follow UNA in inbound mapping and outbound numeric serialization.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void decimalComma() throws Exception
|
||||
{
|
||||
H2Support.create();
|
||||
var input = EdifactSupport.read(EdifactSupport.orders("400", "D96A").replace("UNA:+.? '", "UNA:+,? '").replace("AAA:7.5", "AAA:7,5"));
|
||||
var guide = new BuiltInGuideLoader().resource("ORDERS-D96A");
|
||||
var message = input.allMessages().getFirst();
|
||||
assertThat(new GuideValidator().validate(message, guide, false)).isEmpty();
|
||||
var sample = RetailSupport.sample("ORDERS", "D96A");
|
||||
List<QRecord> rules = new ArrayList<>();
|
||||
sample.get("rules").forEach(r -> rules.add(RetailSupport.record(r)));
|
||||
var result = new InboundMapper(EdiRuntime.current().transforms()).map(message, guide, RetailSupport.record(sample.get("map")), rules);
|
||||
assertThat(result.record().getAssociatedRecords().get("lines").getFirst().getValueBigDecimal("unitPrice")).isEqualByComparingTo("7.5");
|
||||
var outbound = RetailSupport.sample("INVOIC", "D96A");
|
||||
rules.clear();
|
||||
outbound.get("rules").forEach(r -> rules.add(RetailSupport.record(r)));
|
||||
var invoice = new EdifactMapper(',').message(RetailSupport.source(), RetailSupport.record(outbound.get("map")), rules, "1");
|
||||
assertThat(new EdifactWriter().write(invoice.segments(), new EdifactSeparators(':', '+', ',', '?', ' ', '\''), "")).contains("PRI+AAA:7,5'");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Invalid data and message counts are rejected; unknown senders cannot trigger replies.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void rejectsInvalidAndUnknownDocuments() throws Exception
|
||||
{
|
||||
setup();
|
||||
EdifactSupport.receive(EdifactSupport.orders("500", "D96A").replace("QTY+21:2:PCE", "QTY+21:BAD:PCE"), "bad-value");
|
||||
EdifactSupport.receive(EdifactSupport.orders("501", "D96A").replace("UNT+14", "UNT+13"), "bad-count");
|
||||
EdifactSupport.receive(EdifactSupport.orders("502", "D96A").replace("LINDQVIST:14", "UNKNOWN:14"), "unknown");
|
||||
assertThat(new InboxWorker().runOnce()).isEqualTo(3);
|
||||
assertThat(rows("sample_order")).isEmpty();
|
||||
assertThat(rows("edi_transaction").stream().filter(r -> "ORDERS".equals(r.getValueString("transactionSet")))).allMatch(r -> "INVALID".equals(r.getValueString("status")));
|
||||
assertThat(rows("edi_outbound_message")).hasSize(2);
|
||||
assertThat(rows("edi_exception")).anyMatch(r -> "UNKNOWN_PARTNER".equals(r.getValueString("type")));
|
||||
for(var row : rows("edi_outbound_message"))
|
||||
{
|
||||
try(var stream = EdiRuntime.current().payloads().open(new PayloadRef(row.getValueString("payloadRef"), null, 0)))
|
||||
{
|
||||
String ack = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
assertThat(ack).contains("+4").doesNotContain("BAD");
|
||||
if(ack.contains("UCI+501+"))
|
||||
{
|
||||
assertThat(ack).contains("UCM+M1+ORDERS:D:96A:UN:EAN008+4+29'").doesNotContain("UCS+");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Invalid CONTRL references roll back reconciliation; receipt-only responses leave it pending.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void invalidAcknowledgements() throws Exception
|
||||
{
|
||||
QRecord agreement = setup();
|
||||
SystemWrites.call(() ->
|
||||
{
|
||||
var source = SystemWrites.transaction(() -> RetailSupport.saveSource("sample_order", RetailSupport.source()));
|
||||
OutboundEnqueue.enqueue("sample_order", source, "MANUAL", null);
|
||||
return (null);
|
||||
});
|
||||
new OutboundWorker().runOnce();
|
||||
QRecord sent = rows("edi_outbound_message").getFirst();
|
||||
String ackText = "UNA:+.? 'UNB+UNOC:3+LINDQVIST:14+QQQTEST:14+261014:1200+ACK1++++++1'UNH+A1+CONTRL:D:96A:UN'UCI+1+QQQTEST:14+LINDQVIST:14+7'UCM+BOGUS+ORDRSP:D:96A:UN+7'UNT+4+A1'UNZ+1+ACK1'";
|
||||
var invalid = EdifactSupport.read(ackText).allMessages().getFirst();
|
||||
QRecord received = new QRecord().withValue("id", rows("edi_transaction").getFirst().getValue("id"));
|
||||
assertThatThrownBy(() -> SystemWrites.call(() -> SystemWrites.transaction(() ->
|
||||
{
|
||||
new com.kingsrook.qbits.edi.edifact.ContrlReader().reconcile(invalid, agreement, received);
|
||||
return (null);
|
||||
}))).isNotNull();
|
||||
assertThat(rows("edi_functional_group").getFirst().getValueString("ackStatus")).isEqualTo("PENDING");
|
||||
assertThat(rows("edi_transaction").getFirst().getValueString("status")).isEqualTo("QUEUED");
|
||||
var receipt = EdifactSupport.read(ackText.replace("LINDQVIST:14+7", "LINDQVIST:14+8")).allMessages().getFirst();
|
||||
SystemWrites.call(() ->
|
||||
{
|
||||
new com.kingsrook.qbits.edi.edifact.ContrlReader().reconcile(receipt, agreement, received);
|
||||
return (null);
|
||||
});
|
||||
assertThat(rows("edi_interchange").getFirst().getValueString("status")).isEqualTo("ENVELOPED");
|
||||
assertThat(rows("edi_outbound_message").getFirst().getValueString("payloadRef")).isEqualTo(sent.getValueString("payloadRef"));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Build actual QQQ tables and install the same definitions used in the demo.
|
||||
*******************************************************************************/
|
||||
private QRecord setup() throws Exception
|
||||
{
|
||||
H2Support.create();
|
||||
RetailSupport.tables();
|
||||
EdiRuntime.current().initialize();
|
||||
return (SystemWrites.call(() -> EdifactSupport.install("D96A")));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Read test evidence with the service boundary.
|
||||
*******************************************************************************/
|
||||
private List<QRecord> rows(String table) throws Exception
|
||||
{
|
||||
return (SystemWrites.call(() -> EdiRecords.query(table, new QQueryFilter())));
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public final class RetailSupport
|
||||
{
|
||||
var table = new QTableMetaData().withName(name).withLabel(name).withBackendName(backend).withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withBackendName("id"));
|
||||
for(String field : List.of("poNumber", "sku", "shipmentNumber", "shipTime", "sscc", "invoiceNumber", "reportNumber"))
|
||||
for(String field : List.of("buyerId", "poNumber", "sku", "shipmentNumber", "shipTime", "sscc", "invoiceNumber", "reportNumber"))
|
||||
{
|
||||
table.withField(new QFieldMetaData(field, QFieldType.STRING).withBackendName(com.kingsrook.qqq.backend.core.instances.QInstanceEnricher.inferBackendName(field)));
|
||||
}
|
||||
|
||||
@@ -247,10 +247,50 @@ public class EdiDemoServer extends AbstractQQQApplication implements AutoCloseab
|
||||
logistics(local, agreements.get(1));
|
||||
seedAs2(local);
|
||||
healthcare();
|
||||
edifact();
|
||||
return (null);
|
||||
});
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Synthetic Swedish retailer exchanges ORDERS, three responses and CONTRL.
|
||||
*******************************************************************************/
|
||||
private void edifact() throws Exception
|
||||
{
|
||||
var agreement = com.kingsrook.qbits.edi.EdifactSupport.install("D96A");
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
com.kingsrook.qbits.edi.EdifactSupport.receive(com.kingsrook.qbits.edi.EdifactSupport.orders("L" + i, "D96A"), "lindqvist-orders-" + i);
|
||||
}
|
||||
new InboxWorker().runOnce();
|
||||
for(String table : List.of("sample_order", "sample_shipment", "sample_invoice"))
|
||||
{
|
||||
enqueue(table, RetailSupport.source().withValue("poNumber", "LINDQVIST-DEMO"));
|
||||
}
|
||||
new OutboundWorker().runOnce();
|
||||
new SendWorker().runOnce();
|
||||
var messages = EdiRecords.query("edi_outbound_message", EdiRecords.equal("agreementId", agreement.getValue("id")));
|
||||
for(var message : messages)
|
||||
{
|
||||
try(var stream = EdiRuntime.current().payloads().open(new PayloadRef(message.getValueString("payloadRef"), null, 0)))
|
||||
{
|
||||
var original = new com.kingsrook.qbits.edi.edifact.EdifactEnvelopeReader(10000).read(stream).getFirst();
|
||||
if(original.allMessages().stream().anyMatch(m -> "CONTRL".equals(m.type())))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var ack = new com.kingsrook.qbits.edi.edifact.ContrlBuilder().build(original, Map.of(), com.kingsrook.qbits.edi.ack.AckLevel.ALL_TRANSACTIONS, "ACK1", "D96A");
|
||||
var reply = com.kingsrook.qbits.edi.edifact.EdifactOutbound.wrap(agreement,
|
||||
OutboundWorker.row("edi_identity", agreement.getValue("partnerIdentityId")), OutboundWorker.row("edi_identity", agreement.getValue("localIdentityId")), List.of(ack),
|
||||
"REPLY" + message.getValue("id"), java.time.LocalDateTime.now(ZoneOffset.UTC));
|
||||
receive(new com.kingsrook.qbits.edi.edifact.EdifactWriter().write(reply), "lindqvist-contrl-" + message.getValue("id"));
|
||||
}
|
||||
}
|
||||
new InboxWorker().runOnce();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Synthetic clearinghouse claims, remittance and batch eligibility exchange.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -86,7 +86,7 @@ class EdiDemoServerTest
|
||||
demo.seed();
|
||||
demo.seed();
|
||||
assertThat(demo.defineQInstance().getHomeAppName()).isEqualTo("edi");
|
||||
assertThat(SystemWrites.call(() -> EdiRecords.query("edi_partner", new QQueryFilter()))).hasSize(6);
|
||||
assertThat(SystemWrites.call(() -> EdiRecords.query("edi_partner", new QQueryFilter()))).hasSize(7);
|
||||
assertThat(SystemWrites.call(() -> EdiRecords.query("edi_certificate", new QQueryFilter()))).hasSize(5);
|
||||
assertThat(SystemWrites.call(() -> EdiRecords.query("edi_inbox", new QQueryFilter()))).hasSizeGreaterThanOrEqualTo(40);
|
||||
assertThat(SystemWrites.call(() -> EdiRecords.query("edi_transaction", new QQueryFilter()))).anyMatch(row -> "OUTBOUND".equals(row.getValueString("direction")));
|
||||
@@ -94,11 +94,16 @@ class EdiDemoServerTest
|
||||
assertThat(SystemWrites.call(() -> EdiRecords.query("edi_functional_group", new QQueryFilter()))).anyMatch(row -> row.getValueInstant("ackReceivedAt") != null);
|
||||
assertThat(SystemWrites.call(() -> EdiRecords.query("edi_exception", new QQueryFilter()))).anyMatch(row -> "OVERDUE_ACK".equals(row.getValueString("type")));
|
||||
var partners = SystemWrites.call(() -> EdiRecords.query("edi_partner", new QQueryFilter()));
|
||||
assertThat(partners).extracting(row -> row.getValueString("code")).containsExactlyInAnyOrder("NORTHWIND", "HARBOR", "SUMMIT", "RIDGELINE", "PACIFIC", "CEDAR");
|
||||
assertThat(partners).extracting(row -> row.getValueString("code")).containsExactlyInAnyOrder("NORTHWIND", "HARBOR", "SUMMIT", "RIDGELINE", "LINDQVIST", "PACIFIC", "CEDAR");
|
||||
assertThat(partners).allSatisfy(row -> assertThat(row.getValueString("contactEmail")).endsWith("@example.invalid"));
|
||||
assertThat(SystemWrites.call(() -> EdiRecords.query("sample_wms_order", new QQueryFilter()))).hasSize(3);
|
||||
assertThat(SystemWrites.call(() -> EdiRecords.query("sample_shipment_status", new QQueryFilter()))).hasSize(3);
|
||||
var transactions = SystemWrites.call(() -> EdiRecords.query("edi_transaction", new QQueryFilter()));
|
||||
assertThat(transactions).anyMatch(row -> "ORDERS".equals(row.getValueString("transactionSet")) && "APPLIED".equals(row.getValueString("status")));
|
||||
for(String document : java.util.List.of("ORDRSP", "DESADV", "INVOIC"))
|
||||
{
|
||||
assertThat(transactions).anyMatch(row -> document.equals(row.getValueString("transactionSet")) && "ACKNOWLEDGED".equals(row.getValueString("status")));
|
||||
}
|
||||
assertThat(transactions).anyMatch(row -> "945".equals(row.getValueString("transactionSet")) && "SENT".equals(row.getValueString("status")));
|
||||
assertThat(transactions).anyMatch(row -> "204".equals(row.getValueString("transactionSet")) && "SENT".equals(row.getValueString("status")));
|
||||
assertThat(transactions).anyMatch(row -> "940".equals(row.getValueString("transactionSet")) && "INVALID".equals(row.getValueString("status")));
|
||||
|
||||
Reference in new issue
Block a user