Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66fe85886f | ||
|
|
9c5edd0813
|
||
|
|
7afc51089c |
No files matched your search
+57
-11
@@ -1,14 +1,60 @@
|
||||
version: 2.1
|
||||
setup: true
|
||||
|
||||
orbs:
|
||||
qqq-orb: kingsrook/qqq-orb@0.8
|
||||
qqq-orb: kingsrook/qqq-orb@0.6.8
|
||||
|
||||
commands:
|
||||
configure-package-install:
|
||||
steps:
|
||||
- run:
|
||||
name: Keep package installation unattended
|
||||
command: |
|
||||
# These build machines are discarded after the job.
|
||||
sudo install -d /etc/needrestart/conf.d
|
||||
printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null
|
||||
|
||||
workflows:
|
||||
setup:
|
||||
jobs:
|
||||
- qqq-orb/generate_pipeline:
|
||||
orb_version: '0.8'
|
||||
filters:
|
||||
branches:
|
||||
ignore: automation/qbit-rc-pending
|
||||
tags:
|
||||
only: /^v\d+\.\d+\.\d+$/
|
||||
test_only:
|
||||
jobs:
|
||||
- qqq-orb/mvn_test_only:
|
||||
pre-steps:
|
||||
- configure-package-install
|
||||
context: [qqq-maven-registry-credentials]
|
||||
filters:
|
||||
branches:
|
||||
ignore: /(develop|release\/.*)/
|
||||
|
||||
publish_snapshot:
|
||||
jobs:
|
||||
- qqq-orb/mvn_publish:
|
||||
pre-steps:
|
||||
- configure-package-install
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: snapshot
|
||||
filters:
|
||||
branches:
|
||||
only: [develop]
|
||||
|
||||
publish_release_candidate:
|
||||
jobs:
|
||||
- qqq-orb/mvn_publish:
|
||||
pre-steps:
|
||||
- configure-package-install
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: release_candidate
|
||||
filters:
|
||||
branches:
|
||||
only: [/release\/.*/]
|
||||
|
||||
publish_release:
|
||||
jobs:
|
||||
- qqq-orb/mvn_publish:
|
||||
pre-steps:
|
||||
- configure-package-install
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: release
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: [/^v\d+\.\d+\.\d+$/]
|
||||
@@ -1,7 +0,0 @@
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
jobs:
|
||||
maven:
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
|
||||
secrets: inherit
|
||||
@@ -1,23 +0,0 @@
|
||||
name: CodeQL
|
||||
'on':
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- release/**
|
||||
- hotfix/**
|
||||
pull_request: {}
|
||||
schedule:
|
||||
- cron: 41 16 * * 0
|
||||
workflow_dispatch: {}
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
security-events: write
|
||||
packages: read
|
||||
concurrency:
|
||||
group: codeql-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
jobs:
|
||||
analyze:
|
||||
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
|
||||
@@ -1,74 +1,25 @@
|
||||
# Cut the next release candidate (QRun-IO/qqq docs/QRUN-QBIT-REPO-STANDARD.md, C6):
|
||||
# run this workflow by hand, or merge a develop PR labelled `release:rc`. It merges develop into
|
||||
# the release line and the release/* push runs CircleCI publish_release_candidate.
|
||||
name: Release candidate
|
||||
'on':
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: Plan a cut without changing release state
|
||||
description: Print the plan without merging or creating branches
|
||||
type: boolean
|
||||
default: false
|
||||
finalize:
|
||||
description: Retry finalization of an existing cut; do not cut another RC
|
||||
type: boolean
|
||||
default: false
|
||||
recover:
|
||||
description: Recover the same published RC after a deployment timeout; never
|
||||
reupload
|
||||
type: boolean
|
||||
default: false
|
||||
circle_build:
|
||||
description: Original Circle publisher build number (recovery only)
|
||||
type: string
|
||||
default: ''
|
||||
merge_sha:
|
||||
description: Existing cut merge_sha (finalize retry only)
|
||||
type: string
|
||||
default: ''
|
||||
bump_sha:
|
||||
description: Existing cut bump_sha (finalize retry only)
|
||||
type: string
|
||||
default: ''
|
||||
branch:
|
||||
description: Existing cut branch (finalize retry only)
|
||||
type: string
|
||||
default: ''
|
||||
version:
|
||||
description: Existing cut version (finalize retry only)
|
||||
type: string
|
||||
default: ''
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- develop
|
||||
repository_dispatch:
|
||||
types:
|
||||
- qbit-rc-published
|
||||
types: [closed]
|
||||
branches: [develop]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
checks: read
|
||||
|
||||
jobs:
|
||||
release-rc:
|
||||
if: github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch'
|
||||
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
|
||||
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
|
||||
'release:rc'))
|
||||
uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@cc30f599e63c8e41629c99d23395bac1adb8bc44
|
||||
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release:rc'))
|
||||
uses: QRun-IO/.github/.github/workflows/qbit-release-rc.yml@main
|
||||
with:
|
||||
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
|
||||
}}
|
||||
finalize: ${{ github.event_name == 'repository_dispatch' || (github.event_name
|
||||
== 'workflow_dispatch' && inputs.finalize) }}
|
||||
recover: ${{ github.event_name == 'workflow_dispatch' && inputs.recover || false
|
||||
}}
|
||||
circle_build: ${{ github.event_name == 'workflow_dispatch' && inputs.circle_build
|
||||
|| '' }}
|
||||
merge_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.merge_sha
|
||||
|| inputs.merge_sha || '' }}
|
||||
bump_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.bump_sha
|
||||
|| inputs.bump_sha || '' }}
|
||||
branch: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch
|
||||
|| inputs.branch || '' }}
|
||||
version: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.version
|
||||
|| inputs.version || '' }}
|
||||
secrets: inherit
|
||||
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
profile: qbit
|
||||
orb_version: '0.8'
|
||||
default_branch: develop
|
||||
java_version: '21'
|
||||
browsers: true
|
||||
api_checks: false
|
||||
docs_only: false
|
||||
release_on_main: false
|
||||
release_tag_policy: stable
|
||||
test_branch_policy: non-release
|
||||
publish:
|
||||
snapshot: true
|
||||
release_candidate: true
|
||||
release: true
|
||||
asciidoc: false
|
||||
release_completion: true
|
||||
@@ -52,7 +52,7 @@ The package layout per phase is in `docs/DESIGN.md`.
|
||||
## Dependencies
|
||||
|
||||
Java 21; parent `com.kingsrook:qbit-build-parent:2.0.0`. Default-active `qqq-snapshot` imports QQQ
|
||||
`4.1.0-RC.6` ahead of the parent's 4.0.0 BOM (ADR-0007); move to each new RC within a week (G7).
|
||||
`4.1.0-RC.5` ahead of the parent's 4.0.0 BOM (ADR-0007); move to each new RC within a week (G7).
|
||||
Middleware Javalin is optional. Also StAEDI (X12) and as2-lib with BouncyCastle (AS2), all Apache-2.0/MIT-style licensed.
|
||||
|
||||
## Release
|
||||
|
||||
+1
-46
@@ -4,39 +4,10 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- EDI home is business-only: System, Dead letters, Certificates expiring and Setup status move to `EdiHome.ADMIN_WIDGET_NAMES` for the host's Admin home (gated to `edi.admin.hasAccess`, full width); every home widget now requires `edi.hasAccess` and is hidden without it. A new "Inbound documents are delayed" alert links to the delayed inbound documents once the oldest is older than `inboundDelayedAfterMinutes` (default 60). Setup counts that fail show "Unavailable" and are logged instead of -1.
|
||||
|
||||
- UI API extension edi 1.1.0 (requires QQQ 4.1.0-RC.15, UI API 1.6.0): operationIds, standard error responses with BasicErrorResponseV1, extension tags, and retained deprecated EdiErrorV1 schema.
|
||||
|
||||
- Derive qbit metadata version from the Maven artifact version, keeping release metadata and help bundle versions in sync.
|
||||
|
||||
- Require QQQ 4.1.0-RC.7 for core help-bundle registration and coverage validation.
|
||||
|
||||
- X12 routing and sending honor identity `isActive`, matching EDIFACT.
|
||||
|
||||
### Added
|
||||
|
||||
- Complete, host-neutral `help/qbit-edi.yaml` bundle registered with QQQ core: app, tables, fields, actions, widgets and possible-value sources, with a coverage gate and HOST overrides. Table/app fallback remains available without `helpContent`.
|
||||
- Setup status section on the EDI home (partner, identities, agreement, agreement documents, transport, certificates, guides, maps) with the next step; it counts only the active rows the runtime uses.
|
||||
- "Install sample maps" action on Maps: standard streamed ETL, insert-only by name, installs the 32 bundled synthetic maps as draft, unbound maps bound to the built-in guide.
|
||||
- `EdiTablesGuardrailTest`: every table is used at runtime, seeded or listed as starting empty, and has help.
|
||||
|
||||
- Phase f1 `edi` 1.0.0 spec'd UI API: guide tree, map target/transform metadata, encrypted base64 sample preview with unsaved rule overrides, and per-rule validation under `/qqq/v1/qbit/edi`.
|
||||
- Versioned `Edi...V1` schemas and generated OpenAPI fragment with the RC.6 contract/compatibility checks; real HTTP strict-mode permission, PHI, upload, isolation and validation tests.
|
||||
- Shared sandbox/record-action preview service, current/original guide selection, caller-session transforms, structured revision/expected diffs and value-free PHI diagnostics.
|
||||
- Transform registry argument schemas, including host-supplied schemas. Preview Map record actions now enforce the documented exact `edi.map.preview` permission through core's full-name permission checker.
|
||||
|
||||
- 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.
|
||||
|
||||
- Phase d warehouse and logistics baseline guides for 940, 945, 214 and 204 in 004010 and 005010 (syntax notes, situational control-total and stop rules, AT7 pairing), inbound/outbound sample maps (940 in, 945 out/in, 214 in, 204 in/out), DATEPART/TIMEPART outbound transforms and `+`-joined inbound paths for date/time pairs, and Harbor Logistics and Ridgeline Freight demo traffic.
|
||||
||||||| parent of bfb63ce (feat: phase c - AS2 transport)
|
||||
|
||||
- Phase c AS2: `As2Transport` send and receive (RFC 4130) with S/MIME signing (SHA-256/384/512; SHA1 flagged), encryption (AES-128/256 CBC or GCM; 3DES flagged), optional compression, synchronous and asynchronous signed MDNs with MIC verification, Test Connection, optional TLS pinning and VAN transports with no partner. Built on as2-lib 5.1.8 and BouncyCastle, entirely in memory (no keystore or temporary files).
|
||||
- `POST /qqq/hooks/edi/as2/{transport}` and `/{transport}/mdn` route provider: identity, decryption (ACTIVE or NEXT keys), signature and MIC checks, Message-ID dedupe returning the original MDN, error MDNs with no inbox row, and a queued, retried asynchronous MDN sender.
|
||||
@@ -67,22 +38,6 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic
|
||||
- Repository scaffold to the QRun qbit repository standard: `qbit-build-parent` 2.0.0, QQQ 4.1.0-RC.5, Maven wrapper, Checkstyle, JaCoCo gate, CircleCI qqq-orb 0.6.8, release-candidate workflow, Dependabot, community files.
|
||||
- `EdiQBitProducer` and `EdiQBitConfig` (table prefix `edi_`, permission base `edi`).
|
||||
|
||||
### Changed
|
||||
|
||||
- QQQ baseline is now 4.1.0-RC.6. EDI tables/fields and healthcare test fixtures use core `DataClassification` through `AiTableMetaData`/`AiFieldMetaData`, preserving PHI, PII, CONFIDENTIAL and PHI AI-exposure restrictions.
|
||||
- Exception Assign and new/reopened partner rejection/dead-letter exceptions send value-free core notifications after commit, with capturing-sink and PHI canary coverage.
|
||||
- Retain `PhiLogScope` for core #1147 until RC.7: RC.6 still logs RDBMS query filters and has no redaction hook or `FieldAccessHelper`. Contingent dependency-behavior APIs are also absent; shared-choice filters remain tested.
|
||||
|
||||
### Removed
|
||||
|
||||
- Reflective `EdiClassifications` RC.5 stand-in. File-gateway stand-ins remain until its API is published; the object-storage API integration is unchanged.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Shared-choice filters group partner-or-shared alternatives beneath an AND root, so core search criteria cannot admit another partner's transports.
|
||||
|
||||
- 999 now accepts SNIP 3+ findings with `E` instead of rejecting every ERROR. HIPAA acknowledgement selection follows the implementation version and enabled agreement expectation; outbound HIPAA ST carries its implementation identifier.
|
||||
- Qualified HL/NM1 anchors distinguish healthcare hierarchy levels; repeated loop names under different parents retain separate occurrences. Situational rules inherit direct ancestor segments without borrowing values from sibling occurrences.
|
||||
- Review/Result records expose identifiers only. A marked RC.5 PHI logging bridge prevents core upsert query diagnostics from copying claim/member values into logs.
|
||||
|
||||
- Inbound apply no longer fails with a null-pointer error on a target table that has no associations (flat tables such as the 214 status sample).
|
||||
@@ -52,7 +52,7 @@ The package layout per phase is in `docs/DESIGN.md`.
|
||||
## Dependencies
|
||||
|
||||
Java 21; parent `com.kingsrook:qbit-build-parent:2.0.0`. Default-active `qqq-snapshot` imports QQQ
|
||||
`4.1.0-RC.6` ahead of the parent's 4.0.0 BOM (ADR-0007); move to each new RC within a week (G7).
|
||||
`4.1.0-RC.5` ahead of the parent's 4.0.0 BOM (ADR-0007); move to each new RC within a week (G7).
|
||||
Middleware Javalin is optional. Also StAEDI (X12) and as2-lib with BouncyCastle (AS2), all Apache-2.0/MIT-style licensed.
|
||||
|
||||
## Release
|
||||
|
||||
@@ -1,40 +1,20 @@
|
||||
# QBit EDI
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
## Status
|
||||
|
||||
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
|
||||
Phases a1, a2, b and c implement inbound/outbound retail EDI 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.7+**, an RDBMS backend, and `com.kingsrook.qbits:qbit-edi:0.1.0-SNAPSHOT`.
|
||||
Java 21, QQQ **4.1.0-RC.5**, an RDBMS backend, and `com.kingsrook.qbits:qbit-edi:0.1.0-SNAPSHOT`.
|
||||
Apply [the PostgreSQL schema](docs/ddl/edi-postgres.sql). Targets and their associations must
|
||||
share the EDI backend so each transaction set can commit or roll back atomically.
|
||||
|
||||
@@ -57,26 +37,9 @@ a least-privilege `QSession` with target and association permissions. Applying w
|
||||
produces an APPLY exception: `no service session configured`.
|
||||
|
||||
The `edi` app has Activity/Setup sections, typed home widgets, record screens and standard
|
||||
Review/Result actions. Main tables have RC.6 search-field metadata; `EdiIntegrations.mainTables()`
|
||||
Review/Result actions. Main tables have RC.5 search-field metadata; `EdiIntegrations.mainTables()`
|
||||
also exposes the list for host search registration.
|
||||
|
||||
## In-app help
|
||||
|
||||
The producer registers [help/qbit-edi.yaml](src/main/resources/help/qbit-edi.yaml), a `QBIT`
|
||||
bundle versioned with this artifact. It covers the EDI app, tables, fields, actions, widgets,
|
||||
and possible-value sources. Live `{{...}}` references follow the current metadata.
|
||||
|
||||
Hosts can register their own `layer: HOST` bundle with `HelpBundles.register(qInstance,
|
||||
"help/my-host.yaml")`, using the same keys and roles to override selected entries. Enable
|
||||
core's `helpContent` provider with provenance fields, then call
|
||||
`QInstanceHelpContentManager.loadHelpContent(qInstance)` and `HelpBundles.syncOnStartup(qInstance)`
|
||||
after defining metadata. Core preserves user edits during sync. If you configure a different
|
||||
EDI app name, use that name in your host's app help key.
|
||||
|
||||
Registration is safe without `helpContent`; those hosts retain the table/app help fallback.
|
||||
`EdiHelpBundleTest` runs the core `HelpBundleAssertions` coverage gate and checks widget and
|
||||
possible-value-source coverage during `./mvnw verify`, without host-specific exclusions.
|
||||
|
||||
## Tables
|
||||
|
||||
There are **26 persisted tables**, whose SQL names equal their QQQ names.
|
||||
@@ -90,60 +53,9 @@ There are **26 persisted tables**, whose SQL names equal their QQQ names.
|
||||
| Storage/queue | `edi_payload`, `edi_outbound_request`, `edi_outbound_message` |
|
||||
|
||||
`edi_preview_download` is a **storage-only metadata alias**, not a SQL table. Supporting tables
|
||||
appear as child lists. Partner-scoped form choices use RC.6 contingent filters, including shared
|
||||
appear as child lists. Partner-scoped form choices use RC.5 contingent filters, including shared
|
||||
transports/guides and local certificates/identities.
|
||||
|
||||
## UI API
|
||||
|
||||
With optional Javalin present, the producer registers the `edi` extension, version `1.1.0`,
|
||||
under `/qqq/v1/qbit/edi`, requiring QQQ 4.1.0-RC.15 (UI API 1.6.0). All routes are endpoint specs and pass strict UI-route auditing.
|
||||
|
||||
| Method | Path after `/qqq/v1/qbit/edi` | Result |
|
||||
|---|---|---|
|
||||
| GET | `/guides/{guideId}/tree` | Nested loops, ordered segments, elements and up to 200 active codes per element |
|
||||
| GET | `/maps/{mapId}/target` | Target fields, direct associations, current rules and registered transform argument schemas |
|
||||
| POST | `/sandbox/preview` | Parse counts, positional findings, acknowledgement, mapped records and revision/expected diffs |
|
||||
| POST | `/maps/{mapId}/validate` | Per-rule path, transform and required-argument errors |
|
||||
|
||||
Preview accepts `{mapId, sampleId?, sampleBase64?, sampleName?, ruleOverrides?, guideRevision?}`.
|
||||
Supply exactly one sample source. Uploads are base64 JSON, at most **5 MiB decoded**, archived as
|
||||
encrypted `edi_map_sample` records through the same storage as Preview Map. No runtime files are
|
||||
created. Configure the host's Javalin `http.maxRequestSize` to at least 8 MiB to allow base64
|
||||
overhead and request metadata. Oversized decoded samples return 400; a smaller host limit returns 413.
|
||||
|
||||
`ruleOverrides` replaces the entire rule list in memory, including an empty list; it never saves
|
||||
rules. `CURRENT` (default) selects the newest active revision of the pinned guide's name, standard,
|
||||
version, transaction set and partner; `ORIGINAL` uses the map's pinned guide. Validate accepts
|
||||
`{rules: [...]}` and checks the pinned guide. Rule fields and `transformArgs` have the same shape
|
||||
as core table CRUD (`transformArgs` is a JSON object encoded as a string).
|
||||
|
||||
Every read enforces table permissions and partner locks with the caller's session, regardless of
|
||||
the host's default core-enforcement setting. Preview additionally requires **`edi.map.preview`**,
|
||||
shared with both Preview Map record actions. Reading target metadata requires target/association
|
||||
table read permissions; guide definitions, code lists, map rules and stored samples require their
|
||||
own table reads. LOOKUP and host transforms run as the caller, without service-account elevation.
|
||||
Only the internal PHI classification check and encrypted sample storage use the EDI system writer.
|
||||
|
||||
For PHI, `result` and `ack.text` are null and both diff lists are empty; findings retain positions
|
||||
and codes with generic messages. Mapping failures are value-free `MAP` findings. Preview never
|
||||
writes target records. Diffs contain differing document records as `{documentIndex, before, after}`;
|
||||
acknowledgements for multiple groups are concatenated, with `type: MIXED` for different ack types.
|
||||
|
||||
The mapper saves `edi_map_rule` through the **core table API**, using its normal
|
||||
`edi.map_rule.insert|edit|delete` permissions and record locks. There are no custom rule-write routes.
|
||||
The Next mapper page is separate phase-f work.
|
||||
|
||||
The generated contract is `src/main/resources/openapi/qqq-v1/edi.yaml`; extension component names are
|
||||
`Edi...V1`, alongside core `BasicErrorResponseV1`. `EdiErrorV1` remains deprecated for compatibility. Regenerate with the locally installed migration build:
|
||||
|
||||
```powershell
|
||||
./mvnw.cmd -o -B '-Dqqq.snapshot.version=4.1.99-SNAPSHOT' '-Dtest=EdiApiContractTest' '-Dedi.openapi.update=true' test
|
||||
```
|
||||
|
||||
Core provides `UiApiContract.check`, used to compare generated and checked-in YAML, validate
|
||||
operation IDs and references, and run `OpenApiCompatibilityChecker` against the committed fragment baseline. The extension also appears
|
||||
in the host's `/qqq/v1/openapi.yaml` and `/qqq/v1/version`.
|
||||
|
||||
## Inbound operation
|
||||
|
||||
`InboundSink.receive(InboundPayload)` stores encrypted bytes and SHA-256 before returning a durable
|
||||
@@ -154,10 +66,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 or UNH applies in its own transaction through QQQ
|
||||
actions with explicit permission enforcement. ISA15=T or UNB11=1 applies only with `applyTestDocuments`.
|
||||
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`.
|
||||
|
||||
Routing checks ISA or UNB identities, qualifiers, standard and usage. Linked gateway usernames must match
|
||||
Routing checks ISA 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.
|
||||
@@ -179,35 +91,11 @@ Outbound maps support nested association paths; inbound target paths retain the
|
||||
Preview shows parse counts, SNIP findings, the would-be ack, mapped JSON, expected-result and
|
||||
previous-revision comparisons. It writes no target records. PHI previews suppress document values.
|
||||
A session-authorized encrypted JSON report is downloadable. Select a stored sample or upload
|
||||
synthetic EDI on the standard Review step. Browser uploads require the host's RC.6 Javalin
|
||||
synthetic EDI on the standard Review step. Browser uploads require the host's RC.5 Javalin
|
||||
`uploadedFileArchiveTableName` to be `edi_preview_download` and multipart handling without disk
|
||||
staging. The alias encrypts streams and persists samples; this process accepts no filesystem archive.
|
||||
Samples must contain synthetic data only.
|
||||
|
||||
## Setup and starter data
|
||||
|
||||
No Liquibase changelog or host include is needed: the qbit seeds its own reference data. When the
|
||||
host calls `EdiRuntime.initialize()` at startup (after the schema exists) the 41 built-in guides
|
||||
load, with their segments, elements, rules, code lists and code values. It only changes a guide when
|
||||
the bundled content changes, so edits to your own rows are kept.
|
||||
|
||||
Everything else is host or runtime data, and every table explains itself when empty (help bundle
|
||||
`help/qbit-edi.yaml`). Set up in this order:
|
||||
|
||||
1. Partner.
|
||||
2. Identities: one of yours (no partner) and one per partner.
|
||||
3. Agreement: pairs your identity with the partner's, for a standard and test or production.
|
||||
4. Agreement documents: one per transaction set and direction, with the guide and optionally a map.
|
||||
5. Transport (AS2, SFTP, HTTPS), linked on the agreement. Certificates only for AS2 signing and encryption.
|
||||
|
||||
The **Setup status** widget (`edi_home_setup`, for the host's Admin home) shows each step, whether
|
||||
it is done (counting only the active rows the runtime uses) and the next step. A count that fails
|
||||
shows "Unavailable" and is logged.
|
||||
|
||||
Maps start empty. The **Install sample maps** action on Maps adds the 32 bundled synthetic examples
|
||||
as draft maps with no partner and no agreement document, by name and never over an existing map. Point
|
||||
a map at your own table, preview it, activate it, then select it on an agreement document.
|
||||
|
||||
## Permissions
|
||||
|
||||
Full table permissions include `edi.partner.read|insert|edit|delete`, `edi.map.*`,
|
||||
@@ -230,14 +118,7 @@ Full table permissions include `edi.partner.read|insert|edit|delete`, `edi.map.*
|
||||
| Scheduled workers / ESB enqueue | `edi.admin` |
|
||||
|
||||
Partner locks use `edi_partner` with `edi.admin` all-access. Home queries explicitly enforce
|
||||
the viewing user's permissions.
|
||||
|
||||
The EDI home shows business work only. Its widgets (`EdiHome.HOME_WIDGET_NAMES`) need
|
||||
`edi.hasAccess` and are hidden without it. "Inbound documents delayed" appears only when the oldest
|
||||
unprocessed inbound document is older than `inboundDelayedAfterMinutes` (default 60), with a link to
|
||||
those documents. System widgets (`EdiHome.ADMIN_WIDGET_NAMES`: System, Dead letters, Certificates
|
||||
expiring, Setup status) are not on the EDI home; they need `edi.admin.hasAccess`, are hidden without
|
||||
it, and a host places them on its Admin home. Processes are table/record actions, never navigation entries.
|
||||
the viewing user's permissions. Processes are table/record actions, never navigation entries.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -264,35 +145,29 @@ in memory. Database storage remains the default for demos/tests.
|
||||
|
||||
For object storage, add/install optional `qbit-object-storage:0.1.0-RC.2`, configure its bucket
|
||||
alias and select `OBJECT_STORAGE`. EDI uses only its public API and system access, with keys
|
||||
`edi/yyyy/MM/dd/kind/uuid`. Its transitive QQQ dependencies are excluded to retain RC.6.
|
||||
`edi/yyyy/MM/dd/kind/uuid`. Its transitive QQQ dependencies are excluded to retain RC.5.
|
||||
|
||||
Optional `qqq-esb` publishes setup/activity events after commit. Document events contain only ID
|
||||
and status because RC.6's standard factory otherwise copies PHI fields. Alert names are
|
||||
and status because RC.5's standard factory otherwise copies PHI fields. Alert names are
|
||||
`edi.inbox.dead-letter`, `edi.ack.overdue`, `edi.ack.rejected`, `edi.send.failed`,
|
||||
`edi.certificate.expiring`, `edi.certificate.expired` and `edi.mdn.overdue` (RC.6 requires dotted names).
|
||||
`edi.certificate.expiring`, `edi.certificate.expired` and `edi.mdn.overdue` (RC.5 requires dotted names).
|
||||
|
||||
Core `DataClassification` metadata marks PHI/PII/CONFIDENTIAL fields through `AiFieldMetaData`,
|
||||
with a CONFIDENTIAL table default through `AiTableMetaData`. PHI fields deny AI exposure.
|
||||
Hosts must supply secured-data protection, database encryption at rest and access policies for PHI columns; markers alone do not mask fields.
|
||||
RC.5 lacks the planned core `DataClassification` API. `EdiClassifications` marks PHI/PII/confidential
|
||||
fields and PHI fields deny AI exposure. Hosts must supply secured-data protection, database
|
||||
encryption at rest and access policies for PHI columns; markers alone do not mask fields.
|
||||
The home reports installed integration and storage state.
|
||||
|
||||
Assign sends a value-free `QAlerts.notify` notification to the assignee after commit. Newly opened
|
||||
or reopened REJECTED_ACK/DEAD_LETTER exceptions notify distinct assignees of OPEN/IN_PROGRESS
|
||||
exceptions for the same partner; these existing assignments define the partner's recipients.
|
||||
Repeated occurrences do not send another notification. Titles and bodies contain no document,
|
||||
partner or exception-summary values. Without an alerts integration, core uses its logging sink.
|
||||
|
||||
## Outbound and transports
|
||||
|
||||
Active OUTBOUND agreement documents select the source table, map, guide and trigger. RECORD_CHANGE
|
||||
uses core's instance listener and the writer's transaction. Source and EDI tables must share the
|
||||
backend. `triggerFilter` is a serialized QQQ `QQueryFilter`; MANUAL also honors that filter.
|
||||
RC.6 catches listener errors without failing the source write: successful enqueues roll back with
|
||||
RC.5 catches listener errors without failing the source write: successful enqueues roll back with
|
||||
the source transaction, but hosts must monitor failed listener execution.
|
||||
|
||||
`edi_outbound_enqueue` is the ESB boundary. Configure an RC.6 `EsbTrigger` to invoke it with
|
||||
`edi_outbound_enqueue` is the ESB boundary. Configure an RC.5 `EsbTrigger` to invoke it with
|
||||
`sourceTableName`, `sourceRecordId` and `eventId`; broker setup belongs to the host. Event IDs are
|
||||
unique per agreement document. RC.6's consumer runner uses JMS, so broker-free acceptance calls
|
||||
unique per agreement document. RC.5's consumer runner uses JMS, so broker-free acceptance calls
|
||||
this process directly.
|
||||
|
||||
`edi_outbound_worker` and `edi_send_worker` run every 15 seconds when a scheduler is configured.
|
||||
@@ -381,53 +256,6 @@ the agreement document `ackDueMinutes` (60 when unset).
|
||||
Existing databases need the new `edi_as2_mdn` table, `edi_outbound_message.transport_mic` and
|
||||
`edi_transport.weak_crypto` (see `docs/ddl/edi-postgres.sql`).
|
||||
|
||||
## HIPAA and PHI
|
||||
|
||||
Original **QQQ baseline guides** cover 837P (005010X222A1), 837I (005010X223A2),
|
||||
835 (005010X221A1), 270/271 (005010X279A1), 276/277 (005010X212), 834 (005010X220A1)
|
||||
and 999 (005010X231A1). These are in-house structure and usage baselines, not licensed TR3s.
|
||||
No TR3 text or full licensed code lists ship. Hosts may import their licensed guides.
|
||||
|
||||
SNIP 1–2 covers envelopes and the active guide. SNIP 3 uses exact decimals for claim charges,
|
||||
claim/line CAS adjustments and payments less signed PLB adjustments (zero tolerance). SNIP 4
|
||||
rules run per loop occurrence, including dependent patients, claim/line dates, diagnoses,
|
||||
subscriber demographics and benefits-or-rejection responses. SNIP 3+ findings produce `E` and
|
||||
an exception; SNIP 1–2 errors produce `R`. HIPAA replies use 999 when acknowledgement is enabled.
|
||||
SNIP 5 includes ICD-10 diagnosis shape, NPI Luhn with the 80840 prefix, and US ZIP shape.
|
||||
`ExternalCodeSetProvider.isValid(codeSet, code, asOf)` supplies licensed membership checks for
|
||||
EXTERNAL code lists; without it, only formats are checked. The System widget reports that state.
|
||||
|
||||
Healthcare agreement documents default to PHI and SNIP 4. Clinical keys go only to
|
||||
`edi_transaction.phiBusinessKey`; validation excerpts, acknowledgements, events, exceptions,
|
||||
alerts and process summaries contain no submitted clinical values. Healthcare sample maps and
|
||||
host tables live **only in test sources/resources** and use seeded synthetic patients and claims.
|
||||
Their NPI `1234567893` is a checksum test fixture, not an asserted provider identity.
|
||||
|
||||
**The host must encrypt the database at rest and should install qbit-secured-data.** Payloads
|
||||
are always AES-GCM encrypted; PHI columns rely on host database encryption. RC.6 core
|
||||
classification metadata marks PHI/PII/CONFIDENTIAL and denies PHI AI exposure, but does not
|
||||
provide secured-data field tiers. Without secured-data policies, a user with table access can
|
||||
read PHI columns.
|
||||
The home explicitly reports this limitation. Use record-level audits on host PHI target tables
|
||||
until core classified-value audit protection is available.
|
||||
|
||||
RC.6 still includes query criteria in RDBMS failure logs and has no logging redaction hook.
|
||||
The marked `PhiLogScope` bridge suppresses synchronous logger diagnostics during PHI mapping/application and preview; ordinary value-free EDI events
|
||||
remain persisted. It changes no logger levels and does not suppress other threads. The bridge
|
||||
tracks core #1147 (secured-data logging), with a removal/review deadline of 4.1.0-RC.7.
|
||||
Map samples use record-level audits to avoid copying expected-result PHI into audit details. Host code outside these scopes must protect its
|
||||
own logging, audits and asynchronous work.
|
||||
|
||||
Download Raw Document uses a session-bound process grant, rechecks `edi.raw.download` and record
|
||||
read/partner access at download time, streams decrypted bytes without local files, and records
|
||||
`RAW_DOWNLOADED` with the user and row references, never content. Download filenames are generic.
|
||||
|
||||
`PhiCanaryTest` scans every five-character marker window through gateway/HTTPS intake, forced
|
||||
SNIP 2/3/4 errors, mapping/apply, 999, replay/resend, Review/Result, alerts/notifications, DEBUG logs, all EDI
|
||||
query exports and all home/child widgets. FULL exports exempt only PHI-classified columns;
|
||||
RC.6 HIDDEN field-lock exports have no exemptions. MASKED tiers and the optional Quick Search
|
||||
qbit require the newer secured-data integration and are not claimed by these RC.6 tests.
|
||||
|
||||
## Build and verification
|
||||
|
||||
```bash
|
||||
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
# Upgrade notes
|
||||
|
||||
## Unreleased
|
||||
|
||||
- No schema change and no Liquibase include.
|
||||
- Behavior change: an inactive identity (`isActive` false) now stops X12 routing and sending, as it already did for EDIFACT. Inbound documents from or to it are rejected as an unknown partner, and outbound requests for its agreements wait until it is active. Identities default to active, so only identities someone switched off are affected.
|
||||
- Hosts that already call `EdiRuntime.initialize()` at startup keep getting the 41 built-in guides and code lists. A host that never called it will now see "Needs setup" for Guides and code lists on the home.
|
||||
- New widget "Setup status" (`edi_home_setup`).
|
||||
- Behavior change: the EDI home shows business widgets only (`EdiHome.HOME_WIDGET_NAMES`), each now requiring `edi.hasAccess` (hidden without it). `edi_home_system`, `edi_home_dead`, `edi_home_certificates` and `edi_home_setup` left the EDI home: they are listed in `EdiHome.ADMIN_WIDGET_NAMES`, require `edi.admin.hasAccess` (hidden without it), and the host places them on its Admin home.
|
||||
- New business widget `edi_home_delayed` ("Inbound documents are delayed"), shown only when the oldest unprocessed inbound document is older than the new config value `inboundDelayedAfterMinutes` (default 60).
|
||||
- New table action "Install sample maps" on Maps, permission `edi.admin`. It is never run automatically and only adds draft maps whose name does not exist yet.
|
||||
+1
-23
@@ -42,17 +42,9 @@ 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, Harbor, Ridgeline and Lindqvist have configured outbound documents; the healthcare scenario adds its own. The in-memory gateway
|
||||
retries are visible. Northwind alone has configured outbound documents. 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.
|
||||
@@ -66,17 +58,3 @@ descending creation date and ID. The home exception queue puts OPEN before IN_PR
|
||||
first within each status. RC.5 has no table-level default-sort metadata to override Next's sort.
|
||||
Inbound message labels use external IDs because filenames are classified as PHI.
|
||||
Light/dark visual layout and production scheduler/gateway deployment remain host acceptance.
|
||||
|
||||
Phase e adds a fifth partner, **Cedar Health Clearinghouse** (CEDAR, PHI enabled), after the existing
|
||||
retail and logistics scenarios. It sends seeded synthetic professional and institutional claims,
|
||||
a remittance, a professional claim with a one-dollar balancing error (SNIP 3), and a professional
|
||||
claim missing its dependent patient loop (SNIP 4). Both business-rule errors are accepted with
|
||||
errors (`E`) and open exceptions. Value-free 999s go to the in-memory clearinghouse outbox.
|
||||
A synthetic eligibility request goes out as a 270 and a matching-trace 271 arrives. Clinical keys
|
||||
appear only in the classified PHI key column, not the ordinary document key or record labels.
|
||||
|
||||
The healthcare sample maps and tables are test-only. Patients are seeded `Testpatient Alpha`
|
||||
fixtures, member IDs begin with `SYN`, and `1234567893` is a documented NPI checksum test value.
|
||||
The host database must be encrypted for real PHI; this disposable demo uses synthetic data only.
|
||||
The System widget shows format-only external code validation until a provider is registered.
|
||||
Inbox and interchange records offer the audited **Download Raw Document** action.
|
||||
+27
-116
@@ -130,7 +130,7 @@ Package layout (`com.kingsrook.qbits.edi`):
|
||||
|
||||
| Dependency | Scope | Why |
|
||||
|---|---|---|
|
||||
| QQQ `4.1.0-RC.6` (then each new RC, G7) | compile | core classification, `QAlerts.raise/resolve/notify` |
|
||||
| QQQ `4.1.0-RC.5` (then each new RC, G7) | compile | core, `QAlerts.raise/resolve` (RC.5) |
|
||||
| `qqq-middleware-javalin` | optional | hooks route provider; UI API extension (RC.6) |
|
||||
| `qqq-esb` | optional | outbound trigger from ESB table events |
|
||||
| `qqq-backend-module-filesystem` (SFTP) | compile | SFTP push and VAN polling. This is core's SFTP client, the one qbit-sftp-data-integration uses; EDI uses it directly instead of depending on that qbit (G1). Remote SFTP only; nothing local. |
|
||||
@@ -139,20 +139,13 @@ Package layout (`com.kingsrook.qbits.edi`):
|
||||
| `qbit-object-storage` (its `api` package only) | optional | raw payload storage (section 10) |
|
||||
| `qbit-file-gateway` (its `api` package only) | optional | inbound SFTP and partner-pull OUTBOX (section 5.3) |
|
||||
|
||||
Core features checked against the `v4.1.0-RC.6` tag:
|
||||
Core features that arrive in RC.6, and how EDI bridges until then:
|
||||
|
||||
| Need | Core | RC.6 status |
|
||||
| Need | Core | Until RC.6 |
|
||||
|---|---|---|
|
||||
| `DataClassification` on fields (PHI) | `actions.ai.DataClassification` (#1142) | Uses `AiFieldMetaData.withClassification` and CONFIDENTIAL `AiTableMetaData` defaults; classification stand-in removed, including healthcare test fixtures |
|
||||
| User notifications | `QAlerts.notify` (#1125) | Assign notifies the assignee; new/reopened rejection and dead-letter exceptions notify current partner assignees (section 4.4) |
|
||||
| UI API extension point | `MiddlewareSpecExtension` (#1128) | Available in core; sandbox remains a record action until phase f |
|
||||
| Contingent choice dependency behavior | `PossibleValueDependencyBehavior`, `possibleValueSourceDependsOn` | Neither API exists in the tag; existing filters retain shared choices before a partner is selected |
|
||||
| Secured-data tiers and log redaction | `FieldAccessHelper`, logging hook (#1147) | Spec only; no implementation in the tag. Retain `PhiLogScope`, `STAND-IN(core=QRun-IO/qqq#1147, removeBy=4.1.0-RC.7)` |
|
||||
|
||||
**RC.6 baseline:** classifications and notifications now use core APIs. The PHI logging bridge
|
||||
remains because RDBMS query failures still log filters and QLogger has no classified-value
|
||||
redaction hook; review/remove it by RC.7 under #1147. FULL and HIDDEN field-lock canaries run;
|
||||
MASKED tiers await `FieldAccessHelper`. Host logging/audits outside PHI scopes still need protection.
|
||||
| `DataClassification` on fields (PHI) | `actions.ai.DataClassification` (#1142) | `EdiClassifications` stand-in, `STAND-IN(core=QRun-IO/qqq#1142, removeBy=4.1.0-RC.6)`, sets the classification reflectively when the class is present |
|
||||
| User notifications | `QAlerts.notify` (#1125) | alerts only via `QAlerts.raise` (RC.5); notifications added with the RC.6 bump |
|
||||
| UI API extension point | `MiddlewareSpecExtension` (#1128) | the sandbox is a record action (section 8.3); spec'd endpoints come in phase f |
|
||||
|
||||
The file-gateway `api` ships in that qbit's phase (a). Until it is on a published SNAPSHOT, EDI uses
|
||||
`GatewayStandIn` with the identical shape, marked `STAND-IN(core=QRun-IO/qbit-file-gateway,
|
||||
@@ -254,11 +247,7 @@ One row per document, version and direction that the agreement carries.
|
||||
- When the ack arrives, `QAlerts.resolve("edi.ack.overdue", dedupKey, note)`.
|
||||
- A rejecting ack (AK9/IK9 `R`, or partial `P`) raises `edi.ack.rejected` (ERROR) and opens a
|
||||
`REJECTED_ACK` exception.
|
||||
- Assign sends the assignee a value-free `QAlerts.notify` notification after the update commits.
|
||||
New or reopened `REJECTED_ACK`/`DEAD_LETTER` exceptions notify distinct assignees of
|
||||
OPEN/IN_PROGRESS exceptions for the same partner (including the reopened row's assignee).
|
||||
Existing active exception assignments define partner recipients; no new assignment model is
|
||||
introduced. Coalesced repeat occurrences do not notify again. Rollbacks discard notifications.
|
||||
- From RC.6, assignees of exceptions also get a `QAlerts.notify` notification.
|
||||
|
||||
## 5. Transports
|
||||
|
||||
@@ -431,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 and EDIFACT)
|
||||
## 6. Parsing (X12 now, EDIFACT later)
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
@@ -444,15 +433,9 @@ 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 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.
|
||||
- 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.
|
||||
|
||||
## 7. Implementation guides as data
|
||||
|
||||
@@ -576,41 +559,16 @@ upload is stored as a sample), and the standard Review/Result lines show:
|
||||
5. the diff against the previous map revision's result and against `expectedResultJson`.
|
||||
|
||||
Nothing is written to target tables: the preview runs the whole pipeline with a no-op applier.
|
||||
Phase f1 provides the `edi` 1.0.0 `MiddlewareSpecExtension`, with component schemas named
|
||||
`Edi...V1` and a generated, contract-tested fragment at `openapi/qqq-v1/edi.yaml`:
|
||||
Phase f adds spec'd endpoints (`POST /qqq/v1/qbit/edi/sandbox/preview` and related) for the mapper
|
||||
UI.
|
||||
|
||||
- `GET /qqq/v1/qbit/edi/guides/{guideId}/tree`
|
||||
- `GET /qqq/v1/qbit/edi/maps/{mapId}/target`
|
||||
- `POST /qqq/v1/qbit/edi/sandbox/preview`
|
||||
- `POST /qqq/v1/qbit/edi/maps/{mapId}/validate`
|
||||
|
||||
Preview Map and the HTTP preview share `MapPreviewService`. HTTP preview takes `mapId`, exactly
|
||||
one of `sampleId`/`sampleBase64`, optional `sampleName`, a complete in-memory `ruleOverrides`
|
||||
replacement, and `guideRevision`. Uploads are limited to 5 MiB decoded and stored encrypted as
|
||||
samples; the host must allow at least 8 MiB HTTP bodies for base64 overhead. `CURRENT` (HTTP default)
|
||||
selects the newest active revision in the pinned guide family and partner; `ORIGINAL` uses the
|
||||
map's pinned guide, as the record action does.
|
||||
|
||||
The response contains parse counts, positional findings, acknowledgement type/text, mapped records
|
||||
and associations, structured per-document diffs, and `phiSuppressed`. PHI returns null result/ack
|
||||
text, empty diffs and generic finding messages. Mapping errors are value-free `MAP` findings.
|
||||
The preview action permission is exactly `edi.map.preview`, with no `.hasAccess` suffix.
|
||||
Guide, sample and rule reads explicitly enforce permissions and record locks; transforms run in
|
||||
the caller session. Only classification and encrypted sample storage use the internal system writer.
|
||||
|
||||
### 8.4 No-code mapper UI (phase f1 API built; Next page follows)
|
||||
### 8.4 No-code mapper UI (phase f; specified now, built later)
|
||||
A custom Next UI page (custom UIs may be nav entries):
|
||||
- left, the guide tree (loops, segments, elements with usage and code lists); right, the target
|
||||
table's fields and associations; drag to create a rule, then choose a transform;
|
||||
- a live preview pane on a chosen sample (the sandbox endpoint), with errors inline;
|
||||
- revisions: edit a DRAFT, activate, compare revisions;
|
||||
- reads and writes only through `/qqq/v1`: the guide-tree and map-target GETs above, the sandbox
|
||||
preview POST and the map validate POST; writes remain core CRUD on `edi_map_rule`, enforcing
|
||||
`edi.map_rule.insert|edit|delete` and record locks.
|
||||
- target metadata includes direct associations and transform argument JSON Schemas from the
|
||||
registry. Validate accepts `{rules: [...]}`, returns indexed per-rule errors against the pinned
|
||||
guide/target and never executes transforms or saves rules. Guide code lists return at most
|
||||
200 active codes per element.
|
||||
- reads and writes only through `/qqq/v1` (CRUD on `edi_map_rule` plus the extension endpoints).
|
||||
|
||||
## 9. Flows
|
||||
|
||||
@@ -792,13 +750,6 @@ Non-HIPAA documents validate at level 2 by default and HIPAA documents at level
|
||||
document can raise or lower its level.
|
||||
|
||||
### 11.4 External code sets
|
||||
|
||||
JSON element constraints support `source: "EXTERNAL"` and `externalCodeSet`. Normalized code-list
|
||||
rows preserve both properties. Register the provider with
|
||||
`EdiQBitConfig.withExternalCodeSetProvider(...)`; it is excluded from configuration JSON.
|
||||
Membership failures are positional SNIP 5 findings. The effective date uses a service date,
|
||||
then a payment date, otherwise the configured clock. Provider failures disclose no values.
|
||||
|
||||
`ExternalCodeSetProvider` (SPI: `boolean isValid(String codeSet, String code, LocalDate asOf)`),
|
||||
registered by the host, for example backed by a licensed table the host loads. Code lists with
|
||||
`source = EXTERNAL` delegate to it. Without a provider they check the format only, and the home says
|
||||
@@ -806,7 +757,7 @@ so.
|
||||
|
||||
## 12. HIPAA and PHI
|
||||
|
||||
- **Classification** (`DataClassification`, core #1142; native RC.6 metadata):
|
||||
- **Classification** (`DataClassification`, core #1142; the stand-in until RC.6):
|
||||
- PHI: `edi_transaction.phiBusinessKey`, `edi_transaction_error.valueExcerpt` (never written for
|
||||
PHI documents, but classified anyway), `edi_map_sample.expectedResultJson`, `edi_inbox.fileName`
|
||||
(file names often carry member or claim ids), and the payloads themselves (encrypted, never in a
|
||||
@@ -827,17 +778,13 @@ so.
|
||||
- **Minimum necessary:** PHI maps write only the mapped fields. The raw document stays encrypted and
|
||||
can be downloaded only with the record action "Download Raw Document" (permission
|
||||
`edi.raw.download`), which is audited and, with secured-data, written to its access log.
|
||||
- **Canary egress tests** (phase e, the core SECURED-DATA procedure): two randomized markers per
|
||||
patient-name, member-id, claim-number and diagnosis field run through gateway and HTTPS hooks,
|
||||
validation with SNIP 2/3/4 failures, 999, map/apply, exceptions, reprocess/resend, every home and
|
||||
child widget, every persisted `edi_` table's QueryAction export, Review/Result, captured alerts and notifications
|
||||
and an in-memory DEBUG log appender. Every five-character window is scanned, detecting all
|
||||
longer substrings. Only explicit PHI value/display-value columns are exempt for FULL exports;
|
||||
ciphertext bytes are checked for plaintext. RC.6 HIDDEN field locks are separately exercised
|
||||
without exemptions. The storage-only download alias has no query surface. MASKED tiers and
|
||||
the optional Quick Search index await their host/core integration; they are not simulated.
|
||||
Long diagnosis markers use a widened test-only guide element so accepted-with-error fixtures
|
||||
can also reach mapping/apply; separate malformed monetary elements force structural rejection.
|
||||
- **Canary egress tests** (phase e, the core SECURED-DATA procedure): synthetic 837P and 835
|
||||
documents carry `CANARY-PHI-<random>` in the patient name, member id, claim number and a
|
||||
diagnosis. They run through receive, validate (with forced errors), 999, map, apply, exception,
|
||||
reprocess, resend, the home widgets, exports of every `edi_` table, Quick Search, alerts and
|
||||
captured logs, as a FULL, a MASKED and a HIDDEN user. The test fails on any substring of the canary
|
||||
longer than 4 characters outside the encrypted payloads and the PHI-classified columns, and for the
|
||||
MASKED and HIDDEN users anywhere at all.
|
||||
- No real PHI anywhere in the repo: samples are synthetic, produced by a seeded generator.
|
||||
|
||||
## 13. App home and navigation
|
||||
@@ -978,6 +925,7 @@ queued acknowledgements are implemented. These RC.5 adaptations refine the execu
|
||||
Object storage uses only its published API.
|
||||
- Standard optional ESB publication follows RC.5. Document events use the core envelope/publisher
|
||||
with ID/status only because the standard factory copies every field, including PHI keys.
|
||||
- The a1 classification stand-in and host responsibility for PHI field protection remain unchanged.
|
||||
|
||||
Synthetic H2 verification covers permissions, association rollback, retry/stale claims, gateway
|
||||
storage failures, original/current replay, 997/999, ack reconciliation, previews and home renderers.
|
||||
@@ -1007,7 +955,7 @@ RC.5 adaptations implemented in this worktree:
|
||||
- `InboundApplier` assumed every target table has associations; a flat table (the 214 status sample) hit a null pointer. It is now null-safe.
|
||||
- The 214 sample map records the first LX event per transaction into a flat status table and always inserts (no match key), so repeated statuses form a history; a host that wants every LX event maps an association.
|
||||
- Demo: Harbor Logistics gets a gateway OUTBOX transport for 945s (the partner pulls over SFTP, per 5.3) after the retail seed has run, so its earlier acknowledgements keep their visible retries; the fourth partner Ridgeline Freight is added, so `EdiDemoServerTest` now expects four partners.
|
||||
|
||||
||||||| parent of bfb63ce (feat: phase c - AS2 transport)
|
||||
|
||||
## Phase c implementation notes (2026-10-14)
|
||||
|
||||
@@ -1029,40 +977,3 @@ RC.5 adaptations implemented in this worktree:
|
||||
`lastError` instead of a generic text.
|
||||
- Not implemented: EC and RSA-PSS signing keys (RSA only), MIC comparison under a different algorithm than
|
||||
the one requested, CAQH/Peppol profiles, and AS2 over a host-mounted client certificate (`MTLS`).
|
||||
|
||||
|
||||
## Phase e implementation notes (2026-10-14)
|
||||
|
||||
- See the RC.6 baseline in section 3. Map samples use record-level audits because expected-result
|
||||
JSON can contain PHI. Raw downloads use a session-bound storage grant and an `edi_event` audit;
|
||||
a secured-data access log remains host integration work.
|
||||
- Situational rules bind to their occurrence and inherit only direct ancestor segments, so a
|
||||
sibling line's service date cannot satisfy another line. Fixed loop-head qualifiers distinguish
|
||||
HL/NM1 roles; the same 2300/2400 names can occur under subscriber and patient parents.
|
||||
- The 271 baseline requires benefits or an AAA rejection for a traced response; it does not
|
||||
perform cross-document 270/271 correlation. Provider differences that cannot be inferred from
|
||||
the submitted document belong in a companion guide or host rule; no rendering provider is
|
||||
invented from an absent identifier.
|
||||
- Healthcare maps and sample target tables are test-only, per the phase-e scope. The 837 sample
|
||||
map is a single-claim example with service-line associations; the validator supports multiple
|
||||
claims and checks each independently. The 835 and 834 samples map multiple claim/member
|
||||
associations. These are baseline examples, not a complete licensed TR3 implementation.
|
||||
- Child-list widgets explicitly enforce their nested queries on RC.5; the generic renderer
|
||||
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.6
|
||||
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.
|
||||
+1
-28
@@ -1,6 +1,6 @@
|
||||
# EDI guide JSON
|
||||
|
||||
The files in `src/main/resources/edi/guides/` are original QQQ baseline guides, not licensed
|
||||
The five 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,30 +49,3 @@ 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.
|
||||
+3
-16
@@ -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 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.
|
||||
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.
|
||||
|
||||
## Warehouse and logistics samples
|
||||
|
||||
@@ -76,16 +76,3 @@ 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,7 +3,6 @@ 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,
|
||||
@@ -20,13 +19,11 @@ 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)
|
||||
);
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<properties>
|
||||
<qqq.version>4.1.0-RC.26</qqq.version> <!-- feeds qbit-build-parent's qqq-bom-pom import -->
|
||||
<revision>0.1.0-SNAPSHOT</revision>
|
||||
<revision>0.1.0-RC.2</revision>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<jacoco.version>0.8.15</jacoco.version>
|
||||
@@ -28,7 +27,7 @@
|
||||
<parent>
|
||||
<groupId>com.kingsrook</groupId>
|
||||
<artifactId>qbit-build-parent</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<version>2.0.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
@@ -69,7 +68,7 @@
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-frontend-next</artifactId><version>1.0.0-RC.38</version><scope>test</scope></dependency>
|
||||
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-frontend-next</artifactId><version>1.0.0-RC.14</version><scope>test</scope></dependency>
|
||||
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk18on</artifactId><version>1.85</version></dependency>
|
||||
<dependency><groupId>com.helger.as2</groupId><artifactId>as2-lib</artifactId><version>5.1.8</version></dependency>
|
||||
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-backend-module-filesystem</artifactId></dependency>
|
||||
@@ -81,7 +80,7 @@
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
<artifactId>qbit-object-storage</artifactId>
|
||||
<version>0.1.0-RC.10</version>
|
||||
<version>0.1.0-RC.2</version>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion><groupId>com.kingsrook.qqq</groupId><artifactId>*</artifactId></exclusion>
|
||||
@@ -127,36 +126,7 @@
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<!-- help bundles carry the artifact version (QQQ help bundle provenance) -->
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>help/*.yaml</include>
|
||||
<include>qbit-edi.properties</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<excludes>
|
||||
<exclude>help/*.yaml</exclude>
|
||||
<exclude>qbit-edi.properties</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<!-- the artifact version, for the help bundle version test -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables combine.children="append">
|
||||
<qbit.artifactVersion>${project.version}</qbit.artifactVersion>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
@@ -243,7 +213,7 @@
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<qqq.snapshot.version>4.1.0-RC.26</qqq.snapshot.version>
|
||||
<qqq.snapshot.version>4.1.0-RC.5</qqq.snapshot.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
@@ -55,26 +55,6 @@ public class EdiQBitConfig implements QBitConfig
|
||||
return (java.util.Set.copyOf(sendAsEdiTables));
|
||||
}
|
||||
|
||||
private com.kingsrook.qbits.edi.guide.ExternalCodeSetProvider externalCodeSetProvider;
|
||||
|
||||
/*******************************************************************************
|
||||
** Optional host provider for licensed external code sets.
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public com.kingsrook.qbits.edi.guide.ExternalCodeSetProvider getExternalCodeSetProvider()
|
||||
{
|
||||
return (externalCodeSetProvider);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Register a provider; null retains format-only validation.
|
||||
*******************************************************************************/
|
||||
public EdiQBitConfig withExternalCodeSetProvider(com.kingsrook.qbits.edi.guide.ExternalCodeSetProvider value)
|
||||
{
|
||||
externalCodeSetProvider = value;
|
||||
return (this);
|
||||
}
|
||||
|
||||
private String payloadBucketAlias = "edi";
|
||||
private java.util.function.Supplier<com.kingsrook.qqq.backend.core.model.session.QSession> serviceSessionSupplier = null;
|
||||
private java.time.Clock clock = java.time.Clock.systemUTC();
|
||||
@@ -89,7 +69,6 @@ public class EdiQBitConfig implements QBitConfig
|
||||
private String appLabel = "EDI";
|
||||
private String parentAppName;
|
||||
private String publicBaseUrl;
|
||||
private Integer inboundDelayedAfterMinutes = 60;
|
||||
|
||||
|
||||
|
||||
@@ -115,10 +94,6 @@ public class EdiQBitConfig implements QBitConfig
|
||||
{
|
||||
errors.add("secretsResolver and partnerLocks are required");
|
||||
}
|
||||
if(inboundDelayedAfterMinutes == null || inboundDelayedAfterMinutes < 1)
|
||||
{
|
||||
errors.add("inboundDelayedAfterMinutes must be at least 1");
|
||||
}
|
||||
if(appName == null || appName.isBlank())
|
||||
{
|
||||
errors.add("appName is required");
|
||||
@@ -395,22 +370,4 @@ public class EdiQBitConfig implements QBitConfig
|
||||
publicBaseUrl = value;
|
||||
return (this);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** How old (in minutes) the oldest unprocessed inbound document may get before
|
||||
** the EDI home says "Inbound documents are delayed". Default 60.
|
||||
*******************************************************************************/
|
||||
public Integer getInboundDelayedAfterMinutes()
|
||||
{
|
||||
return (inboundDelayedAfterMinutes);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Set the inbound delay threshold in minutes.
|
||||
*******************************************************************************/
|
||||
public EdiQBitConfig withInboundDelayedAfterMinutes(Integer value)
|
||||
{
|
||||
inboundDelayedAfterMinutes = value;
|
||||
return (this);
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,9 @@
|
||||
package com.kingsrook.qbits.edi;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.bundles.HelpBundles;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaDataProducer;
|
||||
|
||||
@@ -40,41 +36,13 @@ public class EdiQBitProducer implements QBitMetaDataProducer<EdiQBitConfig>
|
||||
{
|
||||
public static final String GROUP_ID = "com.kingsrook.qbits";
|
||||
public static final String ARTIFACT_ID = "qbit-edi";
|
||||
public static final String VERSION = loadVersion();
|
||||
public static final String VERSION = "0.1.0";
|
||||
|
||||
private EdiQBitConfig qBitConfig;
|
||||
private String namespace;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Read the artifact version from the Maven-filtered resource.
|
||||
*******************************************************************************/
|
||||
private static String loadVersion()
|
||||
{
|
||||
try(InputStream inputStream = EdiQBitProducer.class.getClassLoader().getResourceAsStream("qbit-edi.properties"))
|
||||
{
|
||||
if(inputStream == null)
|
||||
{
|
||||
throw new IllegalStateException("Missing qbit-edi.properties; build with Maven to supply the artifact version");
|
||||
}
|
||||
Properties properties = new Properties();
|
||||
properties.load(inputStream);
|
||||
String version = properties.getProperty("version");
|
||||
if(version == null || version.isBlank() || version.contains("${"))
|
||||
{
|
||||
throw new IllegalStateException("Missing or unfiltered version in qbit-edi.properties; build with Maven");
|
||||
}
|
||||
return (version);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
throw new IllegalStateException("Could not read the artifact version from qbit-edi.properties", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent configuration setter.
|
||||
*******************************************************************************/
|
||||
@@ -162,16 +130,6 @@ public class EdiQBitProducer implements QBitMetaDataProducer<EdiQBitConfig>
|
||||
MetaDataProducerMultiOutput output = QBitMetaDataProducer.super.produce(instance);
|
||||
output.addSelfToInstance(instance);
|
||||
EdiRuntime.install(instance, qBitConfig);
|
||||
// Registration is metadata-only; core startup sync skips hosts without helpContent.
|
||||
HelpBundles.register(instance, "help/qbit-edi.yaml");
|
||||
// The app name is configurable; its bundle key must follow the installed app.
|
||||
HelpBundles.of(instance).getBundles().getLast().getEntries().stream()
|
||||
.filter(entry -> "app:edi;slot:help".equals(entry.getKey()))
|
||||
.forEach(entry -> entry.setKey("app:" + qBitConfig.getAppName() + ";slot:help"));
|
||||
if(instance.getTable("helpContent") == null)
|
||||
{
|
||||
com.kingsrook.qbits.edi.metadata.EdiHelp.apply(instance, qBitConfig.getAppName());
|
||||
}
|
||||
instance.withSupplementalMetaData(new com.kingsrook.qbits.edi.metadata.EdiRecordLabels());
|
||||
instance.withRecordChangeListener(new com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference(com.kingsrook.qbits.edi.flow.OutboundEnqueue.class));
|
||||
instance.withSupplementalMetaData(new com.kingsrook.qbits.edi.transport.EdiTransportPluginsMetaData()
|
||||
@@ -180,7 +138,6 @@ public class EdiQBitProducer implements QBitMetaDataProducer<EdiQBitConfig>
|
||||
.withPlugin(new com.kingsrook.qbits.edi.as2.As2Transport()).withPlugin(new com.kingsrook.qbits.edi.transport.UnavailableTransport("EMAIL")));
|
||||
try
|
||||
{
|
||||
com.kingsrook.qbits.edi.api.EdiApi.register(instance);
|
||||
com.kingsrook.qqq.middleware.javalin.QJavalinMetaData.ofOrWithNew(instance).withAdditionalRouteProviderReference(
|
||||
new com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference(com.kingsrook.qbits.edi.transport.HttpsRouteProvider.class))
|
||||
.withAdditionalRouteProviderReference(new com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference(com.kingsrook.qbits.edi.as2.As2RouteProvider.class));
|
||||
|
||||
@@ -53,7 +53,7 @@ public final class EdiRuntime
|
||||
private static final Map<QInstance, EdiRuntime> INSTANCES = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private final X12EnvelopeReader reader;
|
||||
private final X12Writer writer = new X12Writer();
|
||||
private final GuideValidator validator;
|
||||
private final GuideValidator validator = new GuideValidator();
|
||||
private final BuiltInGuideLoader guides = new BuiltInGuideLoader();
|
||||
private final com.kingsrook.qbits.edi.storage.PayloadStore payloads;
|
||||
private final EdiQBitConfig config;
|
||||
@@ -64,7 +64,6 @@ public final class EdiRuntime
|
||||
private final Ack999Builder ack999 = new Ack999Builder();
|
||||
private final TA1Builder ta1 = new TA1Builder();
|
||||
private final InboundAckReader acknowledgements = new InboundAckReader();
|
||||
private final com.kingsrook.qbits.edi.flow.OutboundSourceTables outboundSources = new com.kingsrook.qbits.edi.flow.OutboundSourceTables();
|
||||
|
||||
/*******************************************************************************
|
||||
** Construct services without resolving keys until content is stored or opened.
|
||||
@@ -72,7 +71,6 @@ public final class EdiRuntime
|
||||
private EdiRuntime(QInstance instance, EdiQBitConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
validator = new GuideValidator(config);
|
||||
reader = new X12EnvelopeReader(config.getMaxSegmentsPerTransaction());
|
||||
payloads = "OBJECT_STORAGE".equals(config.getPayloadStore())
|
||||
? new com.kingsrook.qbits.edi.storage.ObjectStoragePayloadStore(new PayloadCrypto(config.getSecretsResolver()), config.getPayloadBucketAlias(), config.getClock())
|
||||
@@ -96,22 +94,6 @@ public final class EdiRuntime
|
||||
return (Objects.requireNonNull(INSTANCES.get(QContext.getQInstance()), "EDI runtime has not been installed"));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Services for the current QQQ context, or null when none are installed.
|
||||
*******************************************************************************/
|
||||
public static EdiRuntime find()
|
||||
{
|
||||
return (QContext.getQInstance() == null ? null : INSTANCES.get(QContext.getQInstance()));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Cached source tables that have active RECORD_CHANGE outbound definitions.
|
||||
*******************************************************************************/
|
||||
public com.kingsrook.qbits.edi.flow.OutboundSourceTables outboundSources()
|
||||
{
|
||||
return (outboundSources);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Call at host startup after DDL, with the service session established.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -57,7 +57,7 @@ final class FunctionalAckBuilder
|
||||
for(X12TransactionSet transaction : group.transactions())
|
||||
{
|
||||
List<ValidationFinding> errors = findings.getOrDefault(transaction.st().value(2), List.of());
|
||||
boolean rejected = !transaction.findings().isEmpty() || errors.stream().anyMatch(f -> "ERROR".equals(f.severity()) && f.snipLevel() <= 2);
|
||||
boolean rejected = !transaction.findings().isEmpty() || errors.stream().anyMatch(f -> "ERROR".equals(f.severity()));
|
||||
String status = rejected ? "R" : errors.isEmpty() ? "A" : "E";
|
||||
if(!rejected)
|
||||
{
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.cfg.CoercionAction;
|
||||
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.type.LogicalType;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.BasicOperation;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.BasicResponse;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.extensions.AbstractJsonEndpointSpec;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.extensions.MiddlewareSpecExtension;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.extensions.MiddlewareSpecExtensionsMetaData;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.BasicErrorResponseV1;
|
||||
import com.kingsrook.qqq.openapi.model.HttpMethod;
|
||||
import com.kingsrook.qqq.openapi.model.In;
|
||||
import com.kingsrook.qqq.openapi.model.Parameter;
|
||||
import com.kingsrook.qqq.openapi.model.Schema;
|
||||
import com.kingsrook.qqq.openapi.model.Type;
|
||||
import io.javalin.http.HttpStatus;
|
||||
|
||||
/*******************************************************************************
|
||||
** The EDI 1.1.0 UI API: four spec'd operations in the default qbit namespace.
|
||||
*******************************************************************************/
|
||||
public final class EdiApi
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Static registration only.
|
||||
*******************************************************************************/
|
||||
private EdiApi()
|
||||
{
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Install with optional Javalin from the qbit producer.
|
||||
*******************************************************************************/
|
||||
public static void register(QInstance instance)
|
||||
{
|
||||
MiddlewareSpecExtensionsMetaData.ofOrWithNew(instance).withExtension(extension());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Shared descriptor for route binding and the committed contract.
|
||||
*******************************************************************************/
|
||||
public static MiddlewareSpecExtension extension()
|
||||
{
|
||||
return (new MiddlewareSpecExtension().withName("edi").withSemanticVersion("1.1.0")
|
||||
.withDescription("EDI sandbox and mapper, using the signed-in user's permissions.")
|
||||
.withSpec(GuideTreeSpec.class).withSpec(MapTargetSpec.class).withSpec(PreviewSpec.class).withSpec(ValidateSpec.class));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Common typed JSON contract; core owns authentication and error mapping.
|
||||
*******************************************************************************/
|
||||
public abstract static class EdiSpec extends AbstractJsonEndpointSpec
|
||||
{
|
||||
private final String path;
|
||||
private final String response;
|
||||
private final String request;
|
||||
private final String summary;
|
||||
|
||||
/*******************************************************************************
|
||||
** Immutable operation metadata.
|
||||
*******************************************************************************/
|
||||
protected EdiSpec(String path, String response, String request, String summary)
|
||||
{
|
||||
this.path = path;
|
||||
this.response = response;
|
||||
this.request = request;
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Publish the method, path and permission expectations.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public BasicOperation defineBasicOperation()
|
||||
{
|
||||
return (new BasicOperation().withPath("/qbit/edi" + path).withHttpMethod(request == null ? HttpMethod.GET : HttpMethod.POST)
|
||||
.withShortSummary(summary).withLongDescription(summary + ". Uses the caller session and table read permissions. Preview also requires edi.map.preview. Rule writes use core table CRUD."));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Declare positive integer record identifiers.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<Parameter> defineRequestParameters()
|
||||
{
|
||||
String name = path.contains("{guideId}") ? "guideId" : path.contains("{mapId}") ? "mapId" : null;
|
||||
return (name == null ? List.of() : List.of(new Parameter().withName(name).withIn(In.PATH).withRequired(true)
|
||||
.withDescription("Positive record identifier.").withSchema(new Schema().withType(Type.INTEGER))));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** All schemas use the Edi prefix and V1 suffix.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Map<String, Schema> defineComponentSchemas()
|
||||
{
|
||||
return (path.startsWith("/guides/") ? EdiSchemas.all() : Map.of());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Response component name.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected String getResponseSchemaName()
|
||||
{
|
||||
return (response);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Optional request body contract.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected Schema defineRequestBodySchema()
|
||||
{
|
||||
return (request == null ? null : EdiSchemas.ref(request));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Core middleware errors are explicitly documented.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<BasicResponse> defineAdditionalBasicResponses()
|
||||
{
|
||||
return (List.of(new BasicResponse(HttpStatus.BAD_REQUEST, "Invalid request or sample exceeds 5 MiB", BasicErrorResponseV1.class.getSimpleName()),
|
||||
new BasicResponse(HttpStatus.UNAUTHORIZED, "Sign in required", BasicErrorResponseV1.class.getSimpleName()),
|
||||
new BasicResponse(HttpStatus.FORBIDDEN, "Permission denied", BasicErrorResponseV1.class.getSimpleName()),
|
||||
new BasicResponse(HttpStatus.NOT_FOUND, "Missing or invisible record", BasicErrorResponseV1.class.getSimpleName()),
|
||||
new BasicResponse(HttpStatus.CONTENT_TOO_LARGE, "Host HTTP request size limit exceeded", BasicErrorResponseV1.class.getSimpleName())));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Reject malformed or overflowing identifiers without echoing request content.
|
||||
*******************************************************************************/
|
||||
protected Integer id(JsonInput input, String name) throws QBadRequestException
|
||||
{
|
||||
try
|
||||
{
|
||||
int value = Integer.parseInt(input.getPathParam(name));
|
||||
if(value < 1)
|
||||
{
|
||||
throw (new NumberFormatException());
|
||||
}
|
||||
return (value);
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
throw (new QBadRequestException("A positive integer identifier is required"));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Strict scalar typing prevents coercion of unsaved edits and sample identifiers.
|
||||
*******************************************************************************/
|
||||
protected <T> T body(JsonInput input, Class<T> type) throws QBadRequestException
|
||||
{
|
||||
ObjectMapper mapper = JsonMapper.builder().disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
|
||||
.disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT).build();
|
||||
mapper.coercionConfigFor(LogicalType.Textual).setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
|
||||
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail).setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail);
|
||||
try
|
||||
{
|
||||
if(input.getBody() == null)
|
||||
{
|
||||
throw (new IllegalArgumentException("Object body required"));
|
||||
}
|
||||
return (mapper.convertValue(input.getBody(), type));
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
throw (new QBadRequestException("Invalid body; check property names and types"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Read the guide tree.
|
||||
*******************************************************************************/
|
||||
public static class GuideTreeSpec extends EdiSpec
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Public constructor for core spec discovery.
|
||||
*******************************************************************************/
|
||||
public GuideTreeSpec()
|
||||
{
|
||||
super("/guides/{guideId}/tree", "EdiGuideTreeV1", null, "Read the guide tree");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute with the authenticated caller.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected Object handle(JsonInput input) throws Exception
|
||||
{
|
||||
return (new EdiApiService().tree(id(input, "guideId")));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Read target metadata and map rules.
|
||||
*******************************************************************************/
|
||||
public static class MapTargetSpec extends EdiSpec
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Public constructor for core spec discovery.
|
||||
*******************************************************************************/
|
||||
public MapTargetSpec()
|
||||
{
|
||||
super("/maps/{mapId}/target", "EdiTargetV1", null, "Read target metadata and map rules");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute with the authenticated caller.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected Object handle(JsonInput input) throws Exception
|
||||
{
|
||||
return (new EdiApiService().target(id(input, "mapId")));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Preview an encrypted sample with optional unsaved rules.
|
||||
*******************************************************************************/
|
||||
public static class PreviewSpec extends EdiSpec
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Public constructor for core spec discovery.
|
||||
*******************************************************************************/
|
||||
public PreviewSpec()
|
||||
{
|
||||
super("/sandbox/preview", "EdiPreviewV1", "EdiPreviewRequestV1", "Preview an encrypted sample with optional unsaved rules");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute with the authenticated caller.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected Object handle(JsonInput input) throws Exception
|
||||
{
|
||||
return (new EdiApiService().preview(body(input, EdiSchemas.EdiPreviewRequestV1.class)));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Validate unsaved map rules.
|
||||
*******************************************************************************/
|
||||
public static class ValidateSpec extends EdiSpec
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Public constructor for core spec discovery.
|
||||
*******************************************************************************/
|
||||
public ValidateSpec()
|
||||
{
|
||||
super("/maps/{mapId}/validate", "EdiValidationV1", "EdiValidateRequestV1", "Validate unsaved map rules");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute with the authenticated caller.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected Object handle(JsonInput input) throws Exception
|
||||
{
|
||||
return (new EdiApiService().validate(id(input, "mapId"), body(input, EdiSchemas.EdiValidateRequestV1.class)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kingsrook.qbits.edi.EdiRuntime;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiAssociationV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiElementV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiFieldV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiGuideTreeV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiLoopV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiPreviewRequestV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiPreviewV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiRuleErrorsV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiRuleV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiSegmentV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiTargetV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiTransformV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiValidateRequestV1;
|
||||
import com.kingsrook.qbits.edi.api.EdiSchemas.EdiValidationV1;
|
||||
import com.kingsrook.qbits.edi.map.MapPreviewService;
|
||||
import com.kingsrook.qbits.edi.map.MapRuleValidator;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.storage.PayloadKind;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
/*******************************************************************************
|
||||
** Caller-authorized projections and sandbox orchestration, using normal QQQ actions.
|
||||
*******************************************************************************/
|
||||
public class EdiApiService
|
||||
{
|
||||
public static final int MAX_SAMPLE_BYTES = 5 * 1024 * 1024;
|
||||
private static final ObjectMapper JSON = new ObjectMapper().findAndRegisterModules();
|
||||
|
||||
/*******************************************************************************
|
||||
** Enforced reads preserve table permissions, field locks and partner record locks.
|
||||
*******************************************************************************/
|
||||
private static List<QRecord> query(String table, QQueryFilter filter) throws Exception
|
||||
{
|
||||
return (EdiRecords.queryEnforced(table, filter));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** A missing or invisible record has the same response.
|
||||
*******************************************************************************/
|
||||
private static QRecord row(String table, Integer id) throws Exception
|
||||
{
|
||||
if(id == null || id < 1)
|
||||
{
|
||||
throw (new QBadRequestException("A positive identifier is required"));
|
||||
}
|
||||
var records = query(table, EdiRecords.equal("id", id));
|
||||
if(records.isEmpty())
|
||||
{
|
||||
throw (new QNotFoundException("Not found"));
|
||||
}
|
||||
return (records.getFirst());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Read a guide as a nested tree, preserving duplicate loop ids in separate parents.
|
||||
*******************************************************************************/
|
||||
public EdiGuideTreeV1 tree(Integer id) throws Exception
|
||||
{
|
||||
row("edi_guide", id);
|
||||
List<EdiLoopV1> roots = new ArrayList<>();
|
||||
List<EdiSegmentV1> rootSegments = new ArrayList<>();
|
||||
Map<String, EdiLoopV1> loops = new LinkedHashMap<>();
|
||||
Map<String, String> latestPaths = new LinkedHashMap<>();
|
||||
var segments = query("edi_guide_segment", EdiRecords.equal("guideId", id));
|
||||
segments.sort(Comparator.comparing(r -> r.getValueInteger("sequence")));
|
||||
for(var segment : segments)
|
||||
{
|
||||
List<EdiElementV1> elements = new ArrayList<>();
|
||||
var rows = query("edi_guide_element", EdiRecords.equal("guideSegmentId", segment.getValue("id")));
|
||||
rows.sort(Comparator.comparing((QRecord r) -> r.getValueInteger("position")).thenComparing(r -> Objects.requireNonNullElse(r.getValueInteger("componentPosition"), 0)));
|
||||
for(var element : rows)
|
||||
{
|
||||
List<String> codes = List.of();
|
||||
Integer codeList = element.getValueInteger("codeListId");
|
||||
if(codeList != null)
|
||||
{
|
||||
row("edi_code_list", codeList);
|
||||
codes = query("edi_code_list_value", EdiRecords.equal("codeListId", codeList)
|
||||
.withCriteria("isActive", com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.EQUALS, true))
|
||||
.stream().map(r -> r.getValueString("code")).sorted().limit(200).toList();
|
||||
}
|
||||
elements.add(new EdiElementV1(element.getValueInteger("position"), element.getValueInteger("componentPosition"),
|
||||
element.getValueString("elementId"), element.getValueString("name"), element.getValueString("dataType"),
|
||||
element.getValueInteger("minLength"), element.getValueInteger("maxLength"), element.getValueString("usage"), codeList, codes));
|
||||
}
|
||||
EdiSegmentV1 node = new EdiSegmentV1(segment.getValueString("segmentId"), segment.getValueString("position"), segment.getValueString("usage"),
|
||||
segment.getValueInteger("minUse"), segment.getValueInteger("maxUse"), segment.getValueString("name"), elements);
|
||||
String loopId = segment.getValueString("loopId");
|
||||
if(loopId == null || loopId.isBlank())
|
||||
{
|
||||
rootSegments.add(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
String parent = latestPaths.get(segment.getValueString("parentLoopId"));
|
||||
String path = (parent == null ? "" : parent + "/") + loopId;
|
||||
EdiLoopV1 loop = loops.get(path);
|
||||
if(loop == null)
|
||||
{
|
||||
loop = new EdiLoopV1(loopId, segment.getValueInteger("loopMaxRepeat"), new ArrayList<>(), new ArrayList<>());
|
||||
loops.put(path, loop);
|
||||
(parent == null ? roots : loops.get(parent).loops()).add(loop);
|
||||
}
|
||||
latestPaths.put(loopId, path);
|
||||
loop.segments().add(node);
|
||||
}
|
||||
}
|
||||
return (new EdiGuideTreeV1(roots, rootSegments));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Return visible target metadata, rules and the same registry used by mapping.
|
||||
*******************************************************************************/
|
||||
public EdiTargetV1 target(Integer id) throws Exception
|
||||
{
|
||||
QRecord map = row("edi_map", id);
|
||||
QTableMetaData table = targetTable(map);
|
||||
List<EdiAssociationV1> associations = new ArrayList<>();
|
||||
if(table.getAssociations() != null)
|
||||
{
|
||||
for(var association : table.getAssociations())
|
||||
{
|
||||
QTableMetaData child = QContext.getQInstance().getTable(association.getAssociatedTableName());
|
||||
associations.add(new EdiAssociationV1(association.getName(), child.getName(), Objects.requireNonNullElse(child.getLabel(), association.getName()), fields(child)));
|
||||
}
|
||||
}
|
||||
List<EdiRuleV1> rules = new ArrayList<>();
|
||||
for(QRecord rule : query("edi_map_rule", EdiRecords.equal("mapId", id)))
|
||||
{
|
||||
rules.add(rule(rule));
|
||||
}
|
||||
rules.sort(Comparator.comparing(r -> Objects.requireNonNullElse(r.sequence(), 0)));
|
||||
return (new EdiTargetV1(table.getName(), fields(table), associations, rules,
|
||||
EdiRuntime.current().transforms().argumentSchemas().entrySet().stream().map(e -> new EdiTransformV1(e.getKey(), e.getValue())).toList()));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Resolve the configured target without accepting a client-supplied table.
|
||||
*******************************************************************************/
|
||||
private QTableMetaData targetTable(QRecord map) throws Exception
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(map.getValueString("tableName"));
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QBadRequestException("The map target table is unavailable"));
|
||||
}
|
||||
return (table);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Table metadata is only exposed with the table's read permission.
|
||||
*******************************************************************************/
|
||||
private List<EdiFieldV1> fields(QTableMetaData table) throws Exception
|
||||
{
|
||||
QueryInput input = new QueryInput(table.getName());
|
||||
input.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
|
||||
PermissionsHelper.checkTablePermissionThrowing(input, TablePermissionSubType.READ);
|
||||
return (table.getFields().values().stream().map(f -> new EdiFieldV1(f.getName(), f.getLabel(), f.getType().name(), f.getIsRequired(), f.getPossibleValueSourceName())).toList());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Copy only documented rule fields; do not leak persistence or diagnostic fields.
|
||||
*******************************************************************************/
|
||||
private EdiRuleV1 rule(QRecord r)
|
||||
{
|
||||
return (new EdiRuleV1(r.getValueInteger("id"), r.getValueInteger("sequence"), r.getValueString("ediPath"), r.getValueString("qqqPath"),
|
||||
r.getValueString("transform"), r.getValueString("transformArgs"), r.getValueBoolean("required"), r.getValueString("condition"), r.getValueString("defaultValue")));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Convert unsaved edits to detached records; mapId is never accepted from a rule.
|
||||
*******************************************************************************/
|
||||
private List<QRecord> records(List<EdiRuleV1> rules) throws Exception
|
||||
{
|
||||
if(rules == null || rules.size() > 10000 || rules.stream().anyMatch(Objects::isNull))
|
||||
{
|
||||
throw (new QBadRequestException("Supply an array of at most 10000 rules"));
|
||||
}
|
||||
List<QRecord> result = new ArrayList<>();
|
||||
for(EdiRuleV1 rule : rules)
|
||||
{
|
||||
result.add(new QRecord().withValue("id", rule.id()).withValue("sequence", rule.sequence()).withValue("ediPath", rule.ediPath())
|
||||
.withValue("qqqPath", rule.qqqPath()).withValue("transform", rule.transform()).withValue("transformArgs", rule.transformArgs())
|
||||
.withValue("required", rule.required()).withValue("condition", rule.condition()).withValue("defaultValue", rule.defaultValue()));
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Validate all submitted rules, with stable index correlation and no persistence.
|
||||
*******************************************************************************/
|
||||
public EdiValidationV1 validate(Integer id, EdiValidateRequestV1 request) throws Exception
|
||||
{
|
||||
QRecord map = row("edi_map", id);
|
||||
targetTable(map);
|
||||
var guide = MapPreviewService.guide(map, "ORIGINAL");
|
||||
List<QRecord> rules = records(request.rules());
|
||||
List<EdiRuleErrorsV1> results = new ArrayList<>();
|
||||
for(int i = 0; i < rules.size(); i++)
|
||||
{
|
||||
QRecord rule = rules.get(i);
|
||||
results.add(new EdiRuleErrorsV1(i, rule.getValueInteger("id"), rule.getValueInteger("sequence"),
|
||||
new MapRuleValidator().validate(rule, map, guide, EdiRuntime.current().transforms())));
|
||||
}
|
||||
return (new EdiValidationV1(results));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Authorize before decoding or saving an upload; previews never change map rules.
|
||||
*******************************************************************************/
|
||||
public EdiPreviewV1 preview(EdiPreviewRequestV1 request) throws Exception
|
||||
{
|
||||
QRecord map = row("edi_map", request.mapId());
|
||||
AbstractActionInput permission = new AbstractActionInput();
|
||||
permission.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
|
||||
PermissionsHelper.checkProcessPermissionThrowing(permission, "edi_map_preview");
|
||||
String revision = Objects.requireNonNullElse(request.guideRevision(), "CURRENT");
|
||||
if(!List.of("CURRENT", "ORIGINAL").contains(revision))
|
||||
{
|
||||
throw (new QBadRequestException("guideRevision must be CURRENT or ORIGINAL"));
|
||||
}
|
||||
if((request.sampleId() == null) == (request.sampleBase64() == null))
|
||||
{
|
||||
throw (new QBadRequestException("Supply exactly one of sampleId and sampleBase64"));
|
||||
}
|
||||
List<QRecord> overrides = request.ruleOverrides() == null ? null : records(request.ruleOverrides());
|
||||
QRecord sample;
|
||||
if(request.sampleId() != null)
|
||||
{
|
||||
sample = row("edi_map_sample", request.sampleId());
|
||||
if(!Objects.equals(sample.getValueInteger("mapId"), request.mapId()))
|
||||
{
|
||||
throw (new QNotFoundException("Not found"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
String encoded = request.sampleBase64();
|
||||
if(encoded.length() > ((MAX_SAMPLE_BYTES + 2) / 3) * 4)
|
||||
{
|
||||
throw (new QBadRequestException("Sample exceeds the 5 MiB limit"));
|
||||
}
|
||||
byte[] bytes;
|
||||
try
|
||||
{
|
||||
bytes = Base64.getDecoder().decode(encoded);
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
throw (new QBadRequestException("sampleBase64 must be valid base64"));
|
||||
}
|
||||
if(bytes.length == 0 || bytes.length > MAX_SAMPLE_BYTES)
|
||||
{
|
||||
throw (new QBadRequestException("Sample must contain 1 byte through 5 MiB"));
|
||||
}
|
||||
if(request.sampleName() != null && request.sampleName().length() > 255)
|
||||
{
|
||||
throw (new QBadRequestException("sampleName must be at most 255 characters"));
|
||||
}
|
||||
// As in the record action, only encrypted sample archival uses the EDI writer.
|
||||
sample = SystemWrites.call(() ->
|
||||
{
|
||||
var ref = EdiRuntime.current().payloads().put(PayloadKind.SAMPLE, new ByteArrayInputStream(bytes));
|
||||
try
|
||||
{
|
||||
return (SystemWrites.insert("edi_map_sample", new QRecord().withValue("mapId", request.mapId())
|
||||
.withValue("name", Objects.requireNonNullElse(request.sampleName(), "Synthetic upload")).withValue("payloadRef", ref.value())));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
EdiRuntime.current().payloads().delete(ref);
|
||||
throw (e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return (JSON.convertValue(new MapPreviewService().preview(map, sample, overrides, revision), EdiPreviewV1.class));
|
||||
}
|
||||
}
|
||||
@@ -1,528 +0,0 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import com.kingsrook.qqq.middleware.javalin.schemabuilder.ToSchema;
|
||||
import com.kingsrook.qqq.openapi.model.Schema;
|
||||
import com.kingsrook.qqq.openapi.model.Type;
|
||||
|
||||
/*******************************************************************************
|
||||
** Versioned wire types; schemas describe values without exposing QRecord internals.
|
||||
*******************************************************************************/
|
||||
public final class EdiSchemas
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Static schema catalog.
|
||||
*******************************************************************************/
|
||||
private EdiSchemas()
|
||||
{
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** A reference to another versioned wire type.
|
||||
*******************************************************************************/
|
||||
public static Schema ref(String name)
|
||||
{
|
||||
return (new Schema().withRefToSchema(name));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Array schema with an explicit item shape.
|
||||
*******************************************************************************/
|
||||
private static Schema array(Schema item)
|
||||
{
|
||||
return (new Schema().withType(Type.ARRAY).withItems(item));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Ordered guide tree. Root segments are outside loops; nested loops retain parent scope.
|
||||
*******************************************************************************/
|
||||
public record EdiGuideTreeV1(List<EdiLoopV1> loops, List<EdiSegmentV1> segments) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Ordered guide tree. Root segments are outside loops; nested loops retain parent scope.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("loops", array(ref("EdiLoopV1"))),
|
||||
Map.entry("segments", array(ref("EdiSegmentV1")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Guide loop, nested within its parent; repeat is the maximum occurrence count.
|
||||
*******************************************************************************/
|
||||
public record EdiLoopV1(String id, Integer repeat, List<EdiLoopV1> loops, List<EdiSegmentV1> segments) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Guide loop, nested within its parent; repeat is the maximum occurrence count.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("id", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("repeat", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("loops", array(ref("EdiLoopV1"))),
|
||||
Map.entry("segments", array(ref("EdiSegmentV1")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Segment definition in guide sequence order.
|
||||
*******************************************************************************/
|
||||
public record EdiSegmentV1(String id, String position, String usage, Integer min, Integer max, String name, List<EdiElementV1> elements) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Segment definition in guide sequence order.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("id", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("position", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("usage", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("min", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("max", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("name", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("elements", array(ref("EdiElementV1")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Element or composite component; codes contain at most 200 active codes in lexical order.
|
||||
*******************************************************************************/
|
||||
public record EdiElementV1(Integer position, Integer component, String id, String name, String type, Integer minLength, Integer maxLength, String usage, Integer codeListId, List<String> codes) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Element or composite component; codes contain at most 200 active codes in lexical order.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("position", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("component", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("id", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("name", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("type", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("minLength", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("maxLength", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("usage", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("codeListId", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("codes", array(new Schema().withType(Type.STRING).withNullable(true)))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Target metadata visible to the caller, current rules and registered transforms.
|
||||
*******************************************************************************/
|
||||
public record EdiTargetV1(String table, List<EdiFieldV1> fields, List<EdiAssociationV1> associations, List<EdiRuleV1> rules, List<EdiTransformV1> transforms) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Target metadata visible to the caller, current rules and registered transforms.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("table", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("fields", array(ref("EdiFieldV1"))),
|
||||
Map.entry("associations", array(ref("EdiAssociationV1"))),
|
||||
Map.entry("rules", array(ref("EdiRuleV1"))),
|
||||
Map.entry("transforms", array(ref("EdiTransformV1")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** QQQ target field metadata.
|
||||
*******************************************************************************/
|
||||
public record EdiFieldV1(String name, String label, String type, Boolean required, String possibleValueSourceName) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("QQQ target field metadata.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("name", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("label", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("type", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("required", new Schema().withType(Type.BOOLEAN)),
|
||||
Map.entry("possibleValueSourceName", new Schema().withType(Type.STRING).withNullable(true))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Direct QQQ association and its target fields.
|
||||
*******************************************************************************/
|
||||
public record EdiAssociationV1(String name, String table, String label, List<EdiFieldV1> fields) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Direct QQQ association and its target fields.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("name", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("table", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("label", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("fields", array(ref("EdiFieldV1")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Map rule. id is optional for unsaved rules. transformArgs is a JSON object encoded as a string, as in core table CRUD.
|
||||
*******************************************************************************/
|
||||
public record EdiRuleV1(Integer id, Integer sequence, String ediPath, String qqqPath, String transform, String transformArgs, Boolean required, String condition, String defaultValue) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Map rule. id is optional for unsaved rules. transformArgs is a JSON object encoded as a string, as in core table CRUD.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("id", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("sequence", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("ediPath", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("qqqPath", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("transform", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("transformArgs", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("required", new Schema().withType(Type.BOOLEAN).withNullable(true)),
|
||||
Map.entry("condition", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("defaultValue", new Schema().withType(Type.STRING).withNullable(true))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Registered transform and JSON Schema describing its string-valued arguments.
|
||||
*******************************************************************************/
|
||||
public record EdiTransformV1(String name, Map<String, Object> argumentSchema) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Registered transform and JSON Schema describing its string-valued arguments.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("name", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("argumentSchema", new Schema().withType(Type.OBJECT).withAdditionalProperties(true).withDescription("JSON Schema: type, properties, required and additionalProperties. All transform arguments are strings."))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** mapId is required. Supply exactly one of sampleId or sampleBase64. Uploads are at most 5 MiB decoded, stored encrypted. ruleOverrides replaces the whole rule set in memory. CURRENT (default) uses the newest active guide in the pinned guide family; ORIGINAL uses the pinned guide.
|
||||
*******************************************************************************/
|
||||
public record EdiPreviewRequestV1(Integer mapId, Integer sampleId, String sampleBase64, String sampleName, List<EdiRuleV1> ruleOverrides, String guideRevision) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("mapId is required. Supply exactly one of sampleId or sampleBase64. Uploads are at most 5 MiB decoded, stored encrypted. ruleOverrides replaces the whole rule set in memory. CURRENT (default) uses the newest active guide in the pinned guide family; ORIGINAL uses the pinned guide.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("mapId", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("sampleId", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("sampleBase64", new Schema().withType(Type.STRING).withNullable(true).withFormat("byte").withMaxLength(6990508)),
|
||||
Map.entry("sampleName", new Schema().withType(Type.STRING).withNullable(true).withMaxLength(255)),
|
||||
Map.entry("ruleOverrides", array(ref("EdiRuleV1")).withNullable(true)),
|
||||
Map.entry("guideRevision", new Schema().withType(Type.STRING).withNullable(true).withEnumValues(List.of("CURRENT", "ORIGINAL")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Parse, validate, acknowledge and map without target writes. PHI suppresses result, ack text, and both diffs.
|
||||
*******************************************************************************/
|
||||
public record EdiPreviewV1(EdiParseV1 parse, List<EdiFindingV1> findings, EdiAckV1 ack, EdiResultV1 result, EdiDiffV1 diff, Boolean phiSuppressed) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Parse, validate, acknowledge and map without target writes. PHI suppresses result, ack text, and both diffs.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("parse", ref("EdiParseV1")),
|
||||
Map.entry("findings", array(ref("EdiFindingV1"))),
|
||||
Map.entry("ack", ref("EdiAckV1")),
|
||||
Map.entry("result", ref("EdiResultV1").withNullable(true)),
|
||||
Map.entry("diff", ref("EdiDiffV1")),
|
||||
Map.entry("phiSuppressed", new Schema().withType(Type.BOOLEAN))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Counts of wire interchanges, groups and documents.
|
||||
*******************************************************************************/
|
||||
public record EdiParseV1(Integer interchanges, Integer groups, Integer documents) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Counts of wire interchanges, groups and documents.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("interchanges", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("groups", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("documents", new Schema().withType(Type.INTEGER).withNullable(true))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Value-free diagnostic. PHI messages are generic; only positions and codes are retained.
|
||||
*******************************************************************************/
|
||||
public record EdiFindingV1(Integer snipLevel, String severity, Integer segmentPosition, String segmentId, String loopId, Integer elementPosition, String code, String message) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Value-free diagnostic. PHI messages are generic; only positions and codes are retained.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("snipLevel", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("severity", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("segmentPosition", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("segmentId", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("loopId", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("elementPosition", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("code", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("message", new Schema().withType(Type.STRING).withNullable(true))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Concatenated acknowledgement previews; type is 997, 999, TA1, CONTRL, NONE or MIXED. text is null for PHI.
|
||||
*******************************************************************************/
|
||||
public record EdiAckV1(String type, String text) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Concatenated acknowledgement previews; type is 997, 999, TA1, CONTRL, NONE or MIXED. text is null for PHI.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("type", new Schema().withType(Type.STRING).withNullable(true)),
|
||||
Map.entry("text", new Schema().withType(Type.STRING).withNullable(true))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Mapped records in document order; no target writes.
|
||||
*******************************************************************************/
|
||||
public record EdiResultV1(List<EdiRecordV1> records) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Mapped records in document order; no target writes.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("records", array(ref("EdiRecordV1")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Field values and recursively associated records. Values use the target table field types.
|
||||
*******************************************************************************/
|
||||
public record EdiRecordV1(Map<String, Object> values, Map<String, List<EdiRecordV1>> associations) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Field values and recursively associated records. Values use the target table field types.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("values", new Schema().withType(Type.OBJECT).withAdditionalProperties(true)),
|
||||
Map.entry("associations", new Schema().withType(Type.OBJECT).withAdditionalProperties(array(ref("EdiRecordV1"))))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Only differing records are returned. Both lists are empty for PHI.
|
||||
*******************************************************************************/
|
||||
public record EdiDiffV1(List<EdiDifferenceV1> againstPreviousRevision, List<EdiDifferenceV1> againstExpected) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Only differing records are returned. Both lists are empty for PHI.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("againstPreviousRevision", array(ref("EdiDifferenceV1"))),
|
||||
Map.entry("againstExpected", array(ref("EdiDifferenceV1")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** One document comparison; documentIndex is zero based.
|
||||
*******************************************************************************/
|
||||
public record EdiDifferenceV1(Integer documentIndex, Object before, Object after) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("One document comparison; documentIndex is zero based.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("documentIndex", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("before", new Schema().withNullable(true).withDescription("Expected JSON or a previous mapped record.")),
|
||||
Map.entry("after", new Schema().withType(Type.OBJECT).withAdditionalProperties(true).withNullable(true))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** rules is required and may be empty; rules are never persisted.
|
||||
*******************************************************************************/
|
||||
public record EdiValidateRequestV1(List<EdiRuleV1> rules) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("rules is required and may be empty; rules are never persisted.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("rules", array(ref("EdiRuleV1")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** One result per submitted rule, including rules with no errors.
|
||||
*******************************************************************************/
|
||||
public record EdiValidationV1(List<EdiRuleErrorsV1> rules) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("One result per submitted rule, including rules with no errors.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("rules", array(ref("EdiRuleErrorsV1")))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Correlate errors using zero-based index, optional id and sequence.
|
||||
*******************************************************************************/
|
||||
public record EdiRuleErrorsV1(Integer index, Integer id, Integer sequence, List<String> errors) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Correlate errors using zero-based index, optional id and sequence.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("index", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("id", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("sequence", new Schema().withType(Type.INTEGER).withNullable(true)),
|
||||
Map.entry("errors", array(new Schema().withType(Type.STRING).withNullable(true)))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Core middleware error body.
|
||||
*******************************************************************************/
|
||||
public record EdiErrorV1(String error) implements ToSchema
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Describe this wire object for generated clients.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Schema toSchema()
|
||||
{
|
||||
return (new Schema().withType(Type.OBJECT).withDescription("Deprecated since 1.1.0: errors use BasicErrorResponseV1.")
|
||||
.withProperties(new TreeMap<>(Map.ofEntries(
|
||||
Map.entry("error", new Schema().withType(Type.STRING).withNullable(true))))));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Complete deterministic schema catalog for all four operations.
|
||||
*******************************************************************************/
|
||||
public static Map<String, Schema> all()
|
||||
{
|
||||
Map<String, Schema> result = new TreeMap<>();
|
||||
result.put("EdiGuideTreeV1", new EdiGuideTreeV1(null, null).toSchema());
|
||||
result.put("EdiLoopV1", new EdiLoopV1(null, null, null, null).toSchema());
|
||||
result.put("EdiSegmentV1", new EdiSegmentV1(null, null, null, null, null, null, null).toSchema());
|
||||
result.put("EdiElementV1", new EdiElementV1(null, null, null, null, null, null, null, null, null, null).toSchema());
|
||||
result.put("EdiTargetV1", new EdiTargetV1(null, null, null, null, null).toSchema());
|
||||
result.put("EdiFieldV1", new EdiFieldV1(null, null, null, null, null).toSchema());
|
||||
result.put("EdiAssociationV1", new EdiAssociationV1(null, null, null, null).toSchema());
|
||||
result.put("EdiRuleV1", new EdiRuleV1(null, null, null, null, null, null, null, null, null).toSchema());
|
||||
result.put("EdiTransformV1", new EdiTransformV1(null, null).toSchema());
|
||||
result.put("EdiPreviewRequestV1", new EdiPreviewRequestV1(null, null, null, null, null, null).toSchema());
|
||||
result.put("EdiPreviewV1", new EdiPreviewV1(null, null, null, null, null, null).toSchema());
|
||||
result.put("EdiParseV1", new EdiParseV1(null, null, null).toSchema());
|
||||
result.put("EdiFindingV1", new EdiFindingV1(null, null, null, null, null, null, null, null).toSchema());
|
||||
result.put("EdiAckV1", new EdiAckV1(null, null).toSchema());
|
||||
result.put("EdiResultV1", new EdiResultV1(null).toSchema());
|
||||
result.put("EdiRecordV1", new EdiRecordV1(null, null).toSchema());
|
||||
result.put("EdiDiffV1", new EdiDiffV1(null, null).toSchema());
|
||||
result.put("EdiDifferenceV1", new EdiDifferenceV1(null, null, null).toSchema());
|
||||
result.put("EdiValidateRequestV1", new EdiValidateRequestV1(null).toSchema());
|
||||
result.put("EdiValidationV1", new EdiValidationV1(null).toSchema());
|
||||
result.put("EdiRuleErrorsV1", new EdiRuleErrorsV1(null, null, null, null).toSchema());
|
||||
result.put("EdiErrorV1", new EdiErrorV1(null).toSchema());
|
||||
return (result);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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.app;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Explicitly enforce table and field permissions inside the widget's action scope.
|
||||
** RC.5 treats nested DEFAULT queries as trusted, so the generic renderer alone
|
||||
** does not remove fields denied by a FieldSecurityLock.
|
||||
*******************************************************************************/
|
||||
public class EdiChildRecordListRenderer extends ChildRecordListRenderer
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Use the standard typed child list with an enforced query at its public boundary.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected QueryOutput executeQuery(QueryInput input) throws QException
|
||||
{
|
||||
input.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
|
||||
return (super.executeQuery(input));
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,6 @@ import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
@@ -49,7 +47,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.AlertData;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.StatisticsData;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.TableData;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
||||
@@ -59,11 +56,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.DenyBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
/*******************************************************************************
|
||||
** Typed EDI home. Every data query explicitly enforces the viewing user's access.
|
||||
@@ -72,31 +67,9 @@ public class EdiHome extends AbstractWidgetRenderer
|
||||
{
|
||||
public static final List<String> ACTIVITY = List.of("edi_transaction", "edi_interchange", "edi_exception");
|
||||
public static final List<String> SETUP = List.of("edi_partner", "edi_agreement", "edi_transport", "edi_certificate", "edi_guide", "edi_code_list", "edi_map");
|
||||
|
||||
/*******************************************************************************
|
||||
** System-audience widgets (component and queue health, dead letters,
|
||||
** certificate expiry, setup and configuration). They are not on the EDI
|
||||
** business home: a host places them on its Admin home. Each is gated to
|
||||
** ADMIN_PERMISSION_BASE_NAME and hidden from everyone else.
|
||||
*******************************************************************************/
|
||||
public static final List<String> ADMIN_WIDGET_NAMES = List.of("edi_home_system", "edi_home_dead", "edi_home_certificates", "edi_home_setup");
|
||||
|
||||
/*******************************************************************************
|
||||
** Business widgets on the EDI home, in order. Each is gated to the EDI app
|
||||
** permission (edi.hasAccess) and hidden from everyone else.
|
||||
*******************************************************************************/
|
||||
public static final List<String> HOME_WIDGET_NAMES = List.of("edi_home_delayed", "edi_home_in_today", "edi_home_out_today", "edi_home_exceptions",
|
||||
"edi_home_overdue", "edi_home_queue", "edi_home_acks", "edi_home_errors", "edi_home_volume");
|
||||
|
||||
/*******************************************************************************
|
||||
** The EDI administrator permission (edi.admin.hasAccess), as on the admin processes.
|
||||
*******************************************************************************/
|
||||
public static final String ADMIN_PERMISSION_BASE_NAME = EdiQBitConfig.PERMISSION_BASE_NAME + ".admin";
|
||||
|
||||
private static final QLogger LOG = QLogger.getLogger(EdiHome.class);
|
||||
private static final Map<String, String> LABELS = new LinkedHashMap<>();
|
||||
private static final Map<String, String> LABELS = new LinkedHashMap<>();
|
||||
private static final Map<String, String> DOCUMENT_NAMES = new HashMap<>();
|
||||
private static final List<String> TABLES = List.of("setup", "queue", "acks", "errors", "volume", "system");
|
||||
private static final List<String> TABLES = List.of("queue", "acks", "errors", "volume", "system");
|
||||
|
||||
static
|
||||
{
|
||||
@@ -106,14 +79,12 @@ public class EdiHome extends AbstractWidgetRenderer
|
||||
Map.entry("835", "Remittance"), Map.entry("270", "Eligibility Inquiry"), Map.entry("271", "Eligibility Response"), Map.entry("276", "Claim Status Inquiry"),
|
||||
Map.entry("277", "Claim Status Response"), Map.entry("834", "Enrollment"), Map.entry("997", "Functional Ack"), Map.entry("999", "Implementation Ack"),
|
||||
Map.entry("TA1", "Interchange Ack")));
|
||||
LABELS.put("delayed", "Inbound documents delayed");
|
||||
LABELS.put("in_today", "Inbound today");
|
||||
LABELS.put("out_today", "Outbound today");
|
||||
LABELS.put("exceptions", "Open exceptions");
|
||||
LABELS.put("overdue", "Overdue acks");
|
||||
LABELS.put("dead", "Dead letters");
|
||||
LABELS.put("certificates", "Certificates expiring (30 days)");
|
||||
LABELS.put("setup", "Setup status");
|
||||
LABELS.put("queue", "Exception queue");
|
||||
LABELS.put("acks", "Overdue acknowledgements");
|
||||
LABELS.put("errors", "Recent errors (24 hours)");
|
||||
@@ -127,7 +98,7 @@ public class EdiHome extends AbstractWidgetRenderer
|
||||
public static QAppMetaData app(EdiQBitConfig config, List<QTableMetaData> tables)
|
||||
{
|
||||
QAppMetaData app = new QAppMetaData().withName(config.getAppName()).withLabel(config.getAppLabel()).withIcon(new QIcon("swap_horiz"))
|
||||
.withWidgets(HOME_WIDGET_NAMES)
|
||||
.withWidgets(LABELS.keySet().stream().map(n -> "edi_home_" + n).toList())
|
||||
.withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
|
||||
.withSection(new QAppSection().withName("activity").withLabel("Activity").withTables(ACTIVITY))
|
||||
.withSection(new QAppSection().withName("setup").withLabel("Setup").withTables(SETUP));
|
||||
@@ -137,23 +108,15 @@ public class EdiHome extends AbstractWidgetRenderer
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** RC.5 typed statistics, tables and one alert. Business widgets need the EDI
|
||||
** app permission; the ADMIN_WIDGET_NAMES need the EDI administrator
|
||||
** permission. Denied widgets are hidden, never shown as locked shells.
|
||||
** RC.5 typed statistics and tables; no raw markup in metadata or widget data.
|
||||
*******************************************************************************/
|
||||
public static List<QWidgetMetaData> widgets()
|
||||
{
|
||||
List<QWidgetMetaData> widgets = new ArrayList<>();
|
||||
LABELS.forEach((name, label) ->
|
||||
{
|
||||
String widgetName = "edi_home_" + name;
|
||||
boolean admin = ADMIN_WIDGET_NAMES.contains(widgetName);
|
||||
String type = name.equals("delayed") ? WidgetType.ALERT.getType() : (TABLES.contains(name) ? WidgetType.TABLE : WidgetType.STATISTICS).getType();
|
||||
widgets.add(new QWidgetMetaData().withName(widgetName).withLabel(label).withType(type)
|
||||
.withGridColumns(TABLES.contains(name) || admin || name.equals("delayed") ? 12 : 2).withCodeReference(new QCodeReference(EdiHome.class))
|
||||
.withPermissionRules(new QPermissionRules().withPermissionBaseName(admin ? ADMIN_PERMISSION_BASE_NAME : EdiQBitConfig.PERMISSION_BASE_NAME)
|
||||
.withLevel(PermissionLevel.HAS_ACCESS_PERMISSION).withDenyBehavior(DenyBehavior.HIDDEN)));
|
||||
});
|
||||
LABELS.forEach((name, label) -> widgets.add(new QWidgetMetaData().withName("edi_home_" + name).withLabel(label)
|
||||
.withType((TABLES.contains(name) ? WidgetType.TABLE : WidgetType.STATISTICS).getType())
|
||||
.withGridColumns(TABLES.contains(name) ? 12 : 2).withCodeReference(new QCodeReference(EdiHome.class))
|
||||
.withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))));
|
||||
return (widgets);
|
||||
}
|
||||
|
||||
@@ -173,10 +136,8 @@ public class EdiHome extends AbstractWidgetRenderer
|
||||
case "exceptions" -> count("edi_exception", openExceptions());
|
||||
case "overdue" -> count("edi_functional_group", overdueAcks(now));
|
||||
case "dead" -> count("edi_exception", openExceptions().withCriteria("type", QCriteriaOperator.IN, "DEAD_LETTER", "TRANSPORT"));
|
||||
case "delayed" -> delayed(now);
|
||||
case "certificates" -> count("edi_certificate", new QQueryFilter().withCriteria("notAfter", QCriteriaOperator.LESS_THAN_OR_EQUALS, now.plus(30, ChronoUnit.DAYS))
|
||||
.withCriteria("status", QCriteriaOperator.EQUALS, "ACTIVE"));
|
||||
case "setup" -> setup();
|
||||
case "queue" -> queue(now);
|
||||
case "acks" -> acks(now);
|
||||
case "errors" -> errors(now);
|
||||
@@ -230,95 +191,6 @@ public class EdiHome extends AbstractWidgetRenderer
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The setup steps in order, with their status and the next step. Counts are
|
||||
** taken as the viewer; a table the viewer cannot read shows as not visible.
|
||||
*******************************************************************************/
|
||||
private static RenderWidgetOutput setup() throws QException
|
||||
{
|
||||
QQueryFilter active = new QQueryFilter().withCriteria("isActive", QCriteriaOperator.EQUALS, true);
|
||||
EdiSetupStatus.Counts counts = new EdiSetupStatus.Counts(safeCount("edi_partner", new QQueryFilter()),
|
||||
safeCount("edi_identity", new QQueryFilter().withCriteria("isActive", QCriteriaOperator.EQUALS, true).withCriteria("partnerId", QCriteriaOperator.IS_BLANK)),
|
||||
safeCount("edi_identity", new QQueryFilter().withCriteria("isActive", QCriteriaOperator.EQUALS, true).withCriteria("partnerId", QCriteriaOperator.IS_NOT_BLANK)),
|
||||
safeCount("edi_agreement", new QQueryFilter()), safeCount("edi_agreement", active),
|
||||
safeCount("edi_agreement_document", new QQueryFilter()), safeCount("edi_agreement_document", active),
|
||||
safeCount("edi_transport", new QQueryFilter()), safeCount("edi_transport", active),
|
||||
safeCount("edi_certificate", new QQueryFilter()), safeCount("edi_guide", new QQueryFilter()), safeCount("edi_code_list", new QQueryFilter()),
|
||||
safeCount("edi_map", new QQueryFilter()), safeCount("edi_map", new QQueryFilter().withCriteria("status", QCriteriaOperator.EQUALS, "ACTIVE")));
|
||||
List<Map<String, Object>> rows = new ArrayList<>();
|
||||
for(EdiSetupStatus.Item item : EdiSetupStatus.items(counts))
|
||||
{
|
||||
rows.add(row("Step", item.step(), "Status", item.state().label() + (item.detail().isEmpty() ? "" : ": " + item.detail()), "Next step", item.nextStep().isEmpty() ? "Nothing to do." : item.nextStep()));
|
||||
}
|
||||
return (table(List.of("Step", "Status", "Next step"), rows, "edi_partner", new QQueryFilter(), "Open partners"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A count that may fail (for example a table the viewer cannot read): null,
|
||||
** shown as "Unavailable", and logged; never a made-up number.
|
||||
*******************************************************************************/
|
||||
static Integer safeCount(String table, QQueryFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (countOf(table, filter));
|
||||
}
|
||||
catch(QException | RuntimeException e)
|
||||
{
|
||||
LOG.warn("EDI setup count unavailable", e, logPair("table", table));
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The business face of a stalled inbound flow: when the oldest unprocessed
|
||||
** inbound document is older than the configured threshold, a warning with its
|
||||
** age that links to those documents. Hidden when nothing is delayed, or when
|
||||
** the viewer cannot read the inbound documents.
|
||||
*******************************************************************************/
|
||||
private static RenderWidgetOutput delayed(Instant now) throws QException
|
||||
{
|
||||
Integer minutes = EdiRuntime.current().config().getInboundDelayedAfterMinutes();
|
||||
Instant cutoff = now.minus(minutes, ChronoUnit.MINUTES);
|
||||
List<QRecord> oldest = List.of();
|
||||
try
|
||||
{
|
||||
oldest = query("edi_inbox", delayedInbound(cutoff).withOrderBy(new QFilterOrderBy("receivedAt", true)).withLimit(1));
|
||||
}
|
||||
catch(QPermissionDeniedException e)
|
||||
{
|
||||
///////////////////////////////////////////////////////////
|
||||
// omitted, not locked: the viewer cannot open the inbox //
|
||||
///////////////////////////////////////////////////////////
|
||||
}
|
||||
if(oldest.isEmpty())
|
||||
{
|
||||
return (new RenderWidgetOutput(new AlertData().withHideWidget(true)));
|
||||
}
|
||||
int count = countOf("edi_inbox", delayedInbound(cutoff));
|
||||
String age = humanize(Duration.between(oldest.getFirst().getValueInstant("receivedAt"), now));
|
||||
String html = "<strong>Inbound documents are delayed.</strong> " + count + (count == 1 ? " document has" : " documents have")
|
||||
+ " waited more than " + humanize(Duration.ofMinutes(minutes)) + " to be processed; the oldest has waited " + age + ". "
|
||||
+ "<a href=\"" + link("edi_inbox", delayedInbound(cutoff)) + "\">View delayed documents</a>";
|
||||
return (new RenderWidgetOutput(new AlertData(AlertData.AlertType.WARNING, html)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unprocessed inbound documents received before the cutoff.
|
||||
*******************************************************************************/
|
||||
static QQueryFilter delayedInbound(Instant cutoff)
|
||||
{
|
||||
return (inboxBacklog().withCriteria("receivedAt", QCriteriaOperator.LESS_THAN, cutoff));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Open exceptions, oldest first, in four narrow columns.
|
||||
*******************************************************************************/
|
||||
@@ -440,7 +312,7 @@ public class EdiHome extends AbstractWidgetRenderer
|
||||
List<Map<String, Object>> rows = new ArrayList<>();
|
||||
rows.add(row("Item", "Payload storage", "Status", "DATABASE".equals(store) ? "Database, encrypted (demos and tests)" : "Object storage, encrypted"));
|
||||
rows.add(row("Item", "Secured data", "Status", secured ? "Installed" : "Not installed: PHI fields are not masked"));
|
||||
rows.add(row("Item", "External code sets", "Status", EdiRuntime.current().config().getExternalCodeSetProvider() == null ? "Format only: no provider configured" : "Provider configured"));
|
||||
rows.add(row("Item", "External code sets", "Status", "Not configured"));
|
||||
rows.add(row("Item", "Inbound backlog", "Status", countOf("edi_inbox", inboxBacklog())
|
||||
+ (oldestIn.isEmpty() ? "" : ", oldest " + humanize(Duration.between(oldestIn.getFirst().getValueInstant("receivedAt"), now)))));
|
||||
rows.add(row("Item", "Outbound queue", "Status", countOf("edi_outbound_message", outboundQueue())
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* 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.app;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/*******************************************************************************
|
||||
** The Setup status on the EDI home: each setup step in the order it has to be
|
||||
** done (partner, identity, agreement, document, transport), whether it is
|
||||
** done, and the next step. The counts use the same conditions the runtime
|
||||
** uses to route documents (active rows), so the card never reports "set up"
|
||||
** for rows the runtime ignores.
|
||||
*******************************************************************************/
|
||||
public final class EdiSetupStatus
|
||||
{
|
||||
/*******************************************************************************
|
||||
** DONE: working. TODO: the exchange needs it. BUILT_IN: provided by the
|
||||
** qbit. OPTIONAL: only some setups need it. UNKNOWN: its count failed (for
|
||||
** example the viewer cannot read the table), shown as "Unavailable".
|
||||
*******************************************************************************/
|
||||
public enum State
|
||||
{
|
||||
DONE("Set up"), TODO("Needs setup"), BUILT_IN("Built in"), OPTIONAL("Optional"), UNKNOWN("Unavailable");
|
||||
|
||||
private final String label;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
State(String label)
|
||||
{
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** The label shown on the card.
|
||||
*******************************************************************************/
|
||||
public String label()
|
||||
{
|
||||
return (label);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** One line of the card.
|
||||
*******************************************************************************/
|
||||
public record Item(String step, String tableName, State state, String detail, String nextStep)
|
||||
{
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** The row counts the card is built from; null means the count failed and the
|
||||
** step shows as unavailable. Active counts are rows with isActive true (what
|
||||
** the runtime uses).
|
||||
*******************************************************************************/
|
||||
public record Counts(Integer partners, Integer ownIdentities, Integer partnerIdentities, Integer agreements, Integer activeAgreements, Integer agreementDocuments,
|
||||
Integer activeAgreementDocuments, Integer transports, Integer activeTransports, Integer certificates, Integer guides, Integer codeLists,
|
||||
Integer maps, Integer activeMaps)
|
||||
{
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
private EdiSetupStatus()
|
||||
{
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** The items, in setup order.
|
||||
*******************************************************************************/
|
||||
public static List<Item> items(Counts c)
|
||||
{
|
||||
List<Item> items = new ArrayList<>();
|
||||
items.add(item("1. Partner", "edi_partner", c.partners(), c.partners(), "partner", "Add the trading partner with New on Partners."));
|
||||
items.add(identities(c));
|
||||
items.add(item("3. Agreement", "edi_agreement", c.agreements(), c.activeAgreements(), "agreement",
|
||||
"Add an agreement on Agreements: your identity, the partner's identity, the standard and test or production. Make sure Active is on."));
|
||||
items.add(item("4. Agreement documents", "edi_agreement_document", c.agreementDocuments(), c.activeAgreementDocuments(), "agreement document",
|
||||
"Add one per transaction set and direction on Agreement Documents: pick the guide, and a map if it should become records. A document type with no row is not processed."));
|
||||
items.add(item("5. Transport", "edi_transport", c.transports(), c.activeTransports(), "transport",
|
||||
"Add the AS2, SFTP or HTTPS connection on Transports (secrets as variable names), link it on the agreement, then run Test Connection."));
|
||||
items.add(c.certificates() == null ? new Item("Certificates", "edi_certificate", State.UNKNOWN, "", "")
|
||||
: c.certificates() == 0
|
||||
? new Item("Certificates", "edi_certificate", State.OPTIONAL, "None.", "Needed only for AS2 signing and encryption: add yours and the partner's on Certificates.")
|
||||
: new Item("Certificates", "edi_certificate", State.DONE, c.certificates() + (c.certificates() == 1 ? " certificate." : " certificates."), ""));
|
||||
items.add(c.guides() == null || c.codeLists() == null ? new Item("Guides and code lists", "edi_guide", State.UNKNOWN, "", "")
|
||||
: c.guides() == 0 || c.codeLists() == 0
|
||||
? new Item("Guides and code lists", "edi_guide", State.TODO, "The built-in guides are not loaded.", "The host must call EdiRuntime.initialize() at startup, after the schema exists.")
|
||||
: new Item("Guides and code lists", "edi_guide", State.BUILT_IN, c.guides() + " guides and " + c.codeLists() + " code lists.", ""));
|
||||
items.add(c.maps() == null || c.activeMaps() == null ? new Item("Maps", "edi_map", State.UNKNOWN, "", "")
|
||||
: c.maps() == 0
|
||||
? new Item("Maps", "edi_map", State.OPTIONAL, "None.", "Needed to turn documents into records. Run Install sample maps on Maps to start from an example.")
|
||||
: c.activeMaps() == 0
|
||||
? new Item("Maps", "edi_map", State.TODO, c.maps() + " maps, none active.", "Point a map at your table, preview it, then run Activate Map.")
|
||||
: new Item("Maps", "edi_map", State.DONE, c.activeMaps() + " active of " + c.maps() + ".", ""));
|
||||
return (items);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Step 2: your own identity (no partner) and the partner's.
|
||||
*******************************************************************************/
|
||||
private static Item identities(Counts c)
|
||||
{
|
||||
String step = "2. Identities";
|
||||
if(c.ownIdentities() == null || c.partnerIdentities() == null)
|
||||
{
|
||||
return (new Item(step, "edi_identity", State.UNKNOWN, "", ""));
|
||||
}
|
||||
if(c.ownIdentities() == 0)
|
||||
{
|
||||
return (new Item(step, "edi_identity", State.TODO, "None of yours.", "Add your own ISA or UNB qualifier and ID on Identities, with no partner."));
|
||||
}
|
||||
if(c.partnerIdentities() == 0)
|
||||
{
|
||||
return (new Item(step, "edi_identity", State.TODO, "Yours is set; no partner has one.", "Add the partner's qualifier and ID on Identities, with the partner selected."));
|
||||
}
|
||||
return (new Item(step, "edi_identity", State.DONE, c.ownIdentities() + " of yours, " + c.partnerIdentities() + " for partners.", ""));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** A step that needs at least one active row.
|
||||
*******************************************************************************/
|
||||
private static Item item(String step, String table, Integer total, Integer active, String noun, String next)
|
||||
{
|
||||
if(total == null || active == null)
|
||||
{
|
||||
return (new Item(step, table, State.UNKNOWN, "", ""));
|
||||
}
|
||||
if(total == 0)
|
||||
{
|
||||
return (new Item(step, table, State.TODO, "None.", next));
|
||||
}
|
||||
if(active == 0)
|
||||
{
|
||||
return (new Item(step, table, State.TODO, total + " " + noun + (total == 1 ? "" : "s") + ", none active.", "Turn Active on for the one to use."));
|
||||
}
|
||||
return (new Item(step, table, State.DONE, active + " active.", ""));
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* 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";
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* 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");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
* 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"));
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* 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()));
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* 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()));
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,6 @@ import java.util.List;
|
||||
import com.kingsrook.qbits.edi.EdiRuntime;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.alerts.NotificationAudience;
|
||||
import com.kingsrook.qqq.backend.core.alerts.NotificationSignal;
|
||||
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.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@@ -57,33 +54,16 @@ public final class EdiJournal
|
||||
filter.withCriteria(pair[0].toString(), pair[1] == null ? QCriteriaOperator.IS_BLANK : QCriteriaOperator.EQUALS, (Integer) pair[1]);
|
||||
}
|
||||
List<QRecord> matches = EdiRecords.query("edi_exception", filter);
|
||||
QRecord exception;
|
||||
boolean opened = matches.isEmpty();
|
||||
if(opened)
|
||||
if(matches.isEmpty())
|
||||
{
|
||||
exception = SystemWrites.insert("edi_exception", new QRecord().withValue("inboxId", inbox).withValue("interchangeId", interchange).withValue("transactionId", transaction)
|
||||
SystemWrites.insert("edi_exception", new QRecord().withValue("inboxId", inbox).withValue("interchangeId", interchange).withValue("transactionId", transaction)
|
||||
.withValue("partnerId", partner).withValue("type", type).withValue("summary", summary).withValue("status", "OPEN").withValue("occurrences", 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
QRecord row = matches.getFirst();
|
||||
exception = row;
|
||||
opened = List.of("RESOLVED", "IGNORED").contains(row.getValueString("status"));
|
||||
SystemWrites.update("edi_exception", new QRecord().withValue("id", row.getValue("id")).withValue("occurrences", row.getValueInteger("occurrences") + 1).withValue("status", "OPEN"));
|
||||
}
|
||||
if(opened && partner != null && List.of("REJECTED_ACK", "DEAD_LETTER").contains(type))
|
||||
{
|
||||
String[] assignees = EdiRecords.query("edi_exception", EdiRecords.equal("partnerId", partner)
|
||||
.withCriteria("status", QCriteriaOperator.IN, "OPEN", "IN_PROGRESS")).stream()
|
||||
.map(row -> row.getValueString("assignedToUserId")).filter(user -> user != null && !user.isBlank()).distinct().toArray(String[]::new);
|
||||
if(assignees.length > 0)
|
||||
{
|
||||
QAlerts.notify(new NotificationSignal("EDI exception opened")
|
||||
.withBody("An acknowledgement rejection or dead-letter exception needs attention.")
|
||||
.withAudience(NotificationAudience.users(assignees))
|
||||
.withSourceName("edi").withSourceTableName("edi_exception").withSourceRecordId(exception.getValueString("id")));
|
||||
}
|
||||
}
|
||||
event(inbox, interchange, transaction, "EXCEPTION");
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,7 @@ public class InboundProcessor
|
||||
if(EdiRecords.query("edi_outbound_message", EdiRecords.equal("ackForGroupId", groupRow.getValue("id"))).isEmpty())
|
||||
{
|
||||
AckLevel level = AckLevel.valueOf(Objects.requireNonNullElse(config.getValueString("ackLevel"), "ERRORS_ONLY"));
|
||||
boolean hipaa = group.gs().value(8).startsWith("005010X") || "999".equals(config.getValueString("ackExpectation"));
|
||||
boolean hipaa = Boolean.TRUE.equals(config.getValueBoolean("containsPhi")) && group.gs().value(8).startsWith("005010X");
|
||||
var ack = hipaa ? runtime.ack999().build(group, findings, level, "0001") : runtime.ack997().build(group, findings, level, "0001");
|
||||
queue(AckEnvelope.wrap(x12, group, ack, control(agreement, "ISA"), control(agreement, "GS"), LocalDateTime.now(runtime.config().getClock())),
|
||||
agreement, id, groupRow.getValueInteger("id"), hipaa ? "999" : "997");
|
||||
@@ -220,8 +220,7 @@ public class InboundProcessor
|
||||
{
|
||||
QRecord sender = row("edi_identity", agreement.getValueInteger("partnerIdentityId"));
|
||||
QRecord receiver = row("edi_identity", agreement.getValueInteger("localIdentityId"));
|
||||
if(sender != null && receiver != null && !Boolean.FALSE.equals(sender.getValueBoolean("isActive")) && !Boolean.FALSE.equals(receiver.getValueBoolean("isActive"))
|
||||
&& Objects.equals(sender.getValueInteger("partnerId"), agreement.getValueInteger("partnerId"))
|
||||
if(sender != null && receiver != null && Objects.equals(sender.getValueInteger("partnerId"), agreement.getValueInteger("partnerId"))
|
||||
&& receiver.getValue("partnerId") == null && x12.isa().value(5).equals(sender.getValueString("interchangeQualifier"))
|
||||
&& x12.isa().value(6).trim().equals(sender.getValueString("interchangeId")) && x12.isa().value(7).equals(receiver.getValueString("interchangeQualifier"))
|
||||
&& x12.isa().value(8).trim().equals(receiver.getValueString("interchangeId")))
|
||||
@@ -262,7 +261,6 @@ public class InboundProcessor
|
||||
.withValue("receiverQualifier", x12.isa().value(7)).withValue("receiverId", x12.isa().value(8).trim()).withValue("controlNumber", x12.isa().value(13))
|
||||
.withValue("interchangeVersion", x12.isa().value(12)).withValue("usageIndicator", x12.isa().value(15)).withValue("groupCount", x12.groups().size())
|
||||
.withValue("payloadRef", inbox.getValue("payloadRef")).withValue("payloadSha256", inbox.getValue("payloadSha256")).withValue("sizeBytes", inbox.getValue("sizeBytes"))
|
||||
.withValue("containsPhi", x12.groups().stream().anyMatch(g -> g.gs().value(8).startsWith("005010X")))
|
||||
.withValue("status", duplicate ? "DUPLICATE" : x12.findings().isEmpty() ? "ACCEPTED" : "REJECTED"));
|
||||
EdiJournal.event(inbox.getValueInteger("id"), row.getValueInteger("id"), null, "PARSED");
|
||||
return (row);
|
||||
@@ -309,14 +307,14 @@ public class InboundProcessor
|
||||
return (SystemWrites.insert("edi_transaction", new QRecord().withValue("inboundPosition", position).withValue("interchangeId", interchange.getValue("id")).withValue("functionalGroupId", group.getValue("id"))
|
||||
.withValue("direction", "INBOUND").withValue("partnerId", interchange.getValue("partnerId")).withValue("transactionSet", tx.st().value(1))
|
||||
.withValue("version", group.getValue("version")).withValue("controlNumber", tx.st().value(2)).withValue("segmentCount", tx.segments().size())
|
||||
.withValue("agreementDocumentId", document == null ? null : document.getValue("id")).withValue("containsPhi", group.getValueString("version").startsWith("005010X") || document != null && Boolean.TRUE.equals(document.getValueBoolean("containsPhi")))
|
||||
.withValue("agreementDocumentId", document == null ? null : document.getValue("id")).withValue("containsPhi", document != null && Boolean.TRUE.equals(document.getValueBoolean("containsPhi")))
|
||||
.withValue("status", "RECEIVED")));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Replays select original persisted definitions or the currently active revision.
|
||||
*******************************************************************************/
|
||||
protected QRecord revisions(QRecord document, QRecord transaction) throws Exception
|
||||
private QRecord revisions(QRecord document, QRecord transaction) throws Exception
|
||||
{
|
||||
if(document == null)
|
||||
{
|
||||
@@ -354,7 +352,7 @@ public class InboundProcessor
|
||||
List<ValidationFinding> findings = new ArrayList<>();
|
||||
if(document != null && document.getValueInteger("guideId") != null)
|
||||
{
|
||||
findings.addAll(runtime.validator().validate(tx, new GuideRepository().load(document.getValueInteger("guideId")), Boolean.TRUE.equals(document.getValueBoolean("containsPhi")) || group.gs().value(8).startsWith("005010X")));
|
||||
findings.addAll(runtime.validator().validate(tx, new GuideRepository().load(document.getValueInteger("guideId")), Boolean.TRUE.equals(document.getValueBoolean("containsPhi"))));
|
||||
int snip = Objects.requireNonNullElse(document.getValueInteger("snipLevel"), 2);
|
||||
findings.removeIf(f -> f.snipLevel() > snip);
|
||||
}
|
||||
@@ -384,7 +382,7 @@ public class InboundProcessor
|
||||
/*******************************************************************************
|
||||
** Persist findings without repeatedly adding error rows on storage retries.
|
||||
*******************************************************************************/
|
||||
protected void validation(QRecord tx, List<ValidationFinding> findings, QRecord document) throws Exception
|
||||
private 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")))
|
||||
@@ -422,7 +420,7 @@ public class InboundProcessor
|
||||
/*******************************************************************************
|
||||
** A failed target write rolls back only this set, then records an APPLY exception.
|
||||
*******************************************************************************/
|
||||
protected void apply(com.kingsrook.qbits.edi.document.EdiDocument x12, QRecord tx, QRecord document, QRecord agreement) throws Exception
|
||||
private void apply(X12TransactionSet x12, QRecord tx, QRecord document, QRecord agreement) throws Exception
|
||||
{
|
||||
if("APPLIED".equals(tx.getValueString("status")) && !Boolean.TRUE.equals(inbox.getValueBoolean("reprocess")))
|
||||
{
|
||||
@@ -434,7 +432,7 @@ public class InboundProcessor
|
||||
return;
|
||||
}
|
||||
String[] stage = {"MAPPING"};
|
||||
try(var logging = com.kingsrook.qbits.edi.security.PhiLogScope.open(Boolean.TRUE.equals(tx.getValueBoolean("containsPhi"))))
|
||||
try
|
||||
{
|
||||
InboxWorker.fenced(inbox, lease, () ->
|
||||
{
|
||||
@@ -449,7 +447,7 @@ public class InboundProcessor
|
||||
stage[0] = "APPLY";
|
||||
QRecord target = InboundApplier.asPartner(runtime.config().getServiceSessionSupplier(), agreement.getValueInteger("partnerId"), () -> new InboundApplier().apply(map, mapped.record()));
|
||||
String primaryKey = com.kingsrook.qqq.backend.core.context.QContext.getQInstance().getTable(map.getValueString("tableName")).getPrimaryKeyField();
|
||||
boolean phi = Boolean.TRUE.equals(tx.getValueBoolean("containsPhi"));
|
||||
boolean phi = Boolean.TRUE.equals(document.getValueBoolean("containsPhi"));
|
||||
SystemWrites.update("edi_transaction", new QRecord().withValue("id", tx.getValue("id")).withValue("status", "APPLIED")
|
||||
.withValue("mapId", map.getValue("id")).withValue("mapRevision", map.getValue("revision")).withValue("businessKeyType", map.getValue("businessKeyType"))
|
||||
.withValue(phi ? "phiBusinessKey" : "businessKey", mapped.businessKey()).withValue("secondaryKey", phi ? null : mapped.secondaryKey())
|
||||
|
||||
@@ -88,22 +88,11 @@ public class InboxWorker
|
||||
}
|
||||
try(InputStream stream = store.open(new PayloadRef(row.getValueString("payloadRef"), row.getValueString("payloadSha256"), row.getValueLong("sizeBytes"))))
|
||||
{
|
||||
var framing = new com.kingsrook.qbits.edi.document.EnvelopeReader();
|
||||
byte[] envelope;
|
||||
var interchanges = EdiRuntime.current().reader().read(stream);
|
||||
int position = 0;
|
||||
while((envelope = framing.next(stream)) != null)
|
||||
for(var interchange : interchanges)
|
||||
{
|
||||
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);
|
||||
}
|
||||
new InboundProcessor(row, lease, store).process(interchange, ++position);
|
||||
}
|
||||
fenced(row, lease, () ->
|
||||
{
|
||||
|
||||
@@ -73,11 +73,6 @@ 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"));
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
|
||||
package com.kingsrook.qbits.edi.flow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kingsrook.qbits.edi.EdiRuntime;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
@@ -32,80 +30,36 @@ import com.kingsrook.qqq.backend.core.actions.tables.listeners.RecordChangeListe
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.listeners.RecordChangeType;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
/*******************************************************************************
|
||||
** Transactional source listener and explicit enqueue boundary for MANUAL/ESB.
|
||||
** The listener applies only to tables with an active RECORD_CHANGE definition
|
||||
** (cached per runtime, see {@link OutboundSourceTables}), and to the definition
|
||||
** tables themselves, whose changes invalidate that cache.
|
||||
*******************************************************************************/
|
||||
public class OutboundEnqueue implements RecordChangeListenerInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(OutboundEnqueue.class);
|
||||
|
||||
/*******************************************************************************
|
||||
** Definition changes (any type), and inserts/updates of configured sources.
|
||||
** Ignore internal tables and deletes; definitions are read at event time.
|
||||
*******************************************************************************/
|
||||
public boolean appliesTo(String table, RecordChangeType type)
|
||||
{
|
||||
EdiRuntime runtime = EdiRuntime.find();
|
||||
if(runtime == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
if(OutboundSourceTables.DEFINITION_TABLES.contains(table))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
if(table.startsWith("edi_") || type == RecordChangeType.DELETE)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
try
|
||||
{
|
||||
return (runtime.outboundSources().contains(table, QContext.getQBackendTransaction()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// if the definitions can't be read, apply - so the event runs its //
|
||||
// own queries and reports the failure, rather than silently //
|
||||
// dropping outbound work. //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
LOG.warn("Unable to read EDI outbound source tables", e, logPair("table", table));
|
||||
return (true);
|
||||
}
|
||||
return (!table.startsWith("edi_") && type != RecordChangeType.DELETE);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Use the event transaction, even when the caller did not put it in QContext.
|
||||
** Definitions are read once per event, not once per record.
|
||||
*******************************************************************************/
|
||||
public void onRecordsChanged(RecordChangeEvent event) throws QException
|
||||
{
|
||||
if(OutboundSourceTables.DEFINITION_TABLES.contains(event.getTableName()))
|
||||
{
|
||||
EdiRuntime.current().outboundSources().invalidate(event.getTransaction());
|
||||
return;
|
||||
}
|
||||
var previous = QContext.getQBackendTransaction();
|
||||
QContext.setTransaction(event.getTransaction());
|
||||
try
|
||||
{
|
||||
SystemWrites.call(() ->
|
||||
for(QRecord record : event.getRecords())
|
||||
{
|
||||
List<QRecord> definitions = definitions(event.getTableName(), OutboundSourceTables.TRIGGER);
|
||||
for(QRecord record : event.getRecords())
|
||||
{
|
||||
enqueue(event.getTableName(), record, null, definitions);
|
||||
}
|
||||
return (null);
|
||||
});
|
||||
enqueue(event.getTableName(), record, "RECORD_CHANGE", null);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -118,60 +72,45 @@ public class OutboundEnqueue implements RecordChangeListenerInterface
|
||||
*******************************************************************************/
|
||||
public static Integer enqueue(String table, QRecord source, String trigger, String eventId) throws QException
|
||||
{
|
||||
return (SystemWrites.call(() -> enqueue(table, source, eventId, definitions(table, trigger))));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Active outbound documents for the table whose trigger matches (MANUAL
|
||||
** matches every trigger) and whose agreement is active. Called inside a
|
||||
** {@link SystemWrites#call}.
|
||||
*******************************************************************************/
|
||||
static List<QRecord> definitions(String table, String trigger) throws QException
|
||||
{
|
||||
List<QRecord> matching = new ArrayList<>();
|
||||
for(QRecord document : EdiRecords.query("edi_agreement_document", EdiRecords.equal("sourceTableName", table)
|
||||
.withCriteria("direction", QCriteriaOperator.EQUALS, "OUTBOUND").withCriteria("isActive", QCriteriaOperator.EQUALS, true)))
|
||||
return (SystemWrites.call(() ->
|
||||
{
|
||||
if("MANUAL".equals(trigger) || trigger.equals(document.getValueString("outboundTrigger")))
|
||||
int count = 0;
|
||||
var definitions = EdiRecords.query("edi_agreement_document", EdiRecords.equal("sourceTableName", table)
|
||||
.withCriteria("direction", QCriteriaOperator.EQUALS, "OUTBOUND").withCriteria("isActive", QCriteriaOperator.EQUALS, true));
|
||||
for(QRecord document : definitions)
|
||||
{
|
||||
matching.add(document);
|
||||
if(!"MANUAL".equals(trigger) && !trigger.equals(document.getValueString("outboundTrigger")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
QRecord agreement = OutboundWorker.row("edi_agreement", document.getValueInteger("agreementId"));
|
||||
if(!Boolean.TRUE.equals(agreement.getValueBoolean("isActive")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var metadata = QContext.getQInstance().getTable(table);
|
||||
if(!metadata.getBackendName().equals(EdiRuntime.current().config().getBackendName()))
|
||||
{
|
||||
throw new QException("Outbound source and EDI must share the transactional backend");
|
||||
}
|
||||
String filter = document.getValueString("triggerFilter");
|
||||
if(filter != null && !filter.isBlank() && EdiRecords.query(table, EdiRecords.equal(metadata.getPrimaryKeyField(), source.getValue(metadata.getPrimaryKeyField()))
|
||||
.withSubFilter(new ObjectMapper().readValue(filter, QQueryFilter.class))).isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(eventId != null && !EdiRecords.query("edi_outbound_request", EdiRecords.equal("eventId", eventId)
|
||||
.withCriteria("agreementDocumentId", QCriteriaOperator.EQUALS, document.getValue("id"))).isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SystemWrites.insert("edi_outbound_request", new QRecord().withValue("agreementDocumentId", document.getValue("id"))
|
||||
.withValue("sourceTableName", table).withValue("sourceRecordId", source.getValueString(metadata.getPrimaryKeyField()))
|
||||
.withValue("eventId", eventId).withValue("status", "PENDING").withValue("attempts", 0)
|
||||
.withValue("nextAttemptAt", EdiRuntime.current().config().getClock().instant()));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
Set<Integer> activeAgreements = OutboundSourceTables.activeAgreementIds(matching);
|
||||
matching.removeIf(document -> !activeAgreements.contains(document.getValueInteger("agreementId")));
|
||||
return (matching);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Enqueue one source record against already-resolved definitions.
|
||||
*******************************************************************************/
|
||||
private static int enqueue(String table, QRecord source, String eventId, List<QRecord> definitions) throws Exception
|
||||
{
|
||||
int count = 0;
|
||||
for(QRecord document : definitions)
|
||||
{
|
||||
var metadata = QContext.getQInstance().getTable(table);
|
||||
if(!metadata.getBackendName().equals(EdiRuntime.current().config().getBackendName()))
|
||||
{
|
||||
throw new QException("Outbound source and EDI must share the transactional backend");
|
||||
}
|
||||
String filter = document.getValueString("triggerFilter");
|
||||
if(filter != null && !filter.isBlank() && EdiRecords.query(table, EdiRecords.equal(metadata.getPrimaryKeyField(), source.getValue(metadata.getPrimaryKeyField()))
|
||||
.withSubFilter(new ObjectMapper().readValue(filter, QQueryFilter.class))).isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(eventId != null && !EdiRecords.query("edi_outbound_request", EdiRecords.equal("eventId", eventId)
|
||||
.withCriteria("agreementDocumentId", QCriteriaOperator.EQUALS, document.getValue("id"))).isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SystemWrites.insert("edi_outbound_request", new QRecord().withValue("agreementDocumentId", document.getValue("id"))
|
||||
.withValue("sourceTableName", table).withValue("sourceRecordId", source.getValueString(metadata.getPrimaryKeyField()))
|
||||
.withValue("eventId", eventId).withValue("status", "PENDING").withValue("attempts", 0)
|
||||
.withValue("nextAttemptAt", EdiRuntime.current().config().getClock().instant()));
|
||||
count++;
|
||||
}
|
||||
return (count);
|
||||
return (count);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* 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.flow;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** Per-runtime cache of the source tables that have an active RECORD_CHANGE
|
||||
** outbound definition on an active agreement. The record-change listener
|
||||
** consults it so writes to every other table (audits, jobs, alerts...) cost
|
||||
** nothing. It is loaded from committed data only (never in a writer's
|
||||
** transaction). A change to an agreement or document invalidates it, and
|
||||
** again once that change commits (discarding any reload that raced it); until
|
||||
** then the changing transaction bypasses the cache, so it sees its own
|
||||
** definitions. A short TTL covers changes made by another process.
|
||||
*******************************************************************************/
|
||||
public final class OutboundSourceTables
|
||||
{
|
||||
static final String TRIGGER = "RECORD_CHANGE";
|
||||
static final Set<String> DEFINITION_TABLES = Set.of("edi_agreement", "edi_agreement_document");
|
||||
|
||||
private static final Duration TTL = Duration.ofSeconds(60);
|
||||
|
||||
private final AtomicLong generation = new AtomicLong();
|
||||
private final AtomicLong loads = new AtomicLong();
|
||||
private final Set<QBackendTransaction> changingTransactions = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
|
||||
private volatile Snapshot snapshot;
|
||||
|
||||
/*******************************************************************************
|
||||
** Loaded table names, with the generation and time they were loaded at.
|
||||
*******************************************************************************/
|
||||
private record Snapshot(Set<String> tables, long generation, long loadedAtNanos)
|
||||
{
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Whether record changes to this table, written in this transaction (may be
|
||||
** null), can enqueue outbound work.
|
||||
*******************************************************************************/
|
||||
public boolean contains(String table, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
if(transaction != null && changingTransactions.contains(transaction))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
Snapshot current = snapshot;
|
||||
if(current == null || current.generation() != generation.get() || System.nanoTime() - current.loadedAtNanos() > TTL.toNanos())
|
||||
{
|
||||
current = load();
|
||||
}
|
||||
return (current.tables().contains(table));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Drop the cache now, and again once the changing transaction commits.
|
||||
*******************************************************************************/
|
||||
public void invalidate(QBackendTransaction transaction)
|
||||
{
|
||||
generation.incrementAndGet();
|
||||
if(transaction != null)
|
||||
{
|
||||
changingTransactions.add(transaction);
|
||||
transaction.addAfterCommitCallback(() ->
|
||||
{
|
||||
changingTransactions.remove(transaction);
|
||||
generation.incrementAndGet();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** How many times the definitions have been loaded (for tests and diagnostics).
|
||||
*******************************************************************************/
|
||||
public long loadCount()
|
||||
{
|
||||
return (loads.get());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Read committed active definitions once. A load that raced an invalidation
|
||||
** is not kept as current, because it carries the older generation.
|
||||
*******************************************************************************/
|
||||
private Snapshot load() throws QException
|
||||
{
|
||||
long loadGeneration = generation.get();
|
||||
Set<String> tables = SystemWrites.call(() ->
|
||||
{
|
||||
List<QRecord> documents = committed("edi_agreement_document", new QQueryFilter()
|
||||
.withCriteria("direction", QCriteriaOperator.EQUALS, "OUTBOUND").withCriteria("isActive", QCriteriaOperator.EQUALS, true)
|
||||
.withCriteria("outboundTrigger", QCriteriaOperator.EQUALS, TRIGGER));
|
||||
Set<Integer> agreementIds = agreementIds(documents);
|
||||
Set<Integer> activeAgreements = new HashSet<>();
|
||||
if(!agreementIds.isEmpty())
|
||||
{
|
||||
committed("edi_agreement", new QQueryFilter().withCriteria("isActive", QCriteriaOperator.EQUALS, true)
|
||||
.withCriteria("id", QCriteriaOperator.IN, agreementIds)).forEach(agreement -> activeAgreements.add(agreement.getValueInteger("id")));
|
||||
}
|
||||
Set<String> result = new HashSet<>();
|
||||
for(QRecord document : documents)
|
||||
{
|
||||
if(document.getValueString("sourceTableName") != null && activeAgreements.contains(document.getValueInteger("agreementId")))
|
||||
{
|
||||
result.add(document.getValueString("sourceTableName"));
|
||||
}
|
||||
}
|
||||
return (Set.copyOf(result));
|
||||
});
|
||||
loads.incrementAndGet();
|
||||
Snapshot loaded = new Snapshot(tables, loadGeneration, System.nanoTime());
|
||||
snapshot = loaded;
|
||||
return (loaded);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Query committed rows - deliberately without the caller's transaction.
|
||||
*******************************************************************************/
|
||||
private static List<QRecord> committed(String table, QQueryFilter filter) throws QException
|
||||
{
|
||||
return (new QueryAction().execute(new QueryInput(table).withFilter(filter)).getRecords());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** The distinct non-null agreement ids the documents reference.
|
||||
*******************************************************************************/
|
||||
private static Set<Integer> agreementIds(List<QRecord> documents)
|
||||
{
|
||||
Set<Integer> ids = new HashSet<>();
|
||||
documents.forEach(document -> ids.add(document.getValueInteger("agreementId")));
|
||||
ids.remove(null);
|
||||
return (ids);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Active agreement ids among those the documents reference, in one query in
|
||||
** the current transaction.
|
||||
*******************************************************************************/
|
||||
static Set<Integer> activeAgreementIds(List<QRecord> documents) throws QException
|
||||
{
|
||||
Set<Integer> ids = agreementIds(documents);
|
||||
Set<Integer> active = new HashSet<>();
|
||||
if(!ids.isEmpty())
|
||||
{
|
||||
for(QRecord agreement : EdiRecords.query("edi_agreement", EdiRecords.equal("isActive", true).withCriteria("id", QCriteriaOperator.IN, ids)))
|
||||
{
|
||||
active.add(agreement.getValueInteger("id"));
|
||||
}
|
||||
}
|
||||
return (active);
|
||||
}
|
||||
}
|
||||
@@ -87,22 +87,13 @@ public class OutboundWorker
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
QRecord localIdentity = row("edi_identity", agreement.getValueInteger("localIdentityId"));
|
||||
QRecord partnerIdentity = row("edi_identity", agreement.getValueInteger("partnerIdentityId"));
|
||||
if(localIdentity == null || partnerIdentity == null || Boolean.FALSE.equals(localIdentity.getValueBoolean("isActive")) || Boolean.FALSE.equals(partnerIdentity.getValueBoolean("isActive")))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// an inactive identity stops sending, as it stops routing; requests wait //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
return (0);
|
||||
}
|
||||
var requests = EdiRecords.query("edi_outbound_request", EdiRecords.equal("agreementDocumentId", id).withCriteria("status", QCriteriaOperator.EQUALS, "PENDING").withLimit(100));
|
||||
if(requests.isEmpty())
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
List<QRecord> accepted = new ArrayList<>();
|
||||
List<com.kingsrook.qbits.edi.document.EdiDocument> sets = new ArrayList<>();
|
||||
List<X12TransactionSet> 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>();
|
||||
@@ -124,9 +115,9 @@ public class OutboundWorker
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try(var logging = com.kingsrook.qbits.edi.security.PhiLogScope.open(Boolean.TRUE.equals(document.getValueBoolean("containsPhi"))))
|
||||
try
|
||||
{
|
||||
com.kingsrook.qbits.edi.document.EdiDocument set = InboundApplier.asPartner(EdiRuntime.current().config().getServiceSessionSupplier(), agreement.getValueInteger("partnerId"), () ->
|
||||
X12TransactionSet set = InboundApplier.asPartner(EdiRuntime.current().config().getServiceSessionSupplier(), agreement.getValueInteger("partnerId"), () ->
|
||||
{
|
||||
String table = request.getValueString("sourceTableName");
|
||||
String primaryKey = QContext.getQInstance().getTable(table).getPrimaryKeyField();
|
||||
@@ -138,9 +129,7 @@ public class OutboundWorker
|
||||
{
|
||||
throw new IllegalArgumentException("Source record or active map unavailable");
|
||||
}
|
||||
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)));
|
||||
return (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())))
|
||||
{
|
||||
@@ -181,13 +170,8 @@ 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<? extends com.kingsrook.qbits.edi.document.EdiDocument> sets, Integer original) throws Exception
|
||||
public static QRecord envelope(QRecord agreement, QRecord document, QRecord map, List<QRecord> requests, List<X12TransactionSet> 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"));
|
||||
@@ -203,7 +187,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), sets.stream().map(s -> (X12TransactionSet) s).toList(), 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), List.copyOf(sets), List.of())), List.of(), List.of());
|
||||
String ending = switch(value(agreement, "lineEnding", "NONE"))
|
||||
{
|
||||
case "LF" -> "\n";
|
||||
@@ -227,7 +211,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.header().value(1)).withValue("version", gs.value(8)).withValue("controlNumber", set.header().value(2)).withValue("status", "QUEUED")
|
||||
.withValue("transactionSet", set.st().value(1)).withValue("version", gs.value(8)).withValue("controlNumber", set.st().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", "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");
|
||||
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");
|
||||
|
||||
/*******************************************************************************
|
||||
** Read the documented JSON format in memory.
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.guide;
|
||||
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Host-owned, licensed code sets. Implementations must not log submitted values.
|
||||
*******************************************************************************/
|
||||
@FunctionalInterface
|
||||
public interface ExternalCodeSetProvider
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Check membership on the document's effective service or payment date.
|
||||
*******************************************************************************/
|
||||
boolean isValid(String codeSet, String code, LocalDate asOf);
|
||||
}
|
||||
@@ -27,13 +27,6 @@ import java.util.List;
|
||||
/*******************************************************************************
|
||||
** An element or composite component constraint.
|
||||
*******************************************************************************/
|
||||
public record GuideElement(Integer position, Integer componentPosition, String elementId, String dataType, Integer minLength, Integer maxLength, String usage, List<String> codes, String fixedValue, Integer repeatMax, String condition, String source, String externalCodeSet)
|
||||
public record GuideElement(Integer position, Integer componentPosition, String elementId, String dataType, Integer minLength, Integer maxLength, String usage, List<String> codes, String fixedValue, Integer repeatMax, String condition)
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Preserve the original constructor for host-authored inline constraints.
|
||||
*******************************************************************************/
|
||||
public GuideElement(Integer position, Integer componentPosition, String elementId, String dataType, Integer minLength, Integer maxLength, String usage, List<String> codes, String fixedValue, Integer repeatMax, String condition)
|
||||
{
|
||||
this(position, componentPosition, elementId, dataType, minLength, maxLength, usage, codes, fixedValue, repeatMax, condition, null, null);
|
||||
}
|
||||
}
|
||||
@@ -47,37 +47,22 @@ public class GuideRepository
|
||||
*******************************************************************************/
|
||||
public Guide load(Integer id) throws Exception
|
||||
{
|
||||
return (load(id, false));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** UI preview explicitly enforces every guide read, independently of host defaults.
|
||||
*******************************************************************************/
|
||||
public Guide load(Integer id, boolean enforcePermissions) throws Exception
|
||||
{
|
||||
var guideRows = rows("edi_guide", "id", id, enforcePermissions);
|
||||
if(guideRows.isEmpty())
|
||||
{
|
||||
throw (new com.kingsrook.qqq.backend.core.exceptions.QNotFoundException("Not found"));
|
||||
}
|
||||
QRecord guide = guideRows.getFirst();
|
||||
List<QRecord> definitions = new ArrayList<>(rows("edi_guide_segment", "guideId", id, enforcePermissions));
|
||||
QRecord guide = rows("edi_guide", "id", id).getFirst();
|
||||
List<QRecord> definitions = new ArrayList<>(rows("edi_guide_segment", "guideId", id));
|
||||
definitions.sort(Comparator.comparing(r -> r.getValueInteger("sequence")));
|
||||
List<GuideSegment> segments = new ArrayList<>();
|
||||
List<QRecord> rules = rows("edi_guide_rule", "guideId", id, enforcePermissions);
|
||||
List<QRecord> rules = rows("edi_guide_rule", "guideId", id);
|
||||
for(QRecord definition : definitions)
|
||||
{
|
||||
Integer segmentId = definition.getValueInteger("id");
|
||||
List<GuideElement> elements = new ArrayList<>();
|
||||
for(QRecord element : rows("edi_guide_element", "guideSegmentId", segmentId, enforcePermissions))
|
||||
for(QRecord element : rows("edi_guide_element", "guideSegmentId", segmentId))
|
||||
{
|
||||
List<String> codes = null;
|
||||
QRecord codeList = null;
|
||||
if(element.getValueInteger("codeListId") != null)
|
||||
{
|
||||
codeList = rows("edi_code_list", "id", element.getValueInteger("codeListId"), enforcePermissions).getFirst();
|
||||
codes = new ArrayList<>();
|
||||
for(QRecord code : rows("edi_code_list_value", "codeListId", element.getValueInteger("codeListId"), enforcePermissions))
|
||||
for(QRecord code : rows("edi_code_list_value", "codeListId", element.getValueInteger("codeListId")))
|
||||
{
|
||||
if(Boolean.TRUE.equals(code.getValueBoolean("isActive")))
|
||||
{
|
||||
@@ -87,7 +72,7 @@ public class GuideRepository
|
||||
}
|
||||
elements.add(new GuideElement(element.getValueInteger("position"), element.getValueInteger("componentPosition"), element.getValueString("elementId"),
|
||||
element.getValueString("dataType"), element.getValueInteger("minLength"), element.getValueInteger("maxLength"), element.getValueString("usage"), codes,
|
||||
element.getValueString("fixedValue"), element.getValueInteger("repeatMax"), condition(rules, element.getValueInteger("situationalRuleId")), codeList == null ? null : codeList.getValueString("source"), codeList == null ? null : codeList.getValueString("externalCodeSet")));
|
||||
element.getValueString("fixedValue"), element.getValueInteger("repeatMax"), condition(rules, element.getValueInteger("situationalRuleId"))));
|
||||
}
|
||||
List<GuideRule> segmentRules = rules.stream().filter(r -> segmentId.equals(r.getValueInteger("guideSegmentId")) && !"condition".equals(r.getValueString("message"))).map(GuideRepository::rule).toList();
|
||||
segments.add(new GuideSegment(definition.getValueString("segmentId"), definition.getValueString("loopId"), definition.getValueString("parentLoopId"),
|
||||
@@ -122,11 +107,11 @@ public class GuideRepository
|
||||
for(GuideElement element : list(segment.elements()))
|
||||
{
|
||||
Integer codeListId = null;
|
||||
if(element.codes() != null || "EXTERNAL".equals(element.source()))
|
||||
if(element.codes() != null)
|
||||
{
|
||||
codeListId = insert("edi_code_list", new QRecord().withValue("name", guide.name() + "/" + revision + "/" + sequence + "/" + element.position())
|
||||
.withValue("elementId", element.elementId()).withValue("source", element.source() == null ? "BUILT_IN" : element.source()).withValue("externalCodeSet", element.externalCodeSet()), transaction).getValueInteger("id");
|
||||
for(String code : list(element.codes()))
|
||||
.withValue("elementId", element.elementId()).withValue("source", "BUILT_IN"), transaction).getValueInteger("id");
|
||||
for(String code : element.codes())
|
||||
{
|
||||
insert("edi_code_list_value", new QRecord().withValue("codeListId", codeListId).withValue("code", code).withValue("isActive", true), transaction);
|
||||
}
|
||||
@@ -219,9 +204,9 @@ public class GuideRepository
|
||||
/*******************************************************************************
|
||||
** Query normalized child records.
|
||||
*******************************************************************************/
|
||||
private static List<QRecord> rows(String table, String field, Integer id, boolean enforcePermissions) throws Exception
|
||||
private static List<QRecord> rows(String table, String field, Integer id) throws Exception
|
||||
{
|
||||
return (enforcePermissions ? EdiRecords.queryEnforced(table, EdiRecords.equal(field, id)) : EdiRecords.query(table, EdiRecords.equal(field, id)));
|
||||
return (EdiRecords.query(table, EdiRecords.equal(field, id)));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -34,31 +34,31 @@ import com.kingsrook.qbits.edi.x12.X12Segment;
|
||||
*******************************************************************************/
|
||||
public class GuideValidator
|
||||
{
|
||||
private final com.kingsrook.qbits.edi.EdiQBitConfig config;
|
||||
|
||||
/*******************************************************************************
|
||||
** Standalone validation uses built-in formats without a licensed provider.
|
||||
*******************************************************************************/
|
||||
public GuideValidator()
|
||||
{
|
||||
this(new com.kingsrook.qbits.edi.EdiQBitConfig());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Read the host provider at validation time, allowing runtime registration.
|
||||
*******************************************************************************/
|
||||
public GuideValidator(com.kingsrook.qbits.edi.EdiQBitConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Validate one transaction; diagnostic text never contains submitted values.
|
||||
*******************************************************************************/
|
||||
public List<ValidationFinding> validate(EdiDocument transaction, Guide guide, boolean containsPhi)
|
||||
public List<ValidationFinding> validate(X12TransactionSet transaction, Guide guide, boolean containsPhi)
|
||||
{
|
||||
State state = new State(transaction.segments(), containsPhi, "EDIFACT".equals(guide.standard()));
|
||||
List<Node> roots = tree(guide);
|
||||
State state = new State(transaction.segments(), containsPhi);
|
||||
List<Node> roots = new ArrayList<>();
|
||||
Map<String, Node> nodes = new LinkedHashMap<>();
|
||||
for(GuideSegment segment : guide.segments())
|
||||
{
|
||||
List<Node> destination = roots;
|
||||
if(segment.loopId() != null && !segment.loopId().isBlank())
|
||||
{
|
||||
Node loop = nodes.get(segment.loopId());
|
||||
if(loop == null)
|
||||
{
|
||||
loop = new Node(segment.loopId(), null);
|
||||
nodes.put(segment.loopId(), loop);
|
||||
Node parent = nodes.get(segment.parentLoopId());
|
||||
(parent == null ? roots : parent.children).add(loop);
|
||||
}
|
||||
destination = loop.children;
|
||||
}
|
||||
destination.add(new Node(null, segment));
|
||||
}
|
||||
consume(roots, state, false);
|
||||
while(state.index < state.segments.size())
|
||||
{
|
||||
@@ -86,90 +86,48 @@ public class GuideValidator
|
||||
}
|
||||
for(GuideRule rule : list(guide.rules()))
|
||||
{
|
||||
rule(rule, transaction.header(), null, evaluator, state);
|
||||
rule(rule, transaction.st(), null, evaluator, state);
|
||||
}
|
||||
for(Binding binding : state.bindings)
|
||||
{
|
||||
GuideSegment definition = binding.definition();
|
||||
X12Segment segment = binding.segment();
|
||||
elements(segment, definition, evaluator, state);
|
||||
external(segment, definition, state);
|
||||
for(GuideRule rule : list(definition.rules()))
|
||||
{
|
||||
rule(rule, segment, definition.loopId(), "SITUATIONAL".equals(rule.type()) && definition.loopId() != null ? scoped(binding, state) : evaluator, state);
|
||||
rule(rule, segment, definition.loopId(), evaluator, state);
|
||||
}
|
||||
}
|
||||
if(guide.version().startsWith("005010X"))
|
||||
{
|
||||
state.findings.addAll(new HealthcareValidation().validate(transaction, guide, state.loops));
|
||||
}
|
||||
return (List.copyOf(state.findings));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Resolve guide-defined loop occurrences for the mapper using the validator grammar.
|
||||
*******************************************************************************/
|
||||
public Map<String, List<List<X12Segment>>> loops(EdiDocument transaction, Guide guide)
|
||||
{
|
||||
State state = new State(transaction.segments(), true, "EDIFACT".equals(guide.standard()));
|
||||
List<Node> roots = tree(guide);
|
||||
consume(roots, state, false);
|
||||
return (state.loops);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** The same loop name can occur under subscriber and dependent patient parents.
|
||||
*******************************************************************************/
|
||||
private static List<Node> tree(Guide guide)
|
||||
public Map<String, List<List<X12Segment>>> loops(X12TransactionSet transaction, Guide guide)
|
||||
{
|
||||
State state = new State(transaction.segments(), true);
|
||||
List<Node> roots = new ArrayList<>();
|
||||
Map<String, Node> latest = new LinkedHashMap<>();
|
||||
Map<String, Node> nodes = new LinkedHashMap<>();
|
||||
for(GuideSegment segment : guide.segments())
|
||||
{
|
||||
List<Node> destination = roots;
|
||||
if(segment.loopId() != null && !segment.loopId().isBlank())
|
||||
{
|
||||
Node parent = latest.get(segment.parentLoopId());
|
||||
List<Node> siblings = parent == null ? roots : parent.children;
|
||||
Node loop = siblings.stream().filter(n -> segment.loopId().equals(n.loopId)).findFirst().orElse(null);
|
||||
Node loop = nodes.get(segment.loopId());
|
||||
if(loop == null)
|
||||
{
|
||||
loop = new Node(segment.loopId(), null);
|
||||
siblings.add(loop);
|
||||
nodes.put(segment.loopId(), loop);
|
||||
Node parent = nodes.get(segment.parentLoopId());
|
||||
(parent == null ? roots : parent.children).add(loop);
|
||||
}
|
||||
latest.put(segment.loopId(), loop);
|
||||
destination = loop.children;
|
||||
}
|
||||
destination.add(new Node(null, segment));
|
||||
}
|
||||
return (roots);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Situational rules see their occurrence plus direct ancestor segments, never siblings.
|
||||
*******************************************************************************/
|
||||
private static RuleEvaluator scoped(Binding binding, State state)
|
||||
{
|
||||
List<X12Segment> scope = state.loops.getOrDefault(binding.definition().loopId(), List.of()).stream()
|
||||
.filter(o -> o.contains(binding.segment())).min(java.util.Comparator.comparingInt(List::size)).orElse(state.segments);
|
||||
List<X12Segment> visible = new ArrayList<>(scope);
|
||||
for(List<X12Segment> ancestor : state.loops.values().stream().flatMap(List::stream).toList())
|
||||
{
|
||||
if(ancestor.size() > scope.size() && ancestor.contains(scope.getFirst()))
|
||||
{
|
||||
for(X12Segment segment : ancestor)
|
||||
{
|
||||
boolean nested = state.loops.values().stream().flatMap(List::stream)
|
||||
.anyMatch(o -> o.size() < ancestor.size() && ancestor.contains(o.getFirst()) && o.contains(segment));
|
||||
if(!nested && !visible.contains(segment))
|
||||
{
|
||||
visible.add(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visible.sort(java.util.Comparator.comparingInt(X12Segment::position));
|
||||
return (new RuleEvaluator(visible, state.loops));
|
||||
consume(roots, state, false);
|
||||
return (state.loops);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -183,7 +141,7 @@ public class GuideValidator
|
||||
{
|
||||
GuideSegment head = node.children.getFirst().definition;
|
||||
int count = 0;
|
||||
while(state.matches(head))
|
||||
while(state.matches(head.segmentId()))
|
||||
{
|
||||
int start = state.index;
|
||||
consume(node.children, state, true);
|
||||
@@ -207,7 +165,7 @@ public class GuideValidator
|
||||
GuideSegment definition = node.definition;
|
||||
int count = 0;
|
||||
int maximum = number(definition.maxUse(), 1);
|
||||
while(state.matches(definition.segmentId()) && (node != nodes.getFirst() || !loopOccurrence || state.matches(definition)) && (count == 0 || !loopOccurrence || node != nodes.getFirst()))
|
||||
while(state.matches(definition.segmentId()) && (count == 0 || !loopOccurrence || node != nodes.getFirst()))
|
||||
{
|
||||
X12Segment segment = state.segments.get(state.index++);
|
||||
state.bindings.add(new Binding(segment, definition));
|
||||
@@ -271,13 +229,13 @@ public class GuideValidator
|
||||
{
|
||||
state.add(segment, definition.loopId(), element, "8", "5", "Element too long", value);
|
||||
}
|
||||
if(!validType(element.dataType(), state.edifact && "R".equals(element.dataType()) ? value.replace(',', '.') : value))
|
||||
if(!validType(element.dataType(), 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);
|
||||
}
|
||||
if(element.fixedValue() != null && !element.fixedValue().equals(value)
|
||||
|| !"EXTERNAL".equals(element.source()) && "ID".equals(element.dataType()) && element.codes() != null && !element.codes().contains(value))
|
||||
|| "ID".equals(element.dataType()) && element.codes() != null && !element.codes().contains(value))
|
||||
{
|
||||
state.add(segment, definition.loopId(), element, "8", "7", "Invalid code or fixed value", value);
|
||||
}
|
||||
@@ -285,47 +243,6 @@ public class GuideValidator
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** External membership is SNIP 5 and is optional; formats are always built in.
|
||||
*******************************************************************************/
|
||||
private void external(X12Segment segment, GuideSegment definition, State state)
|
||||
{
|
||||
if(config.getExternalCodeSetProvider() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for(GuideElement element : list(definition.elements()))
|
||||
{
|
||||
if(!"EXTERNAL".equals(element.source()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int repetitions = element.position() <= segment.elements().size() ? segment.elements().get(element.position() - 1).size() : 1;
|
||||
for(int repetition = 1; repetition <= repetitions; repetition++)
|
||||
{
|
||||
String value = segment.value(element.position(), repetition, number(element.componentPosition(), 1));
|
||||
if(!value.isEmpty())
|
||||
{
|
||||
boolean valid;
|
||||
try(var logging = com.kingsrook.qbits.edi.security.PhiLogScope.open(state.phi))
|
||||
{
|
||||
valid = config.getExternalCodeSetProvider().isValid(element.externalCodeSet(), value,
|
||||
HealthcareValidation.asOf(state.segments, config.getClock()));
|
||||
}
|
||||
catch(RuntimeException e)
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
if(!valid)
|
||||
{
|
||||
state.findings.add(new ValidationFinding(segment.position(), segment.id(), definition.loopId(), element.position(), element.componentPosition(),
|
||||
5, "8", "7", "8", "7", "ERROR", "External code validation failed", null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Validate X12 lexical data types without coercing bad values.
|
||||
*******************************************************************************/
|
||||
@@ -453,7 +370,6 @@ 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<>();
|
||||
@@ -462,11 +378,10 @@ public class GuideValidator
|
||||
/*******************************************************************************
|
||||
** Initialize a transaction validation.
|
||||
*******************************************************************************/
|
||||
private State(List<X12Segment> segments, boolean phi, boolean edifact)
|
||||
private State(List<X12Segment> segments, boolean phi)
|
||||
{
|
||||
this.segments = segments;
|
||||
this.phi = phi;
|
||||
this.edifact = edifact;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -477,15 +392,6 @@ public class GuideValidator
|
||||
return (index < segments.size() && segments.get(index).id().equals(id));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Fixed qualifiers distinguish adjacent HL and NM1 loop anchors.
|
||||
*******************************************************************************/
|
||||
private boolean matches(GuideSegment definition)
|
||||
{
|
||||
return (matches(definition.segmentId()) && list(definition.elements()).stream().filter(e -> e.fixedValue() != null)
|
||||
.allMatch(e -> e.fixedValue().equals(segments.get(index).value(e.position(), 1, number(e.componentPosition(), 1)))));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Locate a missing segment relative to the buffered transaction.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
/*
|
||||
* 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.guide;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Original healthcare balancing and format checks, without licensed code content.
|
||||
*******************************************************************************/
|
||||
public class HealthcareValidation
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Exact decimal arithmetic; every diagnostic contains only positions and codes.
|
||||
*******************************************************************************/
|
||||
public List<ValidationFinding> validate(EdiDocument transaction, Guide guide, Map<String, List<List<X12Segment>>> loops)
|
||||
{
|
||||
List<ValidationFinding> findings = new ArrayList<>();
|
||||
List<X12Segment> segments = transaction.segments();
|
||||
if("837".equals(guide.transactionSet()))
|
||||
{
|
||||
for(List<X12Segment> claim : blocks(segments, "CLM"))
|
||||
{
|
||||
X12Segment head = claim.getFirst();
|
||||
try
|
||||
{
|
||||
BigDecimal lines = BigDecimal.ZERO;
|
||||
for(X12Segment line : claim)
|
||||
{
|
||||
if(line.id().equals("SV1") || line.id().equals("SV2"))
|
||||
{
|
||||
lines = lines.add(amount(line, line.id().equals("SV1") ? 2 : 3));
|
||||
}
|
||||
}
|
||||
if(amount(head, 2).compareTo(lines) != 0)
|
||||
{
|
||||
findings.add(finding(head, "2300", 2, null, 3, "Claim charges do not balance"));
|
||||
}
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
findings.add(finding(head, "2300", 2, null, 3, "Claim balance cannot be calculated"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if("835".equals(guide.transactionSet()))
|
||||
{
|
||||
remittance(segments, findings);
|
||||
}
|
||||
for(X12Segment segment : segments)
|
||||
{
|
||||
String loop = loops.entrySet().stream().filter(e -> e.getValue().stream().anyMatch(o -> o.contains(segment)))
|
||||
.min(java.util.Comparator.comparingInt(e -> e.getValue().stream().filter(o -> o.contains(segment)).mapToInt(List::size).min().orElse(Integer.MAX_VALUE)))
|
||||
.map(Map.Entry::getKey).orElse(null);
|
||||
if(segment.id().equals("NM1") && segment.value(8).equals("XX") && !validNpi(segment.value(9)))
|
||||
{
|
||||
findings.add(finding(segment, loop, 9, null, 5, "Invalid NPI format or check digit"));
|
||||
}
|
||||
if(segment.id().equals("N4") && !segment.value(3).isEmpty() && (segment.value(4).isEmpty() || segment.value(4).equals("US"))
|
||||
&& !segment.value(3).matches("[0-9]{5}([0-9]{4}|-[0-9]{4})?"))
|
||||
{
|
||||
findings.add(finding(segment, loop, 3, null, 5, "Invalid postal format"));
|
||||
}
|
||||
if(segment.id().equals("HI"))
|
||||
{
|
||||
for(int position = 1; position <= segment.elements().size(); position++)
|
||||
{
|
||||
for(int repetition = 1; repetition <= segment.elements().get(position - 1).size(); repetition++)
|
||||
{
|
||||
if(List.of("ABK", "ABF", "ABJ", "APR").contains(segment.value(position, repetition, 1))
|
||||
&& !segment.value(position, repetition, 2).matches("[A-Z][0-9][A-Z0-9](?:[A-Z0-9]{0,4}|\\.[A-Z0-9]{1,4})"))
|
||||
{
|
||||
findings.add(finding(segment, loop, position, 2, 5, "Invalid ICD-10 diagnosis format"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (findings);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Claim and line CAS monetary positions are 3, 6, 9, 12, 15 and 18.
|
||||
*******************************************************************************/
|
||||
private static void remittance(List<X12Segment> segments, List<ValidationFinding> findings)
|
||||
{
|
||||
for(List<X12Segment> claim : blocks(segments, "CLP"))
|
||||
{
|
||||
X12Segment head = claim.getFirst();
|
||||
try
|
||||
{
|
||||
BigDecimal adjustments = BigDecimal.ZERO;
|
||||
for(X12Segment segment : claim)
|
||||
{
|
||||
if(segment.id().equals("CAS"))
|
||||
{
|
||||
for(int p = 3; p <= segment.elements().size(); p += 3)
|
||||
{
|
||||
adjustments = adjustments.add(amount(segment, p));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(amount(head, 3).subtract(amount(head, 4)).compareTo(adjustments) != 0)
|
||||
{
|
||||
findings.add(finding(head, "2100", 3, null, 3, "Claim adjustments do not balance"));
|
||||
}
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
findings.add(finding(head, "2100", 3, null, 3, "Claim balance cannot be calculated"));
|
||||
}
|
||||
}
|
||||
for(X12Segment payment : segments.stream().filter(s -> s.id().equals("BPR")).toList())
|
||||
{
|
||||
try
|
||||
{
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for(X12Segment segment : segments)
|
||||
{
|
||||
if(segment.id().equals("CLP"))
|
||||
{
|
||||
total = total.add(amount(segment, 4));
|
||||
}
|
||||
if(segment.id().equals("PLB"))
|
||||
{
|
||||
for(int p = 4; p <= segment.elements().size(); p += 2)
|
||||
{
|
||||
total = total.subtract(amount(segment, p));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(amount(payment, 2).compareTo(total) != 0)
|
||||
{
|
||||
findings.add(finding(payment, null, 2, null, 3, "Payment does not balance"));
|
||||
}
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
findings.add(finding(payment, null, 2, null, 3, "Payment balance cannot be calculated"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Isolate each claim so one claim cannot compensate for another's imbalance.
|
||||
*******************************************************************************/
|
||||
private static List<List<X12Segment>> blocks(List<X12Segment> segments, String anchor)
|
||||
{
|
||||
List<List<X12Segment>> blocks = new ArrayList<>();
|
||||
List<X12Segment> current = null;
|
||||
for(X12Segment segment : segments)
|
||||
{
|
||||
if(segment.id().equals(anchor))
|
||||
{
|
||||
current = new ArrayList<>();
|
||||
blocks.add(current);
|
||||
}
|
||||
if(current != null)
|
||||
{
|
||||
current.add(segment);
|
||||
}
|
||||
}
|
||||
return (blocks);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Missing monetary elements fail balancing rather than silently becoming zero.
|
||||
*******************************************************************************/
|
||||
private static BigDecimal amount(X12Segment segment, int position)
|
||||
{
|
||||
return (new BigDecimal(segment.value(position)));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Position-only finding with a positional trigger for the implementation ack.
|
||||
*******************************************************************************/
|
||||
private static ValidationFinding finding(X12Segment segment, String loop, Integer element, Integer component, int level, String message)
|
||||
{
|
||||
return (new ValidationFinding(segment.position(), segment.id(), loop, element, component, level,
|
||||
"8", "7", "8", "7", "ERROR", message, null)
|
||||
.withContext(level == 3 ? new AckContext(segment.id(), segment.position(), loop, element, component) : null));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** NPI uses the Luhn checksum over the 80840 prefix and all ten digits.
|
||||
*******************************************************************************/
|
||||
public static boolean validNpi(String value)
|
||||
{
|
||||
if(!value.matches("[12][0-9]{9}"))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
String digits = "80840" + value;
|
||||
int sum = 0;
|
||||
for(int i = digits.length() - 1, n = 0; i >= 0; i--, n++)
|
||||
{
|
||||
int digit = digits.charAt(i) - '0';
|
||||
if(n % 2 == 1)
|
||||
{
|
||||
digit *= 2;
|
||||
}
|
||||
sum += digit > 9 ? digit - 9 : digit;
|
||||
}
|
||||
return (sum % 10 == 0);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Service date, then payment date, then the configured clock for undated documents.
|
||||
*******************************************************************************/
|
||||
public static LocalDate asOf(List<X12Segment> segments, Clock clock)
|
||||
{
|
||||
for(X12Segment segment : segments)
|
||||
{
|
||||
String date = segment.id().equals("DTP") && segment.value(1).equals("472") ? segment.value(3)
|
||||
: segment.id().equals("BPR") ? segment.value(16) : "";
|
||||
if(RuleEvaluator.validDate(date) && date.length() == 8)
|
||||
{
|
||||
return (LocalDate.parse(date, DateTimeFormatter.BASIC_ISO_DATE));
|
||||
}
|
||||
}
|
||||
return (LocalDate.now(clock));
|
||||
}
|
||||
}
|
||||
@@ -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() && !loops.containsKey(name))
|
||||
if(element.matches())
|
||||
{
|
||||
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(EdiDocument transaction, Guide guide, QRecord map) throws Exception
|
||||
public Result map(X12TransactionSet 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(EdiDocument transaction, Guide guide, QRecord map, List<QRecord> rules) throws Exception
|
||||
public Result map(X12TransactionSet 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, "EDIFACT".equals(guide.standard()));
|
||||
apply(rule, evaluator, result, fields[0], path);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -117,14 +117,10 @@ 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, "EDIFACT".equals(guide.standard()));
|
||||
apply(rule, new RuleEvaluator(occurrences.get(i), loops), children.get(i), fields[1], relative);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException e)
|
||||
{
|
||||
throw (e);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new IllegalArgumentException("Mapping failed at rule " + rule.getValueInteger("sequence") + " (" + rule.getValueString("ediPath") + " -> " + rule.getValueString("qqqPath") + ")");
|
||||
@@ -136,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, boolean edifact) throws Exception
|
||||
private void apply(QRecord rule, RuleEvaluator evaluator, QRecord target, String field, String path) throws Exception
|
||||
{
|
||||
String condition = rule.getValueString("condition");
|
||||
if(condition != null && !condition.isBlank() && !evaluator.test(condition))
|
||||
@@ -148,10 +144,6 @@ 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()))
|
||||
{
|
||||
|
||||
@@ -20,47 +20,138 @@
|
||||
|
||||
package com.kingsrook.qbits.edi.map;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kingsrook.qbits.edi.EdiRuntime;
|
||||
import com.kingsrook.qbits.edi.ack.AckLevel;
|
||||
import com.kingsrook.qbits.edi.guide.GuideRepository;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.storage.PayloadRef;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** Standard record-action presentation of the shared sandbox preview.
|
||||
** No-op apply preview with ordinary Result lines; PHI never enters process output.
|
||||
*******************************************************************************/
|
||||
public class MapPreview
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Render the same pipeline as HTTP using ordinary Review/Result lines.
|
||||
** Parse, validate, acknowledge and map a stored synthetic sample without target writes.
|
||||
*******************************************************************************/
|
||||
public ArrayList<ProcessSummaryLineInterface> preview(QRecord map, QRecord sample) throws Exception
|
||||
{
|
||||
Map<String, Object> preview = new MapPreviewService().preview(map, sample, null, "ORIGINAL");
|
||||
var json = new ObjectMapper().findAndRegisterModules();
|
||||
EdiRuntime runtime = EdiRuntime.current();
|
||||
boolean phi = List.of("837", "835", "270", "271", "276", "277", "834").contains(map.getValueString("transactionSet"));
|
||||
phi = phi || com.kingsrook.qbits.edi.support.SystemWrites.call(() -> !EdiRecords.query("edi_agreement_document", EdiRecords.equal("mapId", map.getValue("id"))
|
||||
.withCriteria("containsPhi", com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.EQUALS, true)).isEmpty());
|
||||
var guide = new GuideRepository().load(map.getValueInteger("guideId"));
|
||||
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, "Parsed: " + json.writeValueAsString(preview.get("parse"))));
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, "SNIP findings: " + json.writeValueAsString(preview.get("findings"))));
|
||||
if(Boolean.TRUE.equals(preview.get("phiSuppressed")))
|
||||
try(InputStream stream = runtime.payloads().open(new PayloadRef(sample.getValueString("payloadRef"), null, 0)))
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, "PHI map result suppressed; no target records written"));
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, "Acknowledgement preview suppressed for PHI document"));
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, json.writeValueAsString(preview.get("result"))));
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, json.writeValueAsString(preview.get("ack"))));
|
||||
Map<?, ?> diff = (Map<?, ?>) preview.get("diff");
|
||||
for(String key : List.of("againstExpected", "againstPreviousRevision"))
|
||||
var interchanges = runtime.reader().read(stream);
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, interchanges.size(), "interchanges parsed"));
|
||||
for(var interchange : interchanges)
|
||||
{
|
||||
String label = key.equals("againstExpected") ? "Expected result" : "Previous revision";
|
||||
List<?> differences = (List<?>) diff.get(key);
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, label + (differences.isEmpty() ? " matches" : " differs: " + json.writeValueAsString(differences))));
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, interchange.groups().size(), "functional groups parsed"));
|
||||
for(var group : interchange.groups())
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, group.transactions().size(), "transaction sets parsed"));
|
||||
Map<String, List<com.kingsrook.qbits.edi.guide.ValidationFinding>> all = new java.util.LinkedHashMap<>();
|
||||
for(var transaction : group.transactions())
|
||||
{
|
||||
var findings = runtime.validator().validate(transaction, guide, phi);
|
||||
all.put(transaction.st().value(2), findings);
|
||||
for(int snip = 1; snip <= 7; snip++)
|
||||
{
|
||||
int level = snip;
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, (int) findings.stream().filter(f -> f.snipLevel() == level).count(), "findings at SNIP " + snip));
|
||||
}
|
||||
List<QRecord> rules = EdiRecords.query("edi_map_rule", EdiRecords.equal("mapId", map.getValue("id")));
|
||||
rules.sort(java.util.Comparator.comparing(r -> r.getValueInteger("sequence")));
|
||||
var mapped = InboundApplier.asService(runtime.config().getServiceSessionSupplier(), () -> new InboundMapper(runtime.transforms()).map(transaction, guide, map, rules));
|
||||
if(phi)
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, "PHI map result suppressed; no target records written"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ObjectMapper json = new ObjectMapper().findAndRegisterModules();
|
||||
String actual = resultJson(mapped.record());
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, actual));
|
||||
String expected = sample.getValueString("expectedResultJson");
|
||||
if(expected != null)
|
||||
{
|
||||
diff(lines, "Expected result", json.readTree(expected), json.readTree(actual));
|
||||
}
|
||||
Integer revision = map.getValueInteger("revision");
|
||||
var previous = EdiRecords.query("edi_map", EdiRecords.equal("name", map.getValue("name")));
|
||||
previous.removeIf(r -> !java.util.Objects.equals(r.getValue("partnerId"), map.getValue("partnerId"))
|
||||
|| revision == null || r.getValueInteger("revision") == null || r.getValueInteger("revision") >= revision);
|
||||
previous.sort(java.util.Comparator.comparing((QRecord r) -> r.getValueInteger("revision")).reversed());
|
||||
if(!previous.isEmpty())
|
||||
{
|
||||
QRecord prior = previous.getFirst();
|
||||
var priorGuide = new GuideRepository().load(prior.getValueInteger("guideId"));
|
||||
List<QRecord> priorRules = EdiRecords.query("edi_map_rule", EdiRecords.equal("mapId", prior.getValue("id")));
|
||||
priorRules.sort(java.util.Comparator.comparing(r -> r.getValueInteger("sequence")));
|
||||
var priorResult = InboundApplier.asService(runtime.config().getServiceSessionSupplier(),
|
||||
() -> new InboundMapper(runtime.transforms()).map(transaction, priorGuide, prior, priorRules));
|
||||
diff(lines, "Previous revision " + prior.getValueInteger("revision"), json.readTree(resultJson(priorResult.record())), json.readTree(actual));
|
||||
}
|
||||
}
|
||||
}
|
||||
var ack = phi && group.gs().value(8).startsWith("005010X") ? runtime.ack999().build(group, all, AckLevel.ALL_TRANSACTIONS, "0001")
|
||||
: runtime.ack997().build(group, all, AckLevel.ALL_TRANSACTIONS, "0001");
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, phi ? "Acknowledgement preview suppressed for PHI document"
|
||||
: runtime.writer().write(com.kingsrook.qbits.edi.ack.AckEnvelope.wrap(interchange, group, ack, "1", "1", java.time.LocalDateTime.now(runtime.config().getClock())))));
|
||||
}
|
||||
}
|
||||
}
|
||||
return (lines);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Stable record JSON excludes QRecord diagnostics and implementation metadata.
|
||||
*******************************************************************************/
|
||||
private static String resultJson(QRecord record) throws Exception
|
||||
{
|
||||
Map<String, Object> result = new java.util.LinkedHashMap<>();
|
||||
result.put("values", record.getValues());
|
||||
Map<String, Object> associations = new java.util.LinkedHashMap<>();
|
||||
if(record.getAssociatedRecords() != null)
|
||||
{
|
||||
for(var entry : record.getAssociatedRecords().entrySet())
|
||||
{
|
||||
List<Object> children = new ArrayList<>();
|
||||
for(QRecord child : entry.getValue())
|
||||
{
|
||||
children.add(new ObjectMapper().readTree(resultJson(child)));
|
||||
}
|
||||
associations.put(entry.getKey(), children);
|
||||
}
|
||||
}
|
||||
result.put("associations", associations);
|
||||
return (new ObjectMapper().findAndRegisterModules().writeValueAsString(result));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Show a structured before/after diff for synthetic samples only.
|
||||
*******************************************************************************/
|
||||
private static void diff(ArrayList<ProcessSummaryLineInterface> lines, String label, com.fasterxml.jackson.databind.JsonNode before, com.fasterxml.jackson.databind.JsonNode after) throws Exception
|
||||
{
|
||||
if(before.equals(after))
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, label + " matches"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Map<String, Object> difference = Map.of("comparison", label, "before", before, "after", after);
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, label + " differs: " + new ObjectMapper().writeValueAsString(difference)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
/*
|
||||
* 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.map;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kingsrook.qbits.edi.EdiRuntime;
|
||||
import com.kingsrook.qbits.edi.ack.AckEnvelope;
|
||||
import com.kingsrook.qbits.edi.ack.AckLevel;
|
||||
import com.kingsrook.qbits.edi.document.EdiDocument;
|
||||
import com.kingsrook.qbits.edi.edifact.ContrlBuilder;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactEnvelopeReader;
|
||||
import com.kingsrook.qbits.edi.edifact.EdifactWriter;
|
||||
import com.kingsrook.qbits.edi.guide.Guide;
|
||||
import com.kingsrook.qbits.edi.guide.GuideRepository;
|
||||
import com.kingsrook.qbits.edi.guide.ValidationFinding;
|
||||
import com.kingsrook.qbits.edi.security.PhiLogScope;
|
||||
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.exceptions.QBadRequestException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** Shared no-apply preview pipeline. All mapping, including LOOKUP, uses the caller.
|
||||
** This service has no optional middleware dependency.
|
||||
*******************************************************************************/
|
||||
public class MapPreviewService
|
||||
{
|
||||
private static final ObjectMapper JSON = new ObjectMapper().findAndRegisterModules();
|
||||
|
||||
/*******************************************************************************
|
||||
** Read ordered persisted rules, or order a copy of the unsaved replacement set.
|
||||
*******************************************************************************/
|
||||
public static List<QRecord> rules(QRecord map, List<QRecord> overrides) throws Exception
|
||||
{
|
||||
List<QRecord> result = new ArrayList<>(overrides == null ? EdiRecords.queryEnforced("edi_map_rule", EdiRecords.equal("mapId", map.getValue("id"))) : overrides);
|
||||
result.sort(Comparator.comparing(r -> Objects.requireNonNullElse(r.getValueInteger("sequence"), 0)));
|
||||
return (result);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** PHI classification cannot be weakened by the caller or by hidden agreements.
|
||||
*******************************************************************************/
|
||||
public static boolean containsPhi(QRecord map) throws Exception
|
||||
{
|
||||
return (healthcare(map.getValueString("transactionSet")) || SystemWrites.call(() -> !EdiRecords.query("edi_agreement_document",
|
||||
EdiRecords.equal("mapId", map.getValue("id")).withCriteria("containsPhi", QCriteriaOperator.EQUALS, true)).isEmpty()));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Healthcare document types always suppress values, even with a mismatched map.
|
||||
*******************************************************************************/
|
||||
private static boolean healthcare(String type)
|
||||
{
|
||||
return (type != null && List.of("837", "835", "270", "271", "276", "277", "834").contains(type));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** ORIGINAL is the pinned row; CURRENT follows only the same guide family.
|
||||
*******************************************************************************/
|
||||
public static Guide guide(QRecord map, String revision) throws Exception
|
||||
{
|
||||
Integer id = map.getValueInteger("guideId");
|
||||
if("CURRENT".equals(revision))
|
||||
{
|
||||
var pinnedRows = EdiRecords.queryEnforced("edi_guide", EdiRecords.equal("id", id));
|
||||
if(pinnedRows.isEmpty())
|
||||
{
|
||||
throw (new com.kingsrook.qqq.backend.core.exceptions.QNotFoundException("Not found"));
|
||||
}
|
||||
QRecord pinned = pinnedRows.getFirst();
|
||||
var candidates = EdiRecords.queryEnforced("edi_guide", EdiRecords.equal("name", pinned.getValue("name")).withCriteria("status", QCriteriaOperator.EQUALS, "ACTIVE"));
|
||||
candidates.removeIf(r -> !Objects.equals(r.getValue("partnerId"), pinned.getValue("partnerId"))
|
||||
|| !Objects.equals(r.getValue("standard"), pinned.getValue("standard"))
|
||||
|| !Objects.equals(r.getValue("version"), pinned.getValue("version"))
|
||||
|| !Objects.equals(r.getValue("transactionSet"), pinned.getValue("transactionSet")));
|
||||
candidates.sort(Comparator.comparing((QRecord r) -> Objects.requireNonNullElse(r.getValueInteger("revision"), 0)).reversed());
|
||||
if(!candidates.isEmpty())
|
||||
{
|
||||
id = candidates.getFirst().getValueInteger("id");
|
||||
}
|
||||
}
|
||||
return (new GuideRepository().load(id, true));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Parse and inspect the complete sample before exposing any values.
|
||||
*******************************************************************************/
|
||||
public Map<String, Object> preview(QRecord map, QRecord sample, List<QRecord> overrides, String revision) throws Exception
|
||||
{
|
||||
EdiRuntime runtime = EdiRuntime.current();
|
||||
boolean phi = containsPhi(map);
|
||||
Guide guide = guide(map, revision);
|
||||
List<QRecord> rules = rules(map, overrides);
|
||||
State state = new State();
|
||||
try(var logging = PhiLogScope.open(true); InputStream stream = SystemWrites.call(() -> runtime.payloads().open(new PayloadRef(sample.getValueString("payloadRef"), null, 0))))
|
||||
{
|
||||
if("EDIFACT".equals(map.getValueString("standard")))
|
||||
{
|
||||
var interchanges = new EdifactEnvelopeReader(runtime.config().getMaxSegmentsPerTransaction()).read(stream);
|
||||
state.interchanges = interchanges.size();
|
||||
for(var interchange : interchanges)
|
||||
{
|
||||
state.groups += interchange.groups().size();
|
||||
state.envelope(interchange.findings());
|
||||
interchange.groups().forEach(group -> state.envelope(group.findings()));
|
||||
Map<String, List<ValidationFinding>> all = new LinkedHashMap<>();
|
||||
for(var message : interchange.allMessages())
|
||||
{
|
||||
all.put(message.reference(), document(state, map, sample, rules, guide, message, phi));
|
||||
}
|
||||
if(interchange.unb() != null)
|
||||
{
|
||||
var ack = new ContrlBuilder().build(interchange, all, AckLevel.ALL_TRANSACTIONS, "1", map.getValueString("version"));
|
||||
state.ack("CONTRL", phi ? null : new EdifactWriter().write(ack.segments(), interchange.separators(), ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var interchanges = runtime.reader().read(stream);
|
||||
phi = phi || interchanges.stream().flatMap(i -> i.groups().stream()).flatMap(g -> g.transactions().stream()).anyMatch(t -> healthcare(t.st().value(1)));
|
||||
state.interchanges = interchanges.size();
|
||||
for(var interchange : interchanges)
|
||||
{
|
||||
state.groups += interchange.groups().size();
|
||||
state.envelope(interchange.findings());
|
||||
if(!interchange.findings().isEmpty())
|
||||
{
|
||||
var ta1 = runtime.ta1().build(interchange.isa(), interchange.findings());
|
||||
var isa = AckEnvelope.replyIsa(interchange.isa(), "1", java.time.LocalDateTime.now(runtime.config().getClock()));
|
||||
var reply = new com.kingsrook.qbits.edi.x12.X12Interchange(interchange.separators(), isa,
|
||||
com.kingsrook.qbits.edi.x12.X12Segment.of("IEA", 3, "0", isa.value(13)), List.of(), List.of(ta1), List.of());
|
||||
state.ack("TA1", phi ? null : runtime.writer().write(reply));
|
||||
}
|
||||
for(var group : interchange.groups())
|
||||
{
|
||||
state.envelope(group.findings());
|
||||
Map<String, List<ValidationFinding>> all = new LinkedHashMap<>();
|
||||
for(var transaction : group.transactions())
|
||||
{
|
||||
all.put(transaction.st().value(2), document(state, map, sample, rules, guide, transaction, phi));
|
||||
}
|
||||
boolean use999 = phi && group.gs().value(8).startsWith("005010X");
|
||||
var ack = use999 ? runtime.ack999().build(group, all, AckLevel.ALL_TRANSACTIONS, "0001") : runtime.ack997().build(group, all, AckLevel.ALL_TRANSACTIONS, "0001");
|
||||
state.ack(use999 ? "999" : "997", phi ? null : runtime.writer().write(AckEnvelope.wrap(interchange, group, ack, "1", "1", java.time.LocalDateTime.now(runtime.config().getClock()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException e)
|
||||
{
|
||||
throw (e);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
// Parser, custom transform and expected-JSON diagnostics may contain payload values.
|
||||
throw (new QBadRequestException("Sample could not be previewed; check the document, guide and rules."));
|
||||
}
|
||||
return (object("parse", Map.of("interchanges", state.interchanges, "groups", state.groups, "documents", state.documents),
|
||||
"findings", state.findings, "ack", object("type", state.ackType, "text", phi ? null : state.ackText.toString()),
|
||||
"result", phi ? null : Map.of("records", state.records), "diff", Map.of("againstPreviousRevision", state.previous, "againstExpected", state.expected), "phiSuppressed", phi));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Validate and map one document, collecting only value-free findings for PHI.
|
||||
*******************************************************************************/
|
||||
private List<ValidationFinding> document(State state, QRecord map, QRecord sample, List<QRecord> rules, Guide guide, EdiDocument document, boolean phi) throws Exception
|
||||
{
|
||||
int index = state.documents++;
|
||||
state.envelope(document.findings());
|
||||
var findings = EdiRuntime.current().validator().validate(document, guide, phi);
|
||||
for(var finding : findings)
|
||||
{
|
||||
state.findings.add(object("snipLevel", finding.snipLevel(), "severity", finding.severity(), "segmentPosition", finding.segmentPosition(),
|
||||
"segmentId", finding.segmentId(), "loopId", finding.loopId(), "elementPosition", finding.elementPosition(),
|
||||
"code", finding.elementPosition() == null ? finding.ak3Code() : finding.ak4Code(), "message", phi ? "Validation finding" : finding.message()));
|
||||
}
|
||||
QRecord mapped;
|
||||
try
|
||||
{
|
||||
mapped = new InboundMapper(EdiRuntime.current().transforms()).map(document, guide, map, rules).record();
|
||||
}
|
||||
catch(com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException e)
|
||||
{
|
||||
throw (e);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
state.findings.add(object("snipLevel", 0, "severity", "ERROR", "segmentPosition", null, "segmentId", null, "loopId", null,
|
||||
"elementPosition", null, "code", "MAP", "message", "Mapping failed; check the rule paths and transform arguments"));
|
||||
return (findings);
|
||||
}
|
||||
if(!phi)
|
||||
{
|
||||
Map<String, Object> actual = record(mapped);
|
||||
state.records.add(actual);
|
||||
String expected = sample.getValueString("expectedResultJson");
|
||||
if(expected != null)
|
||||
{
|
||||
difference(state.expected, index, JSON.readValue(expected, Object.class), actual);
|
||||
}
|
||||
Integer revision = map.getValueInteger("revision");
|
||||
var previous = EdiRecords.queryEnforced("edi_map", EdiRecords.equal("name", map.getValue("name")));
|
||||
previous.removeIf(r -> !Objects.equals(r.getValue("partnerId"), map.getValue("partnerId"))
|
||||
|| !Objects.equals(r.getValue("standard"), map.getValue("standard")) || !Objects.equals(r.getValue("transactionSet"), map.getValue("transactionSet"))
|
||||
|| !Objects.equals(r.getValue("direction"), map.getValue("direction")) || !Objects.equals(r.getValue("version"), map.getValue("version"))
|
||||
|| !Objects.equals(r.getValue("tableName"), map.getValue("tableName"))
|
||||
|| revision == null || r.getValueInteger("revision") == null || r.getValueInteger("revision") >= revision);
|
||||
previous.sort(Comparator.comparing((QRecord r) -> r.getValueInteger("revision")).reversed());
|
||||
if(!previous.isEmpty() && !containsPhi(previous.getFirst()))
|
||||
{
|
||||
QRecord prior = previous.getFirst();
|
||||
QRecord priorResult = new InboundMapper(EdiRuntime.current().transforms()).map(document, guide(prior, "ORIGINAL"), prior, rules(prior, null)).record();
|
||||
difference(state.previous, index, record(priorResult), actual);
|
||||
}
|
||||
}
|
||||
return (findings);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Stable record values and recursive associations, without QQQ diagnostics.
|
||||
*******************************************************************************/
|
||||
private static Map<String, Object> record(QRecord record)
|
||||
{
|
||||
Map<String, Object> associations = new LinkedHashMap<>();
|
||||
if(record.getAssociatedRecords() != null)
|
||||
{
|
||||
record.getAssociatedRecords().forEach((name, children) -> associations.put(name, children.stream().map(MapPreviewService::record).toList()));
|
||||
}
|
||||
return (object("values", record.getValues(), "associations", associations));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Compare normalized JSON so temporal values and numeric types match wire output.
|
||||
*******************************************************************************/
|
||||
private static void difference(List<Map<String, Object>> result, int index, Object before, Object after)
|
||||
{
|
||||
if(!JSON.valueToTree(before).equals(JSON.valueToTree(after)))
|
||||
{
|
||||
result.add(object("documentIndex", index, "before", before, "after", after));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Ordered objects allowing explicit nulls on the wire.
|
||||
*******************************************************************************/
|
||||
private static Map<String, Object> object(Object... pairs)
|
||||
{
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
for(int i = 0; i < pairs.length; i += 2)
|
||||
{
|
||||
result.put((String) pairs[i], pairs[i + 1]);
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** One invocation's accumulation; never shared across requests.
|
||||
*******************************************************************************/
|
||||
private static class State
|
||||
{
|
||||
private int interchanges;
|
||||
private int groups;
|
||||
private int documents;
|
||||
private final List<Map<String, Object>> findings = new ArrayList<>();
|
||||
private final List<Map<String, Object>> records = new ArrayList<>();
|
||||
private final List<Map<String, Object>> previous = new ArrayList<>();
|
||||
private final List<Map<String, Object>> expected = new ArrayList<>();
|
||||
private String ackType = "NONE";
|
||||
private final StringBuilder ackText = new StringBuilder();
|
||||
|
||||
/*******************************************************************************
|
||||
** Envelope diagnostics carry no raw envelope identifiers.
|
||||
*******************************************************************************/
|
||||
private void envelope(List<EnvelopeFinding> values)
|
||||
{
|
||||
values.forEach(f -> findings.add(object("snipLevel", 1, "severity", "ERROR", "segmentPosition", f.segmentPosition(),
|
||||
"segmentId", null, "loopId", null, "elementPosition", null, "code", f.code(), "message", "Envelope validation finding")));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Aggregate acknowledgements in wire order.
|
||||
*******************************************************************************/
|
||||
private void ack(String type, String text)
|
||||
{
|
||||
ackType = "NONE".equals(ackType) || type.equals(ackType) ? type : "MIXED";
|
||||
if(text != null)
|
||||
{
|
||||
ackText.append(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* 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.map;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kingsrook.qbits.edi.guide.Guide;
|
||||
import com.kingsrook.qbits.edi.guide.GuideSegment;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
/*******************************************************************************
|
||||
** Static validation of unsaved rules; never runs transforms or writes records.
|
||||
*******************************************************************************/
|
||||
public class MapRuleValidator
|
||||
{
|
||||
private static final Pattern ELEMENT = Pattern.compile("([A-Z][A-Z0-9]{1,2}?)([0-9]{2})(?:-([0-9]+))?");
|
||||
private static final Pattern STEP = Pattern.compile("([^\\[]+)(?:\\[(\\*|[1-9][0-9]*|[^\\[\\]]+=[^\\[\\]]+)])?");
|
||||
|
||||
/*******************************************************************************
|
||||
** Report independent errors without echoing rule literals or default values.
|
||||
*******************************************************************************/
|
||||
public List<String> validate(QRecord rule, QRecord map, Guide guide, TransformRegistry registry)
|
||||
{
|
||||
List<String> errors = new ArrayList<>();
|
||||
if(rule.getValueInteger("sequence") == null || rule.getValueInteger("sequence") < 1)
|
||||
{
|
||||
errors.add("sequence must be a positive integer");
|
||||
}
|
||||
boolean outbound = "OUTBOUND".equals(map.getValueString("direction"));
|
||||
String ediPath = rule.getValueString("ediPath");
|
||||
String transform = Objects.requireNonNullElse(rule.getValueString("transform"), "").toUpperCase(Locale.ROOT);
|
||||
if(ediPath == null || ediPath.isBlank())
|
||||
{
|
||||
if(outbound || !transform.equals("CONSTANT") && rule.getValueString("defaultValue") == null)
|
||||
{
|
||||
errors.add("ediPath is required");
|
||||
}
|
||||
}
|
||||
else if(!resolves(ediPath, guide))
|
||||
{
|
||||
errors.add("ediPath does not resolve in the guide");
|
||||
}
|
||||
String target = rule.getValueString("qqqPath");
|
||||
QTableMetaData table = QContext.getQInstance().getTable(map.getValueString("tableName"));
|
||||
String[] fields = target == null ? new String[0] : target.split("\\.", -1);
|
||||
for(int i = 0; table != null && i < fields.length - 1; i++)
|
||||
{
|
||||
var associations = table.getAssociations();
|
||||
String name = fields[i];
|
||||
var association = associations == null ? null : associations.stream().filter(a -> a.getName().equals(name)).findFirst().orElse(null);
|
||||
table = association == null ? null : QContext.getQInstance().getTable(association.getAssociatedTableName());
|
||||
}
|
||||
boolean generated = outbound && (target == null || target.isBlank()) && List.of("CONSTANT", "SEQUENCE", "HL").contains(transform);
|
||||
if(!generated && (table == null || fields.length == 0 || !table.getFields().containsKey(fields[fields.length - 1])
|
||||
|| !outbound && fields.length > 2))
|
||||
{
|
||||
errors.add("qqqPath does not resolve in the target table");
|
||||
}
|
||||
if(fields.length > 1 && !"OUTBOUND".equals(map.getValueString("direction")) && (ediPath == null || !ediPath.contains("[*]/")))
|
||||
{
|
||||
errors.add("An inbound association requires a wildcard loop");
|
||||
}
|
||||
Map<String, Object> schema = registry.argumentSchemas().get(transform);
|
||||
if(!transform.isEmpty() && schema == null)
|
||||
{
|
||||
errors.add("Unknown transform");
|
||||
}
|
||||
try
|
||||
{
|
||||
String raw = rule.getValueString("transformArgs");
|
||||
var args = new ObjectMapper().readTree(raw == null || raw.isBlank() ? "{}" : raw);
|
||||
if(!args.isObject() || java.util.stream.StreamSupport.stream(java.util.Spliterators.spliteratorUnknownSize(args.elements(), 0), false).anyMatch(v -> !v.isTextual()))
|
||||
{
|
||||
errors.add("transformArgs must be a JSON object of strings");
|
||||
}
|
||||
else if(schema != null && schema.get("required") instanceof List<?> required)
|
||||
{
|
||||
for(Object key : required)
|
||||
{
|
||||
if(!args.hasNonNull(key.toString()))
|
||||
{
|
||||
errors.add("Missing required transform argument: " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
errors.add("transformArgs must be a JSON object of strings");
|
||||
}
|
||||
return (errors);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Resolve every joined element path, including selectors and composite positions.
|
||||
*******************************************************************************/
|
||||
private boolean resolves(String path, Guide guide)
|
||||
{
|
||||
for(String part : path.split("\\+(?![^\\[]*])", -1))
|
||||
{
|
||||
List<GuideSegment> scope = guide.segments();
|
||||
String[] steps = part.trim().split("/", -1);
|
||||
for(int index = 0; index < steps.length; index++)
|
||||
{
|
||||
var step = STEP.matcher(steps[index]);
|
||||
if(!step.matches())
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
String name = step.group(1);
|
||||
String qualifier = step.group(2);
|
||||
var element = ELEMENT.matcher(name);
|
||||
if(index == steps.length - 1)
|
||||
{
|
||||
if(!element.matches() || !element(scope, name))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
scope = scope.stream().filter(s -> s.segmentId().equals(element.group(1))).toList();
|
||||
}
|
||||
else
|
||||
{
|
||||
List<GuideSegment> current = scope;
|
||||
scope = current.stream().filter(s -> s.segmentId().equals(name) || inLoop(s, name, guide, 0)).toList();
|
||||
if(scope.isEmpty())
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
if(qualifier != null && qualifier.contains("=") && !element(scope, qualifier.substring(0, qualifier.indexOf('='))))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Ancestor scope matters when different loops contain the same segment.
|
||||
*******************************************************************************/
|
||||
private boolean inLoop(GuideSegment segment, String loop, Guide guide, int depth)
|
||||
{
|
||||
if(loop.equals(segment.loopId()))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
if(segment.parentLoopId() == null || depth > guide.segments().size())
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
return (loop.equals(segment.parentLoopId()) || guide.segments().stream().filter(s -> segment.parentLoopId().equals(s.loopId()))
|
||||
.anyMatch(parent -> inLoop(parent, loop, guide, depth + 1)));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Match an element and its optional composite component.
|
||||
*******************************************************************************/
|
||||
private boolean element(List<GuideSegment> segments, String path)
|
||||
{
|
||||
var match = ELEMENT.matcher(path);
|
||||
if(!match.matches())
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
int position = Integer.parseInt(match.group(2));
|
||||
Integer component;
|
||||
try
|
||||
{
|
||||
component = match.group(3) == null ? null : Integer.valueOf(match.group(3));
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
return (segments.stream().filter(s -> s.segmentId().equals(match.group(1))).flatMap(s -> s.elements().stream())
|
||||
.anyMatch(e -> e.position() == position && (component == null || component.equals(e.componentPosition()))));
|
||||
}
|
||||
}
|
||||
@@ -62,10 +62,7 @@ public class OutboundMapper
|
||||
throw new IllegalArgumentException("An outbound map is required");
|
||||
}
|
||||
List<X12Segment> segments = new ArrayList<>();
|
||||
String version = map.getValueString("version");
|
||||
segments.add(version != null && version.startsWith("005010X")
|
||||
? X12Segment.of("ST", 1, map.getValueString("transactionSet"), control, version)
|
||||
: X12Segment.of("ST", 1, map.getValueString("transactionSet"), control));
|
||||
segments.add(X12Segment.of("ST", 1, map.getValueString("transactionSet"), control));
|
||||
List<QRecord> sorted = rules.stream().sorted(Comparator.comparing(r -> r.getValueInteger("sequence"))).toList();
|
||||
emit(source, sorted, "", "", 1, "", new int[] {0}, segments);
|
||||
segments.add(X12Segment.of("SE", segments.size() + 1, String.valueOf(segments.size() + 1), control));
|
||||
@@ -75,7 +72,7 @@ public class OutboundMapper
|
||||
/*******************************************************************************
|
||||
** Walk each loop once per associated record, retaining the nearest HL parent.
|
||||
*******************************************************************************/
|
||||
protected void emit(QRecord source, List<QRecord> rules, String prefix, String qPrefix, int sequence, String parent,
|
||||
private 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 +109,13 @@ public class OutboundMapper
|
||||
}
|
||||
continue;
|
||||
}
|
||||
List<List<String>> elements = new ArrayList<>();
|
||||
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())
|
||||
if(!match.matches() || match.group(3) != null)
|
||||
{
|
||||
throw new IllegalArgumentException("Unsupported outbound element path");
|
||||
}
|
||||
@@ -130,7 +127,7 @@ public class OutboundMapper
|
||||
}
|
||||
while(elements.size() < position)
|
||||
{
|
||||
elements.add(new ArrayList<>());
|
||||
elements.add("");
|
||||
}
|
||||
String condition = rule.getValueString("condition");
|
||||
if(condition != null && !condition.isBlank() && !new RuleEvaluator(output, Map.of()).test(condition))
|
||||
@@ -172,7 +169,7 @@ public class OutboundMapper
|
||||
}
|
||||
else if(transform.equals("DECIMAL"))
|
||||
{
|
||||
value = decimal(new java.math.BigDecimal(raw.toString()).movePointRight(Integer.parseInt(args.getOrDefault("scale", "0"))).stripTrailingZeros().toPlainString());
|
||||
value = new java.math.BigDecimal(raw.toString()).movePointRight(Integer.parseInt(args.getOrDefault("scale", "0"))).stripTrailingZeros().toPlainString();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,35 +180,16 @@ public class OutboundMapper
|
||||
{
|
||||
throw new IllegalArgumentException("Required outbound field is missing");
|
||||
}
|
||||
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, ""));
|
||||
elements.set(position - 1, Objects.requireNonNullElse(value, ""));
|
||||
}
|
||||
if(elements.stream().flatMap(List::stream).anyMatch(v -> !v.isEmpty()))
|
||||
if(elements.stream().anyMatch(v -> !v.isEmpty()))
|
||||
{
|
||||
output.add(new X12Segment(segmentId, output.size() + 1, elements.stream().map(e -> List.of(e.isEmpty() ? List.of("") : e)).toList()));
|
||||
output.add(X12Segment.of(segmentId, output.size() + 1, elements.toArray(String[]::new)));
|
||||
if("HL".equals(segmentId))
|
||||
{
|
||||
currentParent = elements.getFirst().getFirst();
|
||||
currentParent = elements.getFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** X12 uses a period; EDIFACT can override the numeric service character.
|
||||
*******************************************************************************/
|
||||
protected String decimal(String value)
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* 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.map;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
/*******************************************************************************
|
||||
** The bundled sample maps (synthetic, one per built-in document and version)
|
||||
** and their insert-only installation. Installed maps are drafts, bound to no
|
||||
** partner and to no agreement document, so installing one never changes how
|
||||
** any document is processed; the target table in each is an example name the
|
||||
** host replaces before activating.
|
||||
*******************************************************************************/
|
||||
public final class SampleMaps
|
||||
{
|
||||
public static final List<String> RESOURCES = List.of("204-004010", "204-005010", "204-out-004010", "204-out-005010", "214-004010", "214-005010",
|
||||
"810-004010", "810-005010", "846-004010", "846-005010", "850-004010", "850-005010", "855-004010", "855-005010", "856-004010", "856-005010",
|
||||
"860-004010", "860-005010", "940-004010", "940-005010", "945-004010", "945-005010", "945-in-004010", "945-in-005010",
|
||||
"DESADV-D01B", "DESADV-D96A", "INVOIC-D01B", "INVOIC-D96A", "ORDERS-D01B", "ORDERS-D96A", "ORDRSP-D01B", "ORDRSP-D96A");
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
private SampleMaps()
|
||||
{
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** One bundled sample: the map's values and its rules.
|
||||
*******************************************************************************/
|
||||
public static JsonNode read(String resource) throws IOException
|
||||
{
|
||||
try(InputStream input = SampleMaps.class.getResourceAsStream("/edi/maps/" + resource + "-sample.json"))
|
||||
{
|
||||
if(input == null)
|
||||
{
|
||||
throw (new IOException("Unknown sample map " + resource));
|
||||
}
|
||||
return (new ObjectMapper().readTree(input));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** The bundled samples whose map name does not exist yet (any revision, any
|
||||
** partner), in bundle order. Reads the map table as the processing user.
|
||||
*******************************************************************************/
|
||||
public static List<String> missing() throws QException, IOException
|
||||
{
|
||||
Set<String> existing = new HashSet<>();
|
||||
for(QRecord map : SystemWrites.query("edi_map", new QQueryFilter()))
|
||||
{
|
||||
existing.add(map.getValueString("name"));
|
||||
}
|
||||
List<String> missing = new ArrayList<>();
|
||||
for(String resource : RESOURCES)
|
||||
{
|
||||
if(!existing.contains(read(resource).path("map").path("name").asText()))
|
||||
{
|
||||
missing.add(resource);
|
||||
}
|
||||
}
|
||||
return (missing);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** The map record a sample installs as: a draft, revision 0, no partner,
|
||||
** bound to the built-in guide for its document and version when present.
|
||||
*******************************************************************************/
|
||||
public static QRecord mapRecord(String resource) throws IOException, QException
|
||||
{
|
||||
JsonNode map = read(resource).path("map");
|
||||
QRecord record = new QRecord();
|
||||
map.fields().forEachRemaining(e ->
|
||||
{
|
||||
if(!e.getValue().isNull() && !List.of("status", "revision", "partnerId", "guideId", "parentMapId").contains(e.getKey()))
|
||||
{
|
||||
record.withValue(e.getKey(), e.getValue().asText());
|
||||
}
|
||||
});
|
||||
record.withValue("status", "DRAFT").withValue("revision", 0);
|
||||
Integer guideId = null;
|
||||
int revision = -1;
|
||||
for(QRecord guide : SystemWrites.query("edi_guide", new QQueryFilter().withCriteria("transactionSet", QCriteriaOperator.EQUALS, map.path("transactionSet").asText())
|
||||
.withCriteria("version", QCriteriaOperator.EQUALS, map.path("version").asText()).withCriteria("source", QCriteriaOperator.EQUALS, "BUILT_IN")
|
||||
.withCriteria("status", QCriteriaOperator.EQUALS, "ACTIVE")))
|
||||
{
|
||||
int candidate = guide.getValueInteger("revision") == null ? 0 : guide.getValueInteger("revision");
|
||||
if(candidate > revision)
|
||||
{
|
||||
revision = candidate;
|
||||
guideId = guide.getValueInteger("id");
|
||||
}
|
||||
}
|
||||
return (record.withValue("guideId", guideId));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Insert one sample and its ordered rules. Skips (returns null) a sample
|
||||
** whose map name now exists, so a repeated run adds nothing. It is an
|
||||
** administrator action; two people running it at the same moment is not
|
||||
** guarded against (edi_map has no unique name, since revisions share one).
|
||||
*******************************************************************************/
|
||||
public static Integer install(String resource) throws Exception
|
||||
{
|
||||
JsonNode sample = read(resource);
|
||||
String name = sample.path("map").path("name").asText();
|
||||
if(!EdiRecords.query("edi_map", EdiRecords.equal("name", name)).isEmpty())
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
QRecord map = SystemWrites.insert("edi_map", mapRecord(resource));
|
||||
for(JsonNode rule : sample.path("rules"))
|
||||
{
|
||||
QRecord row = new QRecord();
|
||||
rule.fields().forEachRemaining(e ->
|
||||
{
|
||||
if(!e.getValue().isNull())
|
||||
{
|
||||
row.withValue(e.getKey(), e.getValue().isInt() ? (java.io.Serializable) e.getValue().intValue() : e.getValue().isBoolean() ? (java.io.Serializable) e.getValue().booleanValue() : e.getValue().asText());
|
||||
}
|
||||
});
|
||||
SystemWrites.insert("edi_map_rule", row.withValue("mapId", map.getValue("id")));
|
||||
}
|
||||
return (map.getValueInteger("id"));
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
public class TransformRegistry
|
||||
{
|
||||
private final Map<String, Transform> transforms = new ConcurrentHashMap<>();
|
||||
private final Map<String, Map<String, Object>> argumentSchemas = new ConcurrentHashMap<>();
|
||||
|
||||
/*******************************************************************************
|
||||
** Transform a selected path's values in the service session.
|
||||
@@ -119,59 +118,11 @@ public class TransformRegistry
|
||||
** Register or replace a named host transform.
|
||||
*******************************************************************************/
|
||||
public TransformRegistry register(String name, Transform transform)
|
||||
{
|
||||
return (register(name, transform, schema(name)));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Host transforms publish their argument contract alongside executable behavior.
|
||||
*******************************************************************************/
|
||||
public TransformRegistry register(String name, Transform transform, Map<String, Object> argumentSchema)
|
||||
{
|
||||
transforms.put(name.toUpperCase(Locale.ROOT), transform);
|
||||
argumentSchemas.put(name.toUpperCase(Locale.ROOT), Map.copyOf(argumentSchema));
|
||||
return (this);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Sorted snapshot for mapper clients and static validation.
|
||||
*******************************************************************************/
|
||||
public Map<String, Map<String, Object>> argumentSchemas()
|
||||
{
|
||||
return (java.util.Collections.unmodifiableMap(new java.util.TreeMap<>(argumentSchemas)));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Built-ins accept strings, matching the persisted transformArgs representation.
|
||||
*******************************************************************************/
|
||||
private static Map<String, Object> schema(String name)
|
||||
{
|
||||
List<String> keys = switch(name.toUpperCase(Locale.ROOT))
|
||||
{
|
||||
case "CONSTANT" -> List.of("value");
|
||||
case "LOOKUP" -> List.of("table", "key", "result");
|
||||
case "DATETIME" -> List.of("time", "timezone");
|
||||
case "DATEPART", "TIMEPART" -> List.of("timezone");
|
||||
case "DECIMAL" -> List.of("scale");
|
||||
case "CONCAT" -> List.of("separator");
|
||||
case "SPLIT" -> List.of("separator", "index");
|
||||
case "SUBSTRING" -> List.of("start", "end");
|
||||
case "UNIT" -> List.of("factor", "divisor", "scale");
|
||||
case "SEQUENCE" -> List.of("start");
|
||||
case "HL" -> List.of("code");
|
||||
default -> List.of();
|
||||
};
|
||||
Map<String, Object> properties = new java.util.TreeMap<>();
|
||||
keys.forEach(key -> properties.put(key, Map.of("type", "string")));
|
||||
List<String> required = switch(name.toUpperCase(Locale.ROOT))
|
||||
{
|
||||
case "CONSTANT" -> List.of("value");
|
||||
case "LOOKUP" -> List.of("table", "key");
|
||||
default -> List.of();
|
||||
};
|
||||
return (Map.of("type", "object", "properties", properties, "required", required, "additionalProperties", Map.of("type", "string")));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute a closed registry entry; unknown names fail explicitly.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
/*******************************************************************************
|
||||
** RC.6 form filters; no dependency on later dependency-behavior metadata.
|
||||
** RC.5 form filters; no dependency on later dependency-behavior metadata.
|
||||
*******************************************************************************/
|
||||
public final class EdiContingencies
|
||||
{
|
||||
@@ -75,9 +75,7 @@ public final class EdiContingencies
|
||||
}
|
||||
if(filter != null)
|
||||
{
|
||||
// Core appends its search criteria to the root; keep them ANDed with partner scope.
|
||||
field.withPossibleValueSourceFilter(filter.getBooleanOperator() == QQueryFilter.BooleanOperator.OR
|
||||
? new QQueryFilter().withSubFilter(filter) : filter);
|
||||
field.withPossibleValueSourceFilter(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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.metadata;
|
||||
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.bundles.HelpBundles;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Applies help/qbit-edi.yaml (the QQQ help-bundle format) to the Edi app
|
||||
** and its edi_* tables: what each table is for and how its rows get created,
|
||||
** shown on the table screen and when it is empty.
|
||||
** Fallback for hosts without the helpContent table. Hosts with that table use
|
||||
** the core bundle registry and startup sync, including HOST and USER overrides.
|
||||
*******************************************************************************/
|
||||
public final class EdiHelp
|
||||
{
|
||||
public static final String RESOURCE = "/help/qbit-edi.yaml";
|
||||
public static final String SLOT = "help";
|
||||
public static final String APP_KEY = "edi";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
private EdiHelp()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The bundle's entries by key, validated (fails startup if malformed).
|
||||
*******************************************************************************/
|
||||
public static Map<String, JsonNode> entries() throws QException
|
||||
{
|
||||
Map<String, JsonNode> entries = new LinkedHashMap<>();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
for(var entry : HelpBundles.load("help/qbit-edi.yaml", EdiHelp.class.getClassLoader()).getEntries())
|
||||
{
|
||||
entries.put(entry.getKey(), mapper.valueToTree(entry));
|
||||
}
|
||||
return (entries);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Apply the help to the app (by its configured name) and every edi_* table. A
|
||||
** host's own help on a table is kept (the bundle only fills the slot).
|
||||
*******************************************************************************/
|
||||
public static void apply(QInstance qInstance, String appName) throws QException
|
||||
{
|
||||
Map<String, JsonNode> entries = entries();
|
||||
for(QTableMetaData table : qInstance.getTables().values())
|
||||
{
|
||||
JsonNode entry = entries.get("table:" + table.getName() + ";slot:" + SLOT);
|
||||
if(entry != null && (table.getHelpContent() == null || !table.getHelpContent().containsKey(SLOT)))
|
||||
{
|
||||
table.withHelpContent(SLOT, content(entry));
|
||||
}
|
||||
}
|
||||
QAppMetaData app = qInstance.getApp(appName);
|
||||
JsonNode appEntry = entries.get("app:" + APP_KEY + ";slot:" + SLOT);
|
||||
if(app != null && appEntry != null && (app.getHelpContent() == null || !app.getHelpContent().containsKey(SLOT)))
|
||||
{
|
||||
app.withHelpContent(SLOT, content(appEntry));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Preserve the tooltip summary separately from the Markdown long help.
|
||||
*******************************************************************************/
|
||||
static QHelpContent content(JsonNode entry)
|
||||
{
|
||||
return (new QHelpContent().withSummary(entry.path("summary").asText().strip()).withContentAsMarkdown(entry.path("content").asText().strip()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,10 +51,10 @@ import com.kingsrook.qbits.edi.model.EdiPayload;
|
||||
import com.kingsrook.qbits.edi.model.EdiTransaction;
|
||||
import com.kingsrook.qbits.edi.model.EdiTransactionError;
|
||||
import com.kingsrook.qbits.edi.model.EdiTransport;
|
||||
import com.kingsrook.qbits.edi.security.EdiClassifications;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.AiExposure;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.AiFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.AiTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@@ -232,12 +232,11 @@ public final class EdiTables
|
||||
QTableMetaData table = new QTableMetaData().withName(name).withBackendName(config.getBackendName())
|
||||
.withPrimaryKeyField("id").withFieldsFromEntity(entity).withRecordLabelFormatAndFields("%s", label)
|
||||
.withPermissionRules(new QPermissionRules().withPermissionBaseName("edi." + name.substring(4)).withLevel(PermissionLevel.READ_INSERT_EDIT_DELETE_PERMISSIONS))
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(readOnly || name.equals("edi_map_sample") ? AuditLevel.RECORD : AuditLevel.FIELD));
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(readOnly ? AuditLevel.RECORD : AuditLevel.FIELD));
|
||||
QRecordEntity.registerTable(entity, table);
|
||||
AiTableMetaData.ofOrWithNew(table).withClassification(DataClassification.CONFIDENTIAL);
|
||||
if(List.of("edi_payload", "edi_certificate", "edi_inbox").contains(name))
|
||||
{
|
||||
AiTableMetaData.ofOrWithNew(table).withExposure(AiExposure.NONE);
|
||||
table.withSupplementalMetaData(new AiTableMetaData().withExposure(AiExposure.NONE));
|
||||
}
|
||||
if(name.equals("edi_exception"))
|
||||
{
|
||||
@@ -254,14 +253,13 @@ public final class EdiTables
|
||||
}
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
DataClassification classification = List.of("phiBusinessKey", "fileName", "ciphertext", "expectedResultJson", "valueExcerpt").contains(field.getName()) ? DataClassification.PHI
|
||||
: List.of("contactName", "contactEmail", "contactPhone").contains(field.getName()) ? DataClassification.PII : DataClassification.CONFIDENTIAL;
|
||||
AiFieldMetaData metadata = new AiFieldMetaData().withClassification(classification);
|
||||
if(classification == DataClassification.PHI)
|
||||
String classification = List.of("phiBusinessKey", "fileName", "ciphertext", "expectedResultJson", "valueExcerpt").contains(field.getName()) ? "PHI"
|
||||
: List.of("contactName", "contactEmail", "contactPhone").contains(field.getName()) ? "PII" : "CONFIDENTIAL";
|
||||
field.withSupplementalMetaData(new EdiClassifications(classification));
|
||||
if(classification.equals("PHI"))
|
||||
{
|
||||
metadata.withExposure(AiExposure.NONE);
|
||||
field.withSupplementalMetaData(new AiFieldMetaData().withExposure(AiExposure.NONE));
|
||||
}
|
||||
field.withSupplementalMetaData(metadata);
|
||||
}
|
||||
EdiTablePresentation.apply(table);
|
||||
EdiRecordLabels.configure(table);
|
||||
@@ -283,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", "CONTRL"));
|
||||
sources.add(enumSource("edi_ack_expectation", "NONE", "997", "999"));
|
||||
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", "UNB", "UNG", "UNH"));
|
||||
sources.add(enumSource("edi_level", "ISA", "GS"));
|
||||
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"));
|
||||
@@ -307,7 +305,7 @@ public final class EdiTables
|
||||
sources.add(enumSource("edi_interchange_status", "RECEIVED", "ACCEPTED", "ACCEPTED_WITH_ERRORS", "REJECTED", "DUPLICATE", "ENVELOPED", "SENT", "DELIVERED", "FAILED"));
|
||||
sources.add(enumSource("edi_ack_status", "A", "E", "P", "R", "NONE", "PENDING"));
|
||||
sources.add(enumSource("edi_transaction_status", "RECEIVED", "VALID", "INVALID", "MAPPED", "APPLIED", "EXCEPTION", "QUEUED", "SENT", "ACKNOWLEDGED", "REJECTED", "IGNORED"));
|
||||
sources.add(enumSource("edi_event_type", "RECEIVED", "STORED", "PARSED", "VALIDATED", "ACK_SENT", "ACK_QUEUED", "MAPPED", "APPLIED", "ENVELOPED", "SENT", "MDN", "DELIVERED", "ACK_RECEIVED", "EXCEPTION", "REPROCESSED", "RESENT", "RAW_DOWNLOADED"));
|
||||
sources.add(enumSource("edi_event_type", "RECEIVED", "STORED", "PARSED", "VALIDATED", "ACK_SENT", "ACK_QUEUED", "MAPPED", "APPLIED", "ENVELOPED", "SENT", "MDN", "DELIVERED", "ACK_RECEIVED", "EXCEPTION", "REPROCESSED", "RESENT"));
|
||||
sources.add(enumSource("edi_exception_type", "UNKNOWN_PARTNER", "PARTNER_MISMATCH", "NOT_CONFIGURED", "DUPLICATE", "ENVELOPE", "VALIDATION", "MAPPING", "APPLY", "TRANSPORT", "REJECTED_ACK", "OVERDUE_ACK", "DEAD_LETTER"));
|
||||
sources.add(enumSource("edi_exception_status", "OPEN", "IN_PROGRESS", "RESOLVED", "IGNORED"));
|
||||
sources.add(enumSource("edi_inbox_status", "PENDING", "PROCESSING", "PROCESSED", "RETRY_WAIT", "DEAD_LETTER", "DUPLICATE", "IGNORED"));
|
||||
@@ -424,8 +422,7 @@ public final class EdiTables
|
||||
{
|
||||
return (joins().stream().map(join -> ChildRecordListRenderer.widgetMetaDataBuilder(join).withName(join.getName())
|
||||
.withLabel(joinLabel(join)).withCanAddChildRecord(false)
|
||||
.withOrderBys(childOrder(join.getRightTable())).getWidgetMetaData()
|
||||
.withCodeReference(new QCodeReference(com.kingsrook.qbits.edi.app.EdiChildRecordListRenderer.class)).withGridColumns(12)).toList());
|
||||
.withOrderBys(childOrder(join.getRightTable())).getWidgetMetaData().withGridColumns(12)).toList());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
@@ -33,15 +33,6 @@ 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;
|
||||
|
||||
@@ -848,79 +839,4 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -32,9 +32,6 @@ import com.kingsrook.qbits.edi.map.MapPreview;
|
||||
import com.kingsrook.qbits.edi.storage.EdiRecords;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.alerts.NotificationAudience;
|
||||
import com.kingsrook.qqq.backend.core.alerts.NotificationSignal;
|
||||
import com.kingsrook.qqq.backend.core.alerts.QAlerts;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
@@ -78,17 +75,12 @@ public final class EdiProcesses
|
||||
{
|
||||
result.add(action(table, "preview", "Preview Map", "edi.map.preview"));
|
||||
}
|
||||
for(String table : List.of("edi_inbox", "edi_interchange"))
|
||||
{
|
||||
result.add(action(table, "download", "Download Raw Document", "edi.raw.download").withMaxInputRecords(1));
|
||||
}
|
||||
result.add(action("edi_exception", "assign", "Assign", "edi.exception.assign"));
|
||||
result.add(action("edi_exception", "ignore", "Ignore", "edi.exception.ignore"));
|
||||
result.add(action("edi_exception", "resolve", "Mark Resolved", "edi.exception.resolve"));
|
||||
result.add(action("edi_guide", "override", "Create Partner Override", "edi.guide.override"));
|
||||
result.add(action("edi_guide", "activate", "Activate Guide", "edi.guide.activate"));
|
||||
result.add(action("edi_map", "activate", "Activate Map", "edi.map.activate"));
|
||||
result.add(InstallSampleMapsProcess.process());
|
||||
QProcessMetaData scheduled = new QProcessMetaData().withName("edi_inbox_worker").withLabel("Process Inbox").withIsHidden(true)
|
||||
.withPermissionRules(permission("edi.admin")).withStep(new QBackendStepMetaData().withName("run").withCode(new QCodeReference(WorkerStep.class)));
|
||||
if(config.getSchedulerName() != null)
|
||||
@@ -138,8 +130,6 @@ public final class EdiProcesses
|
||||
.withName(table + "_" + operation).withLabel(label).withTableName(table).withIsHidden(true).withMinInputRecords(1).withPermissionRules(permission(permission));
|
||||
if(operation.equals("preview"))
|
||||
{
|
||||
process.getPermissionRules().withCustomPermissionChecker(
|
||||
com.kingsrook.qqq.backend.core.actions.permissions.UseOtherPermissionNameCustomPermissionChecker.build("edi.map.preview"));
|
||||
if(table.equals("edi_map"))
|
||||
{
|
||||
process.getFrontendStep("review").withFormField(new QFieldMetaData("sampleId", QFieldType.INTEGER)
|
||||
@@ -158,16 +148,6 @@ public final class EdiProcesses
|
||||
process.getFrontendStep("result").withViewField(new QFieldMetaData(field, QFieldType.STRING));
|
||||
}
|
||||
}
|
||||
if(operation.equals("download"))
|
||||
{
|
||||
process.getFrontendStep("result").withComponent(new com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData()
|
||||
.withType(com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType.DOWNLOAD_FORM));
|
||||
for(String field : List.of("downloadFileName", "storageTableName", "storageReference"))
|
||||
{
|
||||
process.getBackendStep("execute").getOutputMetaData().withField(new QFieldMetaData(field, QFieldType.STRING));
|
||||
process.getFrontendStep("result").withViewField(new QFieldMetaData(field, QFieldType.STRING));
|
||||
}
|
||||
}
|
||||
if(operation.equals("assign"))
|
||||
{
|
||||
process.getFrontendStep("review").withFormField(new QFieldMetaData("assignedToUserId", QFieldType.STRING).withLabel("Assign to user ID").withIsRequired(true));
|
||||
@@ -193,7 +173,7 @@ public final class EdiProcesses
|
||||
public void runOnePage(RunBackendStepInput input, RunBackendStepOutput output)
|
||||
{
|
||||
count += input.getRecords().size();
|
||||
input.getRecords().forEach(record -> output.getRecords().add(new QRecord().withValue("id", record.getValue("id"))));
|
||||
output.getRecords().addAll(input.getRecords());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -238,20 +218,6 @@ public final class EdiProcesses
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.OK, 1, "public certificate exported"));
|
||||
}
|
||||
else if(operation.equals("download"))
|
||||
{
|
||||
if(!QContext.getQSession().hasPermission("edi.raw.download"))
|
||||
{
|
||||
throw new QException("Raw document download is not authorized");
|
||||
}
|
||||
var storage = new com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput(com.kingsrook.qbits.edi.storage.PreviewDownloadBackend.TABLE)
|
||||
.withReference("raw:" + table + ":" + row.getValueInteger("id"));
|
||||
com.kingsrook.qqq.backend.core.actions.processes.ProcessFileDownload.registerStorage(storage);
|
||||
output.addValue("downloadFileName", "edi-document.edi");
|
||||
output.addValue("storageTableName", storage.getTableName());
|
||||
output.addValue("storageReference", storage.getReference());
|
||||
lines.add(new ProcessSummaryLine(Status.OK, 1, "Document ready for authorized download"));
|
||||
}
|
||||
else if(operation.equals("preview"))
|
||||
{
|
||||
QRecord map = table.equals("edi_map") ? row : EdiRecords.query("edi_map", EdiRecords.equal("id", row.getValue("mapId"))).getFirst();
|
||||
@@ -322,25 +288,13 @@ public final class EdiProcesses
|
||||
QRecord update = new QRecord().withValue("id", row.getValue("id")).withValue("status", operation.equals("assign") ? "IN_PROGRESS" : operation.equals("ignore") ? "IGNORED" : "RESOLVED");
|
||||
if(operation.equals("assign"))
|
||||
{
|
||||
String assignee = input.getValueString("assignedToUserId");
|
||||
if(assignee == null || assignee.isBlank())
|
||||
{
|
||||
throw new IllegalArgumentException("An assignee is required");
|
||||
}
|
||||
update.withValue("assignedToUserId", assignee);
|
||||
update.withValue("assignedToUserId", input.getValue("assignedToUserId"));
|
||||
}
|
||||
else
|
||||
{
|
||||
update.withValue("resolvedBy", user).withValue("resolvedAt", EdiRuntime.current().config().getClock().instant());
|
||||
}
|
||||
SystemWrites.update(table, update);
|
||||
if(operation.equals("assign"))
|
||||
{
|
||||
QAlerts.notify(new NotificationSignal("EDI exception assigned")
|
||||
.withBody("An EDI exception has been assigned to you.")
|
||||
.withAudience(NotificationAudience.user(update.getValueString("assignedToUserId")))
|
||||
.withSourceName("edi").withSourceTableName(table).withSourceRecordId(row.getValueString("id")));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -362,7 +316,7 @@ public final class EdiProcesses
|
||||
output.addValue("storageTableName", storage.getTableName());
|
||||
output.addValue("storageReference", ref.value());
|
||||
}
|
||||
output.getRecords().add(new QRecord().withValue("id", selected.getValue("id")));
|
||||
output.getRecords().add(selected);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* 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.processes;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.edi.map.SampleMaps;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractExtractStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractLoadStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ProcessSummaryProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
|
||||
/*******************************************************************************
|
||||
** "Install sample maps": a table action on edi_map that adds the bundled
|
||||
** synthetic sample maps whose name does not exist yet. Standard streamed ETL:
|
||||
** Review lists what will be added, Result what was added. Insert-only by map
|
||||
** name, so existing maps and edits are never touched. Installed maps are
|
||||
** drafts with no partner and no agreement document, so nothing changes how a
|
||||
** document is processed until an administrator activates and binds one.
|
||||
*******************************************************************************/
|
||||
public final class InstallSampleMapsProcess
|
||||
{
|
||||
public static final String NAME = "edi_map_installSamples";
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
private InstallSampleMapsProcess()
|
||||
{
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** The process: a table-level action (no records need to be selected).
|
||||
*******************************************************************************/
|
||||
public static QProcessMetaData process()
|
||||
{
|
||||
Map<String, Serializable> values = new LinkedHashMap<>();
|
||||
values.put(StreamedETLWithFrontendProcess.FIELD_SOURCE_TABLE, "edi_map");
|
||||
values.put(StreamedETLWithFrontendProcess.FIELD_DESTINATION_TABLE, "edi_map");
|
||||
values.put(StreamedETLWithFrontendProcess.FIELD_SUPPORTS_FULL_VALIDATION, false);
|
||||
values.put(StreamedETLWithFrontendProcess.FIELD_PREVIEW_MESSAGE,
|
||||
"These sample maps will be added as drafts, with no partner and no agreement document. Maps that already exist are not changed.");
|
||||
QProcessMetaData process = StreamedETLWithFrontendProcess.defineProcessMetaData(Extract.class, Transform.class, Load.class, values);
|
||||
process.setName(NAME);
|
||||
process.setLabel("Install sample maps");
|
||||
process.setIcon(new QIcon("playlist_add"));
|
||||
process.setTableName("edi_map");
|
||||
process.setIsHidden(true);
|
||||
process.setMinInputRecords(0);
|
||||
process.setPermissionRules(new QPermissionRules().withPermissionBaseName("edi.admin").withLevel(PermissionLevel.HAS_ACCESS_PERMISSION));
|
||||
return (process);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** The samples still to install, as the processing user.
|
||||
*******************************************************************************/
|
||||
static List<String> missing() throws QException
|
||||
{
|
||||
return (SystemWrites.call(SampleMaps::missing));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** A summary line with the standard four wordings.
|
||||
*******************************************************************************/
|
||||
private static ProcessSummaryLine line(Status status, String future, String futurePlural, String past, String pastPlural)
|
||||
{
|
||||
ProcessSummaryLine line = new ProcessSummaryLine(status, 0, null);
|
||||
line.setSingularFutureMessage(future);
|
||||
line.setPluralFutureMessage(futurePlural);
|
||||
line.setSingularPastMessage(past);
|
||||
line.setPluralPastMessage(pastPlural);
|
||||
return (line);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Extract: the missing samples, computed (no query of selected records).
|
||||
*******************************************************************************/
|
||||
public static class Extract extends AbstractExtractStep
|
||||
{
|
||||
/*******************************************************************************
|
||||
** How many samples will be added.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Integer doCount(RunBackendStepInput input) throws QException
|
||||
{
|
||||
return (missing().size());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** One record per missing sample.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
||||
{
|
||||
for(String resource : missing())
|
||||
{
|
||||
QRecord map = SystemWrites.call(() -> SampleMaps.mapRecord(resource));
|
||||
getRecordPipe().addRecord(map.withValue("sample", resource));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Transform: forward the records and count them by standard and direction.
|
||||
*******************************************************************************/
|
||||
public static class Transform extends AbstractTransformStep implements ProcessSummaryProviderInterface
|
||||
{
|
||||
private final Map<String, ProcessSummaryLine> byKind = new LinkedHashMap<>();
|
||||
|
||||
/*******************************************************************************
|
||||
** Count by standard and direction.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void runOnePage(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
||||
{
|
||||
for(QRecord record : input.getRecords())
|
||||
{
|
||||
String kind = record.getValueString("standard") + " " + record.getValueString("direction").toLowerCase();
|
||||
byKind.computeIfAbsent(kind, k -> line(Status.OK, k + " sample map will be added as a draft", k + " sample maps will be added as drafts",
|
||||
k + " sample map was added as a draft", k + " sample maps were added as drafts")).incrementCount();
|
||||
output.getRecords().add(record);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Standard Review summary.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput output, boolean isForResultScreen)
|
||||
{
|
||||
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
|
||||
byKind.values().forEach(line -> line.addSelfToListIfAnyCount(lines));
|
||||
if(lines.isEmpty())
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, null, "Every sample map is already installed."));
|
||||
}
|
||||
return (lines);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Load: insert each map and its rules as the processing user.
|
||||
*******************************************************************************/
|
||||
public static class Load extends AbstractLoadStep implements ProcessSummaryProviderInterface
|
||||
{
|
||||
private final ProcessSummaryLine added = line(Status.OK, "map will be added", "maps will be added", "map was added as a draft", "maps were added as drafts");
|
||||
|
||||
/*******************************************************************************
|
||||
** One transaction per map, so a map never lands without its rules.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void runOnePage(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
||||
{
|
||||
for(QRecord record : input.getRecords())
|
||||
{
|
||||
Integer id = SystemWrites.call(() -> SystemWrites.transaction(() -> SampleMaps.install(record.getValueString("sample"))));
|
||||
if(id != null)
|
||||
{
|
||||
added.incrementCountAndAddPrimaryKey(id);
|
||||
output.getRecords().add(new QRecord().withValue("id", id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Standard Result summary.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput output, boolean isForResultScreen)
|
||||
{
|
||||
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
|
||||
added.addSelfToListIfAnyCount(lines);
|
||||
return (lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,11 +61,7 @@ public class OutboundProcesses implements BackendStep
|
||||
.withStep(new QBackendStepMetaData().withName("execute").withCode(new QCodeReference(OutboundProcesses.class)));
|
||||
if(config.getSchedulerName() != null && (seconds != null || cron != null))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// core validation refuses a cron time zone on a repeat schedule //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
process.withSchedule(new QScheduleMetaData().withSchedulerName(config.getSchedulerName()).withRepeatSeconds(seconds).withCronExpression(cron)
|
||||
.withCronTimeZoneId(cron == null ? null : "UTC"));
|
||||
process.withSchedule(new QScheduleMetaData().withSchedulerName(config.getSchedulerName()).withRepeatSeconds(seconds).withCronExpression(cron).withCronTimeZoneId("UTC"));
|
||||
}
|
||||
return (process);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.security;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QSupplementalFieldMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** STAND-IN(core=QRun-IO/qqq#1142, removeBy=4.1.0-RC.6). Field classifications on RC.5.
|
||||
*******************************************************************************/
|
||||
public class EdiClassifications extends QSupplementalFieldMetaData
|
||||
{
|
||||
private final String classification;
|
||||
|
||||
/*******************************************************************************
|
||||
** Retain classification as metadata until the core enum is available.
|
||||
*******************************************************************************/
|
||||
public EdiClassifications(String classification)
|
||||
{
|
||||
this.classification = classification;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Stable supplemental metadata key.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getType()
|
||||
{
|
||||
return ("edi.classification");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Read the classification without requiring an RC.6 class.
|
||||
*******************************************************************************/
|
||||
public String getClassification()
|
||||
{
|
||||
return (classification);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Bridge when a host supplies the newer field classification API.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void enrich(QInstance instance, QFieldMetaData field)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> type = Class.forName("com.kingsrook.qqq.backend.core.actions.ai.DataClassification");
|
||||
Object value = type.getField(classification).get(null);
|
||||
field.getClass().getMethod("setDataClassification", type).invoke(field, value);
|
||||
}
|
||||
catch(ClassNotFoundException e)
|
||||
{
|
||||
// RC.5 keeps the explicit supplemental classification below.
|
||||
}
|
||||
catch(ReflectiveOperationException e)
|
||||
{
|
||||
throw new IllegalStateException("Update the EDI classification bridge for this core version", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* 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.security;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.Logger;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.apache.logging.log4j.core.config.Configuration;
|
||||
import org.apache.logging.log4j.core.filter.AbstractFilter;
|
||||
import org.apache.logging.log4j.message.Message;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** STAND-IN(core=QRun-IO/qqq#1147, removeBy=4.1.0-RC.7).
|
||||
** RC.6 logs query criteria verbatim. Suppress synchronous diagnostic logging only
|
||||
** while PHI mapping/application runs; value-free EDI events remain in the database.
|
||||
** This does not replace host masking or protection of host code outside this scope.
|
||||
*******************************************************************************/
|
||||
public final class PhiLogScope implements AutoCloseable
|
||||
{
|
||||
private static final ThreadLocal<Integer> DEPTH = ThreadLocal.withInitial(() -> 0);
|
||||
private static final Map<Configuration, Boolean> INSTALLED = new WeakHashMap<>();
|
||||
private final int previous;
|
||||
|
||||
/*******************************************************************************
|
||||
** Protect nested PHI work without changing logger levels or unrelated threads.
|
||||
*******************************************************************************/
|
||||
private PhiLogScope(boolean enabled)
|
||||
{
|
||||
previous = DEPTH.get();
|
||||
if(enabled)
|
||||
{
|
||||
install();
|
||||
DEPTH.set(previous + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Open a scope around code that may pass clinical values to RC.6 core actions.
|
||||
*******************************************************************************/
|
||||
public static PhiLogScope open(boolean enabled)
|
||||
{
|
||||
return (new PhiLogScope(enabled));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Install once per logger configuration, including after a host reconfiguration.
|
||||
*******************************************************************************/
|
||||
private static synchronized void install()
|
||||
{
|
||||
if(!(LogManager.getContext(false) instanceof LoggerContext context))
|
||||
{
|
||||
throw new IllegalStateException("PHI processing requires the RC.6 Log4j protection bridge");
|
||||
}
|
||||
Configuration configuration = context.getConfiguration();
|
||||
if(!INSTALLED.containsKey(configuration))
|
||||
{
|
||||
ScopeFilter filter = new ScopeFilter();
|
||||
filter.start();
|
||||
configuration.addFilter(filter);
|
||||
INSTALLED.put(configuration, true);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Always restore nesting, including failed mapping or target transactions.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
if(previous == 0)
|
||||
{
|
||||
DEPTH.remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
DEPTH.set(previous);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Pre-event filter prevents sensitive diagnostics from reaching any appender.
|
||||
*******************************************************************************/
|
||||
private static class ScopeFilter extends AbstractFilter
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Decide before Log4j creates or formats a data-bearing event.
|
||||
*******************************************************************************/
|
||||
private Result decision()
|
||||
{
|
||||
return (DEPTH.get() > 0 ? Result.DENY : Result.NEUTRAL);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** QLogger uses structured Log4j messages.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Result filter(Logger logger, Level level, Marker marker, Message message, Throwable error)
|
||||
{
|
||||
return (decision());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Object diagnostics from third-party mapping or target customizers.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Result filter(Logger logger, Level level, Marker marker, Object message, Throwable error)
|
||||
{
|
||||
return (decision());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Parameterized diagnostics from registered host extensions.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Result filter(Logger logger, Level level, Marker marker, String message, Object... parameters)
|
||||
{
|
||||
return (decision());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Synchronous event filtering for configurations invoking this filter later.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Result filter(LogEvent event)
|
||||
{
|
||||
return (decision());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,17 +60,6 @@ public final class EdiRecords
|
||||
return (new QueryAction().execute(new QueryInput(table).withFilter(filter).withShouldFetchHeavyFields(true).withTransaction(com.kingsrook.qqq.backend.core.context.QContext.getQBackendTransaction())).getRecords());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** UI-facing reads enforce permissions even when the host disables default enforcement.
|
||||
*******************************************************************************/
|
||||
public static List<QRecord> queryEnforced(String table, QQueryFilter filter) throws QException
|
||||
{
|
||||
QueryInput input = new QueryInput(table).withFilter(filter).withShouldFetchHeavyFields(true)
|
||||
.withTransaction(com.kingsrook.qqq.backend.core.context.QContext.getQBackendTransaction());
|
||||
input.setPermissionEnforcement(com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement.ENFORCE);
|
||||
return (new QueryAction().execute(input).getRecords());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Match one field by exact value.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -71,10 +71,6 @@ public class PreviewDownloadBackend implements QBackendModuleInterface, QStorage
|
||||
{
|
||||
throw new QException("Preview download is not authorized for this session");
|
||||
}
|
||||
if(input.getReference().startsWith("raw:"))
|
||||
{
|
||||
return (RawDocumentDownload.open(input.getReference()));
|
||||
}
|
||||
return (SystemWrites.call(() -> EdiRuntime.current().payloads().open(new PayloadRef(input.getReference(), null, 0))));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* 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.storage;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.edi.EdiRuntime;
|
||||
import com.kingsrook.qbits.edi.support.SystemWrites;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Stream an authorized archived document, with a value-free access event per download.
|
||||
*******************************************************************************/
|
||||
public final class RawDocumentDownload
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Recheck permission and partner locks on every access, including registered URLs.
|
||||
*******************************************************************************/
|
||||
public static InputStream open(String reference) throws QException
|
||||
{
|
||||
String[] parts = reference.split(":");
|
||||
if(parts.length != 3 || !parts[0].equals("raw") || !List.of("edi_inbox", "edi_interchange").contains(parts[1])
|
||||
|| QContext.getQSession() == null || !QContext.getQSession().hasPermission("edi.raw.download"))
|
||||
{
|
||||
throw new QException("Raw document download is not authorized");
|
||||
}
|
||||
Integer id;
|
||||
try
|
||||
{
|
||||
id = Integer.valueOf(parts[2]);
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
throw new QException("Invalid raw document reference");
|
||||
}
|
||||
QueryInput query = new QueryInput(parts[1]).withFilter(EdiRecords.equal("id", id));
|
||||
query.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
|
||||
List<QRecord> rows = new QueryAction().execute(query).getRecords();
|
||||
if(rows.size() != 1 || rows.getFirst().getValueString("payloadRef") == null)
|
||||
{
|
||||
throw new QException("Raw document is unavailable");
|
||||
}
|
||||
QRecord row = rows.getFirst();
|
||||
String user = QContext.getQSession().getUser() == null ? "system" : java.util.Objects.toString(QContext.getQSession().getUser().getIdReference(), "system");
|
||||
return (SystemWrites.call(() ->
|
||||
{
|
||||
SystemWrites.insert("edi_event", new QRecord().withValue("type", "RAW_DOWNLOADED").withValue("userName", user)
|
||||
.withValue("inboxId", parts[1].equals("edi_inbox") ? id : row.getValueInteger("inboxId"))
|
||||
.withValue("interchangeId", parts[1].equals("edi_interchange") ? id : null)
|
||||
.withValue("at", EdiRuntime.current().config().getClock().instant()).withValue("detail", "Raw document download authorized"));
|
||||
return (EdiRuntime.current().payloads().open(new PayloadRef(row.getValueString("payloadRef"), null, 0)));
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -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", new String(bytes, java.nio.charset.StandardCharsets.US_ASCII).stripLeading().startsWith("UN") ? "application/EDIFACT" : "application/edi-x12").POST(HttpRequest.BodyPublishers.ofByteArray(bytes));
|
||||
.header("Content-Type", "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,8 +50,7 @@ public class ControlNumberAllocator
|
||||
*******************************************************************************/
|
||||
public long allocate(Integer agreementId, Integer localIdentityId, String level, long min, long max, boolean wrap) throws SQLException
|
||||
{
|
||||
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)
|
||||
if((agreementId == null) == (localIdentityId == null) || !List.of("ISA", "GS").contains(level) || min < 1 || max < min || max > 999999999)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid control number scope or bounds");
|
||||
}
|
||||
@@ -101,7 +100,7 @@ public class ControlNumberAllocator
|
||||
return (min);
|
||||
}
|
||||
}
|
||||
if(minimum < 1 || maximum < minimum || maximum > ceiling || previous < minimum - 1 || previous > maximum)
|
||||
if(minimum < 1 || maximum < minimum || maximum > 999999999 || previous < minimum - 1 || previous > maximum)
|
||||
{
|
||||
throw new IllegalStateException("Stored control number range is invalid");
|
||||
}
|
||||
|
||||
@@ -55,18 +55,6 @@ 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,15 +27,6 @@ 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) implements com.kingsrook.qbits.edi.document.EdiDocument
|
||||
public record X12TransactionSet(X12Segment st, X12Segment se, List<X12Segment> segments, List<EnvelopeFinding> findings)
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Expose the header to standard-neutral validators and maps.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public X12Segment header()
|
||||
{
|
||||
return (st);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,600 +0,0 @@
|
||||
{
|
||||
"name": "QQQ baseline guide 270 005010X279A1",
|
||||
"standard": "X12",
|
||||
"version": "005010X279A1",
|
||||
"transactionSet": "270",
|
||||
"functionalIdCode": "HS",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "ST",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "270"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"fixedValue": "005010X279A1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BHT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 4
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "DT",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000A",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "20"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100A",
|
||||
"parentLoopId": "2000A",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "PR"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000B",
|
||||
"parentLoopId": "2000A",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "21"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100B",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "1P"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000C",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "22"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "TRN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 2,
|
||||
"loopId": "2000C",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "IL"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N3",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 55
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N4",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 30
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DMG",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "DT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "EQ",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2110C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"codes": [
|
||||
"30"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "SE",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 10
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,699 +0,0 @@
|
||||
{
|
||||
"name": "QQQ baseline guide 271 005010X279A1",
|
||||
"standard": "X12",
|
||||
"version": "005010X279A1",
|
||||
"transactionSet": "271",
|
||||
"functionalIdCode": "HB",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "ST",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "271"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"fixedValue": "005010X279A1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BHT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 4
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "DT",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000A",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "20"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100A",
|
||||
"parentLoopId": "2000A",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "PR"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000B",
|
||||
"parentLoopId": "2000A",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "21"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100B",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "1P"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000C",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "22"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "TRN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 2,
|
||||
"loopId": "2000C",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "IL"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N3",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 55
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N4",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 30
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DMG",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "DT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "AAA",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "EB",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2110C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 7,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HSD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2110C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2110C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2110C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "AAA",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2110C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "SE",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 10
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"type": "SITUATIONAL",
|
||||
"expression": "when(present(TRN02), or(present(EB), present(AAA)))",
|
||||
"snipLevel": 4,
|
||||
"severity": "ERROR",
|
||||
"message": "Benefits or rejection reason required for inquiry response"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,762 +0,0 @@
|
||||
{
|
||||
"name": "QQQ baseline guide 276 005010X212",
|
||||
"standard": "X12",
|
||||
"version": "005010X212",
|
||||
"transactionSet": "276",
|
||||
"functionalIdCode": "HR",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "ST",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "276"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"fixedValue": "005010X212"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BHT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 4
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "DT",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000A",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "20"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100A",
|
||||
"parentLoopId": "2000A",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "PR"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000B",
|
||||
"parentLoopId": "2000A",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "21"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100B",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "1P"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000C",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "22"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "TRN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 2,
|
||||
"loopId": "2000C",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "IL"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N3",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 55
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N4",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 30
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DMG",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "DT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "TRN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "STC",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "DT",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "AMT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "SVC",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2220C",
|
||||
"parentLoopId": "2200C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2220C",
|
||||
"parentLoopId": "2200C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "SE",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 10
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,762 +0,0 @@
|
||||
{
|
||||
"name": "QQQ baseline guide 277 005010X212",
|
||||
"standard": "X12",
|
||||
"version": "005010X212",
|
||||
"transactionSet": "277",
|
||||
"functionalIdCode": "HN",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "ST",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "277"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"fixedValue": "005010X212"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BHT",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 4
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "DT",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000A",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "20"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100A",
|
||||
"parentLoopId": "2000A",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "PR"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000B",
|
||||
"parentLoopId": "2000A",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "21"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100B",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "1P"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HL",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000C",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "N0",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 12
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "22"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "TRN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 2,
|
||||
"loopId": "2000C",
|
||||
"parentLoopId": "2000B",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "IL"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N3",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 55
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N4",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 30
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DMG",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "DT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2100C",
|
||||
"parentLoopId": "2000C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "TRN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "STC",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "DT",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "AMT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2200C",
|
||||
"parentLoopId": "2100C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "SVC",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2220C",
|
||||
"parentLoopId": "2200C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"componentPosition": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2220C",
|
||||
"parentLoopId": "2200C",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "SE",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 10
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,539 +0,0 @@
|
||||
{
|
||||
"name": "QQQ baseline guide 834 005010X220A1",
|
||||
"standard": "X12",
|
||||
"version": "005010X220A1",
|
||||
"transactionSet": "834",
|
||||
"functionalIdCode": "BE",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "ST",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "834"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"fixedValue": "005010X220A1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BGN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "DT",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "TM",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N1",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "1000A",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "P5"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N1",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "1000B",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "IN"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "INS",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"Y",
|
||||
"N"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"type": "SITUATIONAL",
|
||||
"expression": "when(eq(INS01, 'Y'), present(DMG))",
|
||||
"snipLevel": 4,
|
||||
"severity": "ERROR",
|
||||
"message": "Subscriber demographics required",
|
||||
"ik3Code": "8",
|
||||
"ik4Code": "2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 10,
|
||||
"loopId": "2000",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2000",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100A",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "IL"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"codes": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N3",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100A",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 55
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N4",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100A",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 30
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2100A",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DMG",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100A",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "DT",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "HD",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2300",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTP",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2300",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"codes": [
|
||||
"D8",
|
||||
"RD8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2300",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "SE",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 10
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,951 +0,0 @@
|
||||
{
|
||||
"name": "QQQ baseline guide 835 005010X221A1",
|
||||
"standard": "X12",
|
||||
"version": "005010X221A1",
|
||||
"transactionSet": "835",
|
||||
"functionalIdCode": "HP",
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "ST",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3,
|
||||
"fixedValue": "835"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 35,
|
||||
"fixedValue": "005010X221A1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "BPR",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "R",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 1
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 16,
|
||||
"dataType": "DT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "TRN",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5
|
||||
},
|
||||
{
|
||||
"segmentId": "N1",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "1000A",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "PR"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N3",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "1000A",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 55
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N4",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "1000A",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 30
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "1000A",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N1",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "1000B",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"fixedValue": "PE"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N3",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "1000B",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 55
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "N4",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "1000B",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 30
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "1000B",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "LX",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2000",
|
||||
"parentLoopId": null,
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 6
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CLP",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 38
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "R",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "R",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 5,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "CAS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 99,
|
||||
"loopId": "2100",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "R",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 5,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 6,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 7,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 10,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 11,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 12,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 13,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 14,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 15,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 16,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 17,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 18,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 19,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "NM1",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 10,
|
||||
"loopId": "2100",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "AN",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "MIA",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "MOA",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2100",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 10,
|
||||
"loopId": "2100",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 10,
|
||||
"loopId": "2100",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "AMT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 10,
|
||||
"loopId": "2100",
|
||||
"parentLoopId": "2000",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "SVC",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 1,
|
||||
"loopId": "2110",
|
||||
"parentLoopId": "2100",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2,
|
||||
"componentPosition": 1
|
||||
},
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"componentPosition": 2,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "HCPCS"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "R",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "R",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "DTM",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2110",
|
||||
"parentLoopId": "2100",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "CAS",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 99,
|
||||
"loopId": "2110",
|
||||
"parentLoopId": "2100",
|
||||
"loopMaxRepeat": 999,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "ID",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"dataType": "R",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 5,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 6,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 7,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 9,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 10,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 11,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 12,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 13,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 14,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 15,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 16,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
},
|
||||
{
|
||||
"position": 17,
|
||||
"dataType": "ID",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 5,
|
||||
"source": "EXTERNAL",
|
||||
"externalCodeSet": "CARC"
|
||||
},
|
||||
{
|
||||
"position": 18,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 19,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 15
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "REF",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 10,
|
||||
"loopId": "2110",
|
||||
"parentLoopId": "2100",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "AMT",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 5,
|
||||
"loopId": "2110",
|
||||
"parentLoopId": "2100",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "LQ",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 20,
|
||||
"loopId": "2110",
|
||||
"parentLoopId": "2100",
|
||||
"loopMaxRepeat": 999
|
||||
},
|
||||
{
|
||||
"segmentId": "PLB",
|
||||
"usage": "SITUATIONAL",
|
||||
"minUse": 0,
|
||||
"maxUse": 99,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "DT",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 8
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"dataType": "R",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 6,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 10,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 12,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
{
|
||||
"position": 14,
|
||||
"dataType": "R",
|
||||
"usage": "SITUATIONAL",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"segmentId": "SE",
|
||||
"usage": "REQUIRED",
|
||||
"minUse": 1,
|
||||
"maxUse": 1,
|
||||
"elements": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataType": "N0",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 10
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"dataType": "AN",
|
||||
"usage": "REQUIRED",
|
||||
"minLength": 1,
|
||||
"maxLength": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "QQQ baseline guide 999 005010X231A1",
|
||||
"name": "QQQ baseline 999 005010X231A1",
|
||||
"standard": "X12",
|
||||
"version": "005010X231A1",
|
||||
"transactionSet": "999",
|
||||
|
||||
@@ -1,474 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,474 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,410 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,410 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,430 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,430 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
{
|
||||
"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\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
{
|
||||
"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\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user