Compare commits

..
6 Commits
Author SHA1 Message Date
forge-recovery dba6990929 Merge pull request 'ci: add the QRun release caller' (#42) from ci-qrun-release-caller-main-v1 into main
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
Shared Java Maven CI / secrets (push) Successful in 17s
QRun-IO/ci-policy: native / native-result (push) Successful in 4s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (push) Successful in 17s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
Shared Java Maven CI / verify (push) Successful in 54s
Shared Java Maven CI / gate (push) Successful in 10s
qrun-release / release (push) Successful in 57m2s
qrun-release / postpublish (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI / verify (push) Successful in 49s
QRun-IO/ci-policy: Shared Java Maven CI / gate (push) Successful in 9s
QRun-IO/ci-policy: ci / java-maven-6 (push) Successful in 57m49s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 5s
2026-11-02 18:44:12 +00:00
24a1896519 ci: add the isolated postpublish job to the QRun release caller
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
Shared Java Maven CI / secrets (push) Successful in 18s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (pull_request) Successful in 16s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
Shared Java Maven CI / gate (push) Canceled after 0s
Shared Java Maven CI / verify (push) Canceled after 0s
qrun-release / release (push) Canceled after 2h37m57s
qrun-release / postpublish (push) Canceled after 0s
QRun-IO/ci-policy: Shared Java Maven CI / verify (pull_request) Successful in 54s
QRun-IO/ci-policy: Shared Java Maven CI / gate (pull_request) Successful in 8s
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Successful in 2h53m20s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 4s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 15:49:11 +00:00
bc770fc71c ci: move the QRun release caller to the thin v2 contract
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-2 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 4s
Shared Java Maven CI / secrets (push) Successful in 25s
QRun-IO/ci-policy: Shared Java Maven CI / verify (pull_request) Failing after 1m12s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (pull_request) Successful in 20s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 6s
QRun-IO/ci-policy: Shared Java Maven CI / gate (pull_request) Canceled after 0s
QRun-IO/ci-policy: ci / java-maven-4 (pull_request) Canceled after 1h5m34s
Shared Java Maven CI / gate (push) Canceled after 0s
Shared Java Maven CI / verify (push) Canceled after 0s
qrun-release / release (push) Canceled after 1h6m1s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 06:18:04 +00:00
forge-recoveryandCodex be364eb871 ci: add the identical QRun release caller
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-2 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI / secrets (pull_request) Successful in 23s
QRun-IO/ci-policy: Shared Java Maven CI / verify (pull_request) Failing after 1m6s
QRun-IO/ci-policy: Shared Java Maven CI / gate (pull_request) Failing after 0s
QRun-IO/ci-policy: ci / java-maven-4 (pull_request) Failing after 11m4s
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 4s
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 04:10:50 +00:00
james.maesandCodex 344087dd43 ci: enroll in the shared QQQ orb (#38)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 19:51:58 -05:00
james.maesandCodex 02f3055c94 ci: use the shared orb pipeline on main (#32)
* ci: use the shared orb pipeline on main

Co-Authored-By: Codex <noreply@openai.com>

* ci: use the shared branch-aware CodeQL worker

Co-Authored-By: Codex <noreply@openai.com>

---------

Co-authored-by: Codex <noreply@openai.com>
2026-10-21 10:20:38 -05:00
199 changed files with 1135 additions and 39802 deletions

No files matched your search

+11 -57
View File
@@ -1,60 +1,14 @@
version: 2.1
setup: true
orbs:
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
qqq-orb: kingsrook/qqq-orb@0.8
workflows:
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+$/]
setup:
jobs:
- qqq-orb/generate_pipeline:
orb_version: '0.8'
filters:
branches:
ignore: automation/qbit-rc-pending
tags:
ignore: /.*/
-20
View File
@@ -1,20 +0,0 @@
# QRun-IO qbit .editorconfig: the Kingsrook layout (3-space indentation, never tabs).
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 3
tab_width = 3
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[*.{cmd,bat}]
end_of_line = crlf
-3
View File
@@ -1,3 +0,0 @@
* text=auto eol=lf
*.cmd text eol=crlf
mvnw text eol=lf
+24
View File
@@ -0,0 +1,24 @@
# Standard QRun release caller (v2, thin contract): every push and manual dispatch delegates to the protected qrun-release-v1 channel. Do not edit per repository.
name: qrun-release
on:
push: {}
workflow_dispatch: {}
permissions:
contents: read
actions: read
jobs:
release:
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/java-maven.yaml@qrun-release-v1
with:
runner: linux-small
browsers: true
browser-runner: browser-build
postpublish:
if: ${{ needs.release.outputs.capture == 'true' }}
needs: release
permissions:
contents: write
actions: write
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/qrun-source-version.yaml@qrun-release-v1
with:
runner: linux-small
-25
View File
@@ -1,25 +0,0 @@
# Weekly, grouped dependency updates. Minor and patch updates arrive as one PR per ecosystem;
# major updates stay separate so each gets its own review. At most three version-update PRs per
# ecosystem are open at once. Security updates are enabled in the repository settings and are
# not limited by this schedule.
version: 2
updates:
- package-ecosystem: maven
directory: /
target-branch: develop
schedule:
interval: weekly
open-pull-requests-limit: 3
groups:
maven-minor-and-patch:
update-types: [minor, patch]
- package-ecosystem: github-actions
directory: /
target-branch: develop
schedule:
interval: weekly
open-pull-requests-limit: 3
groups:
github-actions:
patterns: ['*']
+23
View File
@@ -0,0 +1,23 @@
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
-25
View File
@@ -1,25 +0,0 @@
# 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:
workflow_dispatch:
inputs:
dry_run:
description: Print the plan without merging or creating branches
type: boolean
default: false
pull_request:
types: [closed]
branches: [develop]
permissions:
contents: write
jobs:
release-rc:
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 }}
+25 -15
View File
@@ -1,21 +1,31 @@
# Maven
target/
.env
.idea
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
# IDE
.idea/
*.iml
.project
.classpath
.settings/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Build
*.class
*.log
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
hs_err_pid*
.DS_Store
*.sw*
.flattened-pom.xml
dependency-reduced-pom.xml
/.env.local
/.cache/
.claude/settings.local.json
# Logs
*.log
logs/
-3
View File
@@ -1,3 +0,0 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
+15
View File
@@ -0,0 +1,15 @@
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: none
test_branch_policy: all
publish:
snapshot: false
release_candidate: false
release: false
asciidoc: false
-62
View File
@@ -1,62 +0,0 @@
# AGENTS.md
This file provides guidance to Codex when working with code in this repository.
## Project Overview
**QBit EDI**: a full EDI application qbit for QQQ. Trading partners and agreements, pluggable
transports (AS2, SFTP/FTPS, VAN, HTTPS, email), X12 (004010, 005010; EDIFACT later) parsing and
validation against data-driven implementation guides, 997/999/TA1, versioned maps to QQQ records,
inbound and outbound flows, and HIPAA documents with PHI classification. Design: `docs/DESIGN.md`.
## Build Commands
```bash
./mvnw compile
./mvnw test
./mvnw verify # Checkstyle, tests, JaCoCo: 70% instructions, 90% classes
```
Always use the wrapper (Maven 3.9.11); Maven 3.10 cannot read the parent POM.
## Architecture
Entry point: `EdiQBitProducer implements QBitMetaDataProducer<EdiQBitConfig>` in
`com.kingsrook.qbits.edi`. Component producers are scanned only in `com.kingsrook.qbits.edi.metadata`.
The package layout per phase is in `docs/DESIGN.md`.
## Invariants
- Data lives in standard visible QQQ tables with record screens, secured by permissions and record locks.
- Processes are table or record actions with plain labels, never in the nav; record actions use the
standard streamed-ETL Review/Result summary.
- One app home that owns the EDI data, built from typed widgets (no `RawHTML`, guardrail G5).
- Inbound transport endpoints are non-UI routes under `/qqq/hooks/edi/...`, never under `/qqq/v1`.
UI endpoints are spec'd `MiddlewareSpecExtension`s under `/qqq/v1/qbit/edi/...` with a committed fragment.
- Records are created and updated through normal QQQ actions as a service account.
- Raw documents and PHI fields are classified (`DataClassification`), stored encrypted.
- Secrets are referenced by environment-variable name only. No real PHI in tests or samples.
- No dependency on another qbit's internals (guardrail G1); notifications go through core `QAlerts`.
## Naming
- Tables: `edi_<name>`, lowercase snake_case; SQL names equal QQQ table names.
- Permissions: full name, `edi.*`.
## Code Style
- Copy the Apache header from `checkstyle/license.txt`; 3-space indent, Allman braces, `return (x);`.
- Flower-box Javadoc on every class and method; fluent `withX()` setters.
- Wrapper types in config and entities; JUnit 5 and AssertJ tests.
## Dependencies
Java 21; parent `com.kingsrook:qbit-build-parent:2.0.0`. Default-active `qqq-snapshot` imports QQQ
`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. Planned: StAEDI and as2-lib (both Apache-2.0).
## Release
CircleCI qqq-orb 0.6.8: `develop` publishes SNAPSHOTs, `release/*` publishes RCs (orb version
bumps; cut by the `release-rc.yml` workflow), `vX.Y.Z` tags publish releases. No manual publishing.
Branches are `feature/*` or `fix/*` only; never push `hotfix/*` or `release/*`.
-31
View File
@@ -1,31 +0,0 @@
# Changelog
Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Added
- Phase b retail baseline guides for 855/856/810/846/860 in 004010 and 005010; inbound/outbound sample maps and nested outbound associations with SEQUENCE/HL transforms.
- Transactional RECORD_CHANGE and manual enqueue, host-wired ESB process, batch schedules, validated encrypted envelopes, send workers, retries and dead-letter alerts.
- Instance transport registry, MINA SFTP streaming push/poll with pinned host keys, gateway OUTBOX stand-in/download delivery, and authenticated HTTPS ingress/egress using core Standard Webhooks.
- Ack and certificate monitoring, functional-ack exception resolution, resend/re-envelope/cancel actions, transport tests and outbound queue home statistics.
- Test-scope H2/Next UI demo with synthetic retail traffic, in-memory gateway and public self-signed certificates; local HTTP/SFTP and application acceptance tests.
- Phase a2 inbound vertical slice: gateway intake, fenced inbox claims/retries, dead letters and core alerts; routing, validation, duplicates, safe events and exception occurrence counts.
- Versioned inbound maps, wildcard associations, transforms, permission-enforced transactional upsert and original/current revision replay.
- Queued enveloped TA1/997/999 and inbound acknowledgement reconciliation; outbound request/message tables for phase b.
- Public-API-only encrypted object-storage adapter and marked file-gateway contract stand-in.
- EDI app, typed home with permission-scoped counts, main-table search metadata, optional ESB events with identifier-only document payloads, and standard Review/Result actions.
- Synthetic sample upload, preview comparisons and encrypted JSON downloads; guide overrides and guide/map activation.
- Contingent possible values on existing/new tables, including actual filtered-search tests.
- PostgreSQL schema additions and UTC zone-less timestamps matching RC.5 RDBMS bindings.
- Phase a1 data model: 21 annotated entities, QQQ tables, enum/table value sources, joins, child lists, audit, permissions, partner locks, and PostgreSQL DDL with metadata-derived H2 integration tests.
- StAEDI 1.26.4 X12 streaming envelopes, separator detection, bounded transaction buffers, structured envelope findings, and fixed-width ISA serialization.
- Original 850/997/999 baseline guides, versioned database loading, SNIP 1–2 validation, syntax notes, and the documented situational rule language.
- TA1, 997 and 999 builders with acknowledgement levels, positional situational context, reply envelopes, and inbound acknowledgement parsing.
- Atomic RDBMS control-number allocation with first-use creation, wrap/exhaustion handling, and interchange/group/transaction duplicate detection.
- Database payload storage with AES-256-GCM envelope encryption, SHA-256 integrity metadata, key rotation, and replaceable environment-secret resolution.
- Per-instance runtime services and configuration, synthetic generators, exact acknowledgement fixtures, concurrent SQL tests, and payload tests that verify no temporary files are created.
- 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`).
+17 -54
View File
@@ -1,62 +1,25 @@
# CLAUDE.md
# qbit-template-application
This file provides guidance to Claude Code when working with code in this repository.
GitHub template repository for scaffolding Application QBits (complete mini-applications
with tables, processes, widgets, and UI navigation) on the QQQ platform.
## Project Overview
## QQQ 4.0 baseline — 2026-09-24
**QBit EDI**: a full EDI application qbit for QQQ. Trading partners and agreements, pluggable
transports (AS2, SFTP/FTPS, VAN, HTTPS, email), X12 (004010, 005010; EDIFACT later) parsing and
validation against data-driven implementation guides, 997/999/TA1, versioned maps to QQQ records,
inbound and outbound flows, and HIPAA documents with PHI classification. Design: `docs/DESIGN.md`.
The historical review below predates the 4.0 migration. This template targets Java 21 and the public QQQ 4.0.0 release from Maven Central. The corrected APIs, license references and actual template commands are in README.md and docs/00-getting-started.md. Preserve the documented multi-instance and scaffold limitations.
## Build Commands
## Knowledge base
```bash
./mvnw compile
./mvnw test
./mvnw verify # Checkstyle, tests, JaCoCo: 70% instructions, 90% classes
```
Reviewed dossier and platform knowledge live in the second-brain vault:
Always use the wrapper (Maven 3.9.11); Maven 3.10 cannot read the parent POM.
- Hub: `R:/Git.Local/KofTwentyTwo/second-brain/knowledge/qqq/qqq-hub.md`
- This repo's dossier: `R:/Git.Local/KofTwentyTwo/second-brain/knowledge/qqq/repos/qbit-template-application.md`
- QBit mechanics refresher: `R:/Git.Local/KofTwentyTwo/second-brain/knowledge/qqq/architecture/metadata-model.md`
## Architecture
Reviewed at commit `fb9395ac0d07` (branch `main`, 2026-07-04).
Entry point: `EdiQBitProducer implements QBitMetaDataProducer<EdiQBitConfig>` in
`com.kingsrook.qbits.edi`. Component producers are scanned only in `com.kingsrook.qbits.edi.metadata`.
The package layout per phase is in `docs/DESIGN.md`.
## Invariants
- Data lives in standard visible QQQ tables with record screens, secured by permissions and record locks.
- Processes are table or record actions with plain labels, never in the nav; record actions use the
standard streamed-ETL Review/Result summary.
- One app home that owns the EDI data, built from typed widgets (no `RawHTML`, guardrail G5).
- Inbound transport endpoints are non-UI routes under `/qqq/hooks/edi/...`, never under `/qqq/v1`.
UI endpoints are spec'd `MiddlewareSpecExtension`s under `/qqq/v1/qbit/edi/...` with a committed fragment.
- Records are created and updated through normal QQQ actions as a service account.
- Raw documents and PHI fields are classified (`DataClassification`), stored encrypted.
- Secrets are referenced by environment-variable name only. No real PHI in tests or samples.
- No dependency on another qbit's internals (guardrail G1); notifications go through core `QAlerts`.
## Naming
- Tables: `edi_<name>`, lowercase snake_case; SQL names equal QQQ table names.
- Permissions: full name, `edi.*`.
## Code Style
- Copy the Apache header from `checkstyle/license.txt`; 3-space indent, Allman braces, `return (x);`.
- Flower-box Javadoc on every class and method; fluent `withX()` setters.
- Wrapper types in config and entities; JUnit 5 and AssertJ tests.
## Dependencies
Java 21; parent `com.kingsrook:qbit-build-parent:2.0.0`. Default-active `qqq-snapshot` imports QQQ
`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. Planned: StAEDI and as2-lib (both Apache-2.0).
## Release
CircleCI qqq-orb 0.6.8: `develop` publishes SNAPSHOTs, `release/*` publishes RCs (orb version
bumps; cut by the `release-rc.yml` workflow), `vX.Y.Z` tags publish releases. No manual publishing.
Branches are `feature/*` or `fix/*` only; never push `hotfix/*` or `release/*`.
Key caveat from that review: the scaffold in `src/main` references six qqq APIs that do not
exist in any qqq version (wrong packages for `MetaDataProducerInterface` /
`AbstractWidgetRenderer` / `RenderWidgetInput`, plus nonexistent `QInstance.addAppSection`,
`QProcessMetaData.withInputFields`, `QAppSection.withSourceQBitName`) — it has never compiled,
and CI on `main` is red. Verify against the qqq checkout (`R:/Git.Local/QRunIO/qqq`) before
trusting any API usage shown here.
-3
View File
@@ -1,3 +0,0 @@
# Code of Conduct
See Project level [Code of Conduct](https://github.com/QRun-IO/qqq/blob/develop/CODE_OF_CONDUCT.md)
-3
View File
@@ -1,3 +0,0 @@
# Code Style Guide
* See Project [Code Style Guide](https://github.com/QRun-IO/qqq/blob/develop/CODE_STYLE.md)
-5
View File
@@ -1,5 +0,0 @@
# Contributing
Follow [QQQ contributing guidance](https://github.com/QRun-IO/qqq/blob/develop/CONTRIBUTING.md).
Use Java 21 and `./mvnw verify`, and follow local Checkstyle. Create feature branches from `develop` and target `develop` with PRs. See [AGENTS.md](AGENTS.md) for architecture, naming, security and release conventions, and [the design](https://github.com/QRun-IO/qqq/pull/1137) before extending the qbit.
+1 -1
View File
@@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS
Copyright 2024 Kingsrook, LLC
Copyright 2022-2025 QRunIO, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+3 -3
View File
@@ -1,6 +1,6 @@
QBit EDI
Copyright 2026 Kingsrook, LLC
qBit Template - Application
Copyright 2022-2025 QRunIO, LLC
This product includes software developed at Kingsrook, LLC (https://kingsrook.com/).
This product includes software developed at QRunIO, LLC (https://qrun.io/).
Licensed under the Apache License, Version 2.0.
+42 -203
View File
@@ -1,216 +1,55 @@
# QBit EDI
# QBit Template - Application
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).
Template repository for creating **Application QBits** - complete, self-contained applications or major functional modules for the QQQ framework.
## Status
## When to Use This Template
Phases a1, a2 and b implement inbound/outbound retail EDI. 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 and email sending remain disabled.
A runnable synthetic [demo](docs/DEMO.md) includes Next UI and needs no external services.
Use this template when building:
- Complete mini-applications (WMS, OMS, CRM)
- Major functional modules with multiple tables
- QBits that require UI navigation (QAppSection)
- Self-contained features with business logic
## Requirements and installation
## Quick Start
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.
Requires **Java 21**, **Maven 3.8+**, and QQQ **4.0.0**. Create a repository with **Use this template**, customize its Maven coordinates and Java package/classes, then run `mvn clean verify`. Follow the [Getting Started guide](docs/00-getting-started.md) for the full sequence.
```java
new EdiQBitProducer()
.withQBitConfig(new EdiQBitConfig()
.withBackendName("rdbms")
.withSchedulerName("scheduler") // optional
.withSendAsEdiTable("sample_order") // host-defined table, optional manual action
.withServiceSessionSupplier(() -> makeEdiServiceSession()))
.produce(qInstance);
## Structure
```
src/main/java/com/kingsrook/qbits/example/
├── ExampleAppQBitConfig.java
├── ExampleAppQBitProducer.java
├── model/
│ ├── ExampleEntity.java
│ └── ExampleChildEntity.java
├── processes/
│ ├── ExampleProcessStep.java
│ └── ExampleProcessMetaDataProducer.java
└── widgets/
├── ExampleDashboardWidgetRenderer.java
└── ExampleDashboardWidgetMetaDataProducer.java
```
Call `EdiRuntime.current().initialize()` in the host's authorized startup context to install
baseline guides and data-driven schedules. After defining batch/poll settings, call
`OutboundProcesses.installSchedules()` before starting the host scheduler; rebuild/restart scheduling
after changing those settings. Define partners, identities, active agreements, inbound agreement documents
and active maps as normal QQQ records. `makeEdiServiceSession()` is a host function returning
a least-privilege `QSession` with target and association permissions. Applying without a supplier
produces an APPLY exception: `no service session configured`.
## Key Characteristics
The `edi` app has Activity/Setup sections, typed home widgets, record screens and standard
Review/Result actions. Main tables have RC.5 search-field metadata; `EdiIntegrations.mainTables()`
also exposes the list for host search registration.
| Feature | Description |
|---------|-------------|
| QAppSection | Required - provides UI navigation |
| Tables | Multiple interconnected business tables |
| Processes | Full CRUD with business logic |
| Widgets | Dashboard components |
| Security | Permission key support |
| Integration | Hooks for other QBits |
## Tables
## Documentation
There are **26 persisted tables**, whose SQL names equal their QQQ names.
- [Getting Started](docs/00-getting-started.md)
- [Tables and Relationships](docs/01-tables-and-relationships.md)
- [Processes](docs/02-processes.md)
- [Widgets and UI](docs/03-widgets-and-ui.md)
| Area | Tables |
| --- | --- |
| Setup | `edi_partner`, `edi_identity`, `edi_agreement`, `edi_agreement_document`, `edi_control_number`, `edi_transport`, `edi_certificate` |
| Guides | `edi_guide`, `edi_guide_segment`, `edi_guide_element`, `edi_guide_rule`, `edi_code_list`, `edi_code_list_value` |
| Maps | `edi_map`, `edi_map_rule`, `edi_map_sample` |
| Processing | `edi_inbox`, `edi_interchange`, `edi_functional_group`, `edi_transaction`, `edi_transaction_error`, `edi_exception`, `edi_event` |
| Storage/queue | `edi_payload`, `edi_outbound_request`, `edi_outbound_message` |
## See Also
`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.5 contingent filters, including shared
transports/guides and local certificates/identities.
## Inbound operation
`InboundSink.receive(InboundPayload)` stores encrypted bytes and SHA-256 before returning a durable
inbox record. Transport/external ID retries are idempotent; matching bytes within the 365-day intake
window create DUPLICATE rows. `EdiFileGatewayConsumer` uses the marked internal stand-in contract,
with upload ID plus digest as external ID, and returns `edi_inbox:<id>` without parsing.
Storage failure requests retry in one minute.
`InboxWorker.runOnce()` processes up to 20 messages with compare-and-set claims, fenced database
checkpoints and five-minute stale-claim recovery. Retry delays are 1m, 5m, 15m, 1h and 6h; the next
failure becomes DEAD_LETTER. Each mapped accepted ST applies in its own transaction through QQQ
actions with explicit permission enforcement. ISA15=T applies only with `applyTestDocuments`.
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.
Reprocess restarts the containing inbox from encrypted storage, using original or current guide/map
revisions. Source-position checkpoints and target upsert keys preserve idempotency, stale findings
are replaced, and the same reply is not queued twice. A live PROCESSING claim cannot be reset.
## Maps and preview
EDI paths reuse the guide rule language. Targets support `field` and `association.field`;
wildcards such as `PO1[*]/PO107` produce child records. `matchKeyFields` controls upsert, with
`onMatch` UPDATE/SKIP/EXCEPTION. [MAPS.md](docs/MAPS.md) documents transform arguments.
Resources under `/edi/maps` include inbound 850/860 and outbound 855/856/810/846 maps.
The order, shipment/carton/item, invoice/line and inventory schemas exist **only in test sources**.
Outbound maps support nested association paths; inbound target paths retain the one-association limit.
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.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.
## Permissions
Full table permissions include `edi.partner.read|insert|edit|delete`, `edi.map.*`,
`edi.transaction.read` and `edi.exception.read|edit`. Multiword names use dots
(e.g. `edi.map.rule.read`). Runtime tables disable people-facing mutations.
| Action | Permission |
| --- | --- |
| Reprocess | `edi.reprocess` |
| Preview Map | `edi.map.preview` |
| Assign / Ignore / Mark Resolved | `edi.exception.assign` / `edi.exception.ignore` / `edi.exception.resolve` |
| Create Partner Override / Activate Guide | `edi.guide.override` / `edi.guide.activate` |
| Activate Map | `edi.map.activate` |
| Send As EDI | `edi.send` |
| Resend / Resend As New | `edi.resend` |
| Cancel | `edi.cancel` |
| Test Connection | `edi.transport.test` |
| 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. Processes are table/record actions, never navigation entries.
## Configuration
| Setting | Default / behavior |
| --- | --- |
| `backendName` | Required host RDBMS backend |
| `appName`, `appLabel`, `parentAppName` | `edi`, `EDI`, no parent |
| `schedulerName` | Null; configure for 15-second polling |
| `serviceSessionSupplier` | Null; required for inbound application and outbound source reads/mapping |
| `withSendAsEdiTable(name)` | Opt a host table into Send As EDI |
| `partnerLocks` | `false` |
| `maxSegmentsPerTransaction` | 100000 |
| `payloadStore` | `DATABASE` or `OBJECT_STORAGE` |
| `payloadBucketAlias` | `edi` |
| `secretsResolver` | Environment-variable lookup |
| `clock` | UTC system clock |
| `esbDestinationName` | Null; optional core ESB destination |
Set `QQQ_EDI_PAYLOAD_KEY` to a base64-encoded 32-byte master key; retain the old key in
`QQQ_EDI_PAYLOAD_KEY_PREVIOUS` during rotation. AES-256-GCM envelope encryption uses random data
keys and SHA-256 verification. Both stores use the same envelope and currently buffer a payload
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.5.
Optional `qqq-esb` publishes setup/activity events after commit. Document events contain only ID
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` and `edi.certificate.expired` (RC.5 requires dotted names).
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.
## 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.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.5 `EsbTrigger` to invoke it with
`sourceTableName`, `sourceRecordId` and `eventId`; broker setup belongs to the host. Event IDs are
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.
IMMEDIATE produces one interchange per request. BATCH cuts up to 100 requests for one agreement
document at its UTC `batchSchedule` into one group/interchange. Mapping/enveloping and sending
use database row locks to fence replicas; crashes release the lock. Remote delivery is at least
once: a lost connection after remote acceptance can resend the same bytes and control numbers.
Retry waits are 1m, 5m, 15m, 1h, 3h, 6h, 12h, 24h, capped at the last delay; `maxAttempts` defaults
to 8. Permanent transport rejection is FAILED; retry exhaustion is DEAD_LETTER. Both open a
TRANSPORT exception and raise `edi.send.failed`.
| Transport | Behavior and settings |
| --- | --- |
| GATEWAY | `gatewayEndpointName`; marked `FileGatewayOutbox` stand-in with a provider registration hook. Put is SENT; `EdiFileGatewayConsumer.onDownloaded` is DELIVERED. |
| SFTP | MINA client from core's filesystem module; required `sftpHostKeyFingerprint` in MINA SHA256 format, username and password/private-key env name. Streams upload to `.tmp`, then rename. `sftpPollCron` schedules remote pickup; `sftpFilePattern` is a Java regex (default `.*\.x12`). DELETE/MOVE occurs only after durable inbox commit. |
| HTTPS | POST with NONE/BASIC/BEARER/HMAC; TLS required except loopback. BASIC's secret is `user:password`; BEARER's is the token; HMAC uses core Standard Webhooks `whsec_` format. `httpsHeadersJson` holds nonsecret headers only. Redirects are refused; 2xx is DELIVERED, 429/5xx retry. |
| AS2 / EMAIL | Settings validation only. Sending returns a permanent unavailable result until phase c / the email API. |
Optional Javalin registers `POST /qqq/hooks/edi/https/{transport}`. `httpsInboundAuth` must be
BASIC, BEARER or HMAC with `httpsSecretEnvVar`. It authenticates exact bytes, caps intake at
50 MiB and returns 202 only after durable storage. HMAC verifies timestamps and deduplicates by
transport plus webhook ID. TLS terminates at the host ingress.
The transport registry is `EdiTransportPluginsMetaData`, attached to `QInstance`. Hosts can
register/replace implementations with `withPlugin`. All transports open encrypted PayloadStore
streams; production code never writes local files.
Ack deadlines start on the first successful send. `edi_ack_monitor` runs every five minutes,
raises overdue alerts and exceptions, and checks certificate expiry at 30/14/7 days and expiry.
A functional ack resolves its alert and exception; TA1 updates interchange acknowledgement only.
Resend retains archived bytes/control numbers. Resend As New re-envelopes the archived document
without re-running its map and links `originalInterchangeId`. Cancel accepts QUEUED/RETRY_WAIT only.
Existing a2 databases need `edi_interchange.original_interchange_id INTEGER` and a unique index
on `edi_outbound_request(agreement_document_id, event_id)` in addition to their prior schema.
## Build and verification
```bash
./mvnw -B verify
```
Use the wrapper (Maven 3.9.11); Maven 3.10 cannot read the parent POM. Verification runs Checkstyle,
synthetic H2 tests and JaCoCo (70% instructions, 90% classes). Object-storage key/envelope metadata
are unit-tested. Live S3, a deployed gateway/scheduler and browser layout are not exercised.
The public object-storage facade is final with restricted constructors; tests avoid provider internals.
## License
Apache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
- [qbit-template-extension](https://github.com/QRun-IO/qbit-template-extension) - For infrastructure extensions
- [qbit-template-data](https://github.com/QRun-IO/qbit-template-data) - For reference data providers
-4
View File
@@ -1,4 +0,0 @@
# Security Policy
* See Project [Security Policy](https://github.com/QRun-IO/qqq/blob/develop/SECURITY.md)
* Report a vulnerability privately through this repository's **Security** tab ("Report a vulnerability").
-246
View File
@@ -1,246 +0,0 @@
<?xml version="1.0"?>
<!--
~ QQQ - Low-code Application Framework for Engineers.
~ Copyright (C) 2021-2022. 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.
-->
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
<!--
See reference at https://checkstyle.sourceforge.io/checks.html
-->
<module name = "Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<!--
<module name="LineLength">
<property name="max" value="100"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
-->
<module name="TreeWalker">
<module name="SuppressWarningsHolder"/>
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format" value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message" value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="AvoidStarImport"/>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="LeftCurly">
<property name="option" value="nl"/>
</module>
<module name="RightCurly">
<property name="option" value="alone"/>
</module>
<!--
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
-->
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[_a-zA-Z]([a-z0-9A-Z][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern" value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-zA-Z]([a-z0-9A-Z][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern" value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="CatchParameterName">
<property name="format" value="^[a-zA-Z]([a-z0-9A-Z][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern" value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-zA-Z]([a-z0-9A-Z][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern" value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][_A-Z0-9]*$)"/>
<message key="name.invalidPattern" value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][_A-Z0-9]*$)"/>
<message key="name.invalidPattern" value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="InterfaceTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][_A-Z0-9]*$)"/>
<message key="name.invalidPattern" value="Interface type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="GenericWhitespace">
<message key="ws.followed" value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded" value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow" value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded" value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="Indentation">
<property name="basicOffset" value="3"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="3"/>
<property name="throwsIndent" value="6"/>
<property name="lineWrappingIndentation" value="3"/>
<property name="arrayInitIndent" value="6"/>
</module>
<!--
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
</module>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
</module>
-->
<module name="CustomImportOrder">
<property name="customImportOrderRules" value="SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE###STATIC"/>
<property name="specialImportsRegExp" value="^javax\."/>
<property name="standardPackageRegExp" value="^java\."/>
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="false"/>
</module>
<module name="MethodParamPad"/>
<module name="NoWhitespaceBefore">
<property name="tokens" value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/>
<property name="allowLineBreaks" value="true"/>
</module>
<module name="ParenPad"/>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="JavadocMethod">
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowedAnnotations" value="Override, Test"/>
</module>
<module name="MissingJavadocMethod">
<property name="scope" value="private"/>
</module>
<module name="MissingJavadocType">
<property name="scope" value="private"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="CommentsIndentation"/>
<!-- this would be nice, but requires the the @inheritDoc javadoc tag... -->
<module name="MissingOverride"/>
</module>
<module name="Header">
<property name="headerFile" value="checkstyle/license.txt"/>
<property name="fileExtensions" value="java"/>
<property name="ignoreLines" value="3"/>
</module>
<module name="SuppressWarningsFilter"/>
</module>
-19
View File
@@ -1,19 +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.
*/
+30
View File
@@ -0,0 +1,30 @@
# Getting Started
Targets Java 21, Maven 3.8 or later, and QQQ 4.0.0. Maven resolves the framework dependencies from Maven Central.
1. Choose **Use this template** on GitHub, then clone the repository you created.
2. In `pom.xml`, set your own `groupId`, `artifactId`, name, description, and version.
3. Use your IDE's package refactoring to rename `com.kingsrook.qbits.example` throughout `src/`. Rename the `Example*` classes and update their imports and references with the IDE's rename refactoring.
4. Update the QBit producer's `GROUP_ID`, `ARTIFACT_ID`, and `VERSION` constants to match your project. Give the metadata names their own stable names before combining the QBit with other examples.
5. Build the generated project:
```bash
mvn clean verify
```
The repository provides example Java sources; customization uses normal package/class refactoring. Keep example coordinates unpublished. After customizing, add tests for your QBit's behavior before setting up its publishing workflow.
## Register with a host application
The following uses the original example names; substitute the names chosen above:
```java
new ExampleAppQBitProducer()
.withConfig(new ExampleAppQBitConfig()
.withBackendName("rdbms"))
.produce(qInstance, "my-application");
```
Register the configured backend in the host first. If you add table prefixes or multiple QBit instances, update related table references, possible-value-source names, and process/widget names consistently. A table prefix alone does not namespace all metadata.
[Tables and Relationships](01-tables-and-relationships.md) describes the model; the `processes/` and `widgets/` directories contain the remaining examples.
+50
View File
@@ -0,0 +1,50 @@
# Tables and Relationships
Application QBits typically have multiple interconnected tables representing a business domain.
## Entity Design
Use `@QMetaDataProducingEntity` for automatic metadata generation:
```java
@QMetaDataProducingEntity(producePossibleValueSource = true)
public class Order extends QRecordEntity
{
public static final String TABLE_NAME = "order";
@QField(isPrimaryKey = true)
private Integer id;
@QField(isRequired = true, possibleValueSourceName = "customer")
private Integer customerId;
@QField(isRequired = true)
private String status;
}
```
## Relationships
Define foreign keys using `possibleValueSourceName`:
```java
@QField(isRequired = true, possibleValueSourceName = "exampleEntity")
private Integer exampleEntityId;
```
## Table Prefixing
Application QBits support optional table prefixing:
```java
// Without prefix: order, orderLine
// With prefix "sales": sales_order, sales_orderLine
new MyAppQBitProducer()
.withConfig(new MyAppQBitConfig()
.withBackendName("rdbms")
.withTableNamePrefix("sales"))
.produce(qInstance, "sales-app");
```
The producer applies the prefix to table names. When extending this pattern, also update possible-value-source names, relationship targets, process tables, and navigation references; table renaming alone does not provide complete multi-instance support.
+7
View File
@@ -0,0 +1,7 @@
# Processes
Start with [ExampleProcessMetaDataProducer](../src/main/java/com/kingsrook/qbits/example/processes/ExampleProcessMetaDataProducer.java) and [ExampleProcessStep](../src/main/java/com/kingsrook/qbits/example/processes/ExampleProcessStep.java). The producer defines the process, its table, inputs, and backend step; the step transforms `PENDING` records to `PROCESSED` and returns them in the output.
In QQQ 4.0, import `MetaDataProducerInterface` from `com.kingsrook.qqq.backend.core.model.metadata`. Define process inputs through `withInputMetaData(new QFunctionInputMetaData().withField(...))`. An `AbstractTransformStep` must implement both `runOnePage(RunBackendStepInput, RunBackendStepOutput)` and `getProcessSummary(RunBackendStepOutput, boolean)`; keep the example's implementations when renaming it.
To make the process discoverable in navigation, add its registered name to a `QAppSection` through `withProcesses(List.of(ExampleProcessMetaDataProducer.NAME))`, then include the section in a `QAppMetaData` registered with `qInstance.addApp(...)`. Use the actual registered name if you introduce a prefix. Add tests for record changes and failure cases when replacing the example business logic.
+23
View File
@@ -0,0 +1,23 @@
# Widgets and UI
The application producer registers a `QAppMetaData` containing a `QAppSection`. The section references metadata names already registered in the instance; it is not registered directly on `QInstance`.
```java
QAppSection section = new QAppSection()
.withName("exampleSection")
.withLabel("Example Application")
.withTables(List.of(ExampleEntity.TABLE_NAME, ExampleChildEntity.TABLE_NAME))
.withProcesses(List.of(ExampleProcessMetaDataProducer.NAME))
.withWidgets(List.of(ExampleDashboardWidgetMetaDataProducer.NAME));
qInstance.addApp(new QAppMetaData()
.withName("exampleApp")
.withLabel("Example Application")
.withSection(section));
```
Use [ExampleAppQBitProducer](../src/main/java/com/kingsrook/qbits/example/ExampleAppQBitProducer.java) as the starting point. Keep these references consistent when enabling/disabling tables or introducing prefixes.
The widget has two parts: [its metadata producer](../src/main/java/com/kingsrook/qbits/example/widgets/ExampleDashboardWidgetMetaDataProducer.java) declares a statistics widget and its renderer, and [its renderer](../src/main/java/com/kingsrook/qbits/example/widgets/ExampleDashboardWidgetRenderer.java) returns a `RenderWidgetOutput` containing `StatisticsData`. Replace the example count with a query or calculation. Choose a widget type that matches the returned data model.
For QQQ 4.0, `AbstractWidgetRenderer` is in `com.kingsrook.qqq.backend.core.actions.dashboard.widgets`; `RenderWidgetInput` and `RenderWidgetOutput` are in `com.kingsrook.qqq.backend.core.model.actions.widgets`.
-39
View File
@@ -1,39 +0,0 @@
# EDI local demo
From this worktree with Java 21:
```powershell
./mvnw.cmd -B test-compile exec:java '-Dexec.classpathScope=test' '-Dexec.mainClass=com.kingsrook.qbits.edi.demo.EdiDemoServer' '-Dexec.args=8099'
```
Open http://localhost:8099/app/edi. Stop with Ctrl+C. On Unix use `./mvnw`.
Next UI table routes are `/app/<tableName>` and record routes are `/app/<tableName>/<id>`.
Each launch creates a fresh in-memory H2 database, an in-memory synthetic payload key and a
fully anonymous demo session. No external credentials or services are required. Demo auth is
in test sources only and must not be installed in a real host.
The seed includes Northwind Retail (gateway), Harbor Logistics (SFTP) and Summit Market (HTTPS),
with codes NORTHWIND, HARBOR and SUMMIT and synthetic contacts at `example.invalid`.
It includes identities/agreements, guides/maps, orders with lines, shipments with cartons/items,
invoices and inventory. Forty synthetic inbound documents go through intake and the real inbox worker:
850s, 860s, validation errors, an unknown sender and a byte duplicate. Northwind's 855/856/810/846
pass the outbound worker and an in-memory gateway; incoming 997s reconcile some sent groups.
An overdue group, an exhausted send and several open exceptions provide operational examples.
Two public self-signed certificates are generated in memory; private keys are never persisted.
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 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.
Acceptance boots a random-port QApplicationJavalinServer, loads Next UI, renders and serializes
every registered widget through core RenderWidgetAction/JsonUtils, and POSTs every home and
child-list widget over HTTP using seeded parent records. It also checks configuration JSON,
metadata, table queries, labels, field order, foreign-key value sources and PHI-safe record labels.
Next UI defaults table queries to descending ID (newest records first); activity child lists use
descending creation date and ID. The home exception queue puts OPEN before IN_PROGRESS, newest
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.
-916
View File
@@ -1,916 +0,0 @@
# QBit EDI: design
Status: **Approved** (owner, 2026-10-13: spec first, then build) · drafted 2026-10-13 · Author:
James Maes (drafted with Claude)
`QRun-IO/qbit-edi` (Apache-2.0) is a full EDI application qbit for QQQ. It handles the transports
(AS2, SFTP, VAN mailboxes, HTTPS, email), carries the EDI documents and their implementation guides
as data, runs inbound and outbound flows with 997, 999 and TA1 acknowledgements, and lets each
trading partner's customizations be expressed as versioned data rather than code.
## Owner request (verbatim)
> For EDI, i guess that would be more of a normal application qbit that would just handle all of
> the transport methods (AS2, etc) and then have all of the edi documents built in, with handling
> for edi flows, 997s etc and handling customizations of them, etc.
## Owner decisions
> **Owner decisions (2026-10-12): all recommendations accepted.** Every open question in section 17
> is decided as this spec recommends ("they are approved. we will adjust as needed once we can view
> them working").
**Owner decisions (2026-10-13):**
- Spec first, then build, in the phases of section 16.
- **First documents (X12):**
- retail and supply chain: 850, 855, 856, 810, 846, 860;
- warehouse and logistics: 940, 945, 214, 204;
- healthcare (HIPAA): 837 (P and I), 835, 270/271, 276/277, 834;
- acknowledgements: 997, 999, TA1.
- **Versions:** X12 004010 and 005010, with the 005010 HIPAA TR3s for healthcare. EDIFACT is
designed in now and built last.
- **No files or filesystem: just the database and S3** ("there are no files or filesystem - just
the database and s3"). Raw EDI documents, AS2 messages and MDNs, and certificates live in object
storage and the database, never on local disk. Private keys come from secrets (environment
variables). Every service is stateless, so any replica can process any message.
- **Inbound SFTP** comes through qbit-file-gateway (SFTP only in v1, no FTPS, owner decision),
using its published `api` package (section 5.3).
- **No kof22 adoption.** kof22 does not need EDI. The demo runs in `qqq-sample-project` (section
15).
## Mandatory standards that shape this design
- Data lives in standard, visible QQQ tables with full record screens, secured by permissions and
record locks. Tables are `edi_*`; permissions use the full name `edi.*`.
- Processes are table or record actions with plain labels, never in the nav. Record actions use the
standard streamed-ETL Review and Result summary lines; no custom dry-run forms.
- One app home that owns the EDI data, built from typed widgets (no `RawHTML`, guardrail G5). The
nav lists only the main tables; sub-tables are child lists on their parents.
- The API matches the admin. Main tables publish ESB table events and are searchable in Quick
Search.
- UI endpoints are spec'd `MiddlewareSpecExtension`s under `/qqq/v1/qbit/edi/...` with a committed
fragment and a contract test (G3). Machine endpoints (AS2, HTTPS receive, MDNs) are non-UI routes
under `/qqq/hooks/edi/...`.
- No dependency on another qbit's internals (G1). EDI depends only on the published `api` packages
of qbit-object-storage and qbit-file-gateway, both optional. Alerts and notifications go through
core `QAlerts`.
- No stand-in without `STAND-IN(core=..., removeBy=...)` (G4). Pinned core baseline, no SNAPSHOT on
`develop` (G7).
- Readable at all widths, light and dark. Real data only on the home.
## 1. Goals and non-goals
**Goals**
1. Exchange X12 with any trading partner over AS2, SFTP, a VAN mailbox, HTTPS or email, configured
as data per partner.
2. Never lose a document: store the raw bytes first (encrypted), then process from a durable inbox
with retries and a dead letter. Every step is idempotent and replayable.
3. Validate against implementation guides that are data (segments, elements, usage, conditional
rules, code lists), per standard and version, with versioned per-partner overrides.
4. Acknowledge automatically (TA1, 997, 999), and track the acknowledgements we expect from
partners, alerting when they are overdue.
5. Map documents to and from QQQ records with versioned, data-driven maps, and apply inbound
documents through normal QQQ actions as a service account, so table permissions, validation,
customizers, audit and ESB events all apply.
6. Make operations visible: a document timeline, search by business keys (PO, ASN, invoice and
claim numbers), an exception work queue, resend and reprocess, and a home showing volumes,
overdue acks and errors.
7. Carry PHI safely: classify healthcare content as PHI so the secured-data tiers and the canary
egress tests apply, and encrypt raw documents at rest.
**Non-goals (v1)**
- A VAN service of our own. We connect to VANs as mailboxes.
- Business-level responses beyond acknowledgements (824 Application Advice, 864 text). The model
leaves room for them (section 9.6).
- Shipping licensed X12 or TR3 text (section 7.4).
- CAQH CORE real-time 270/271 (SOAP and MIME envelope). Batch 270/271 is in scope; CORE
connectivity is a later transport plugin (section 5.6).
## 2. Architecture
```
partner ──AS2──► /qqq/hooks/edi/as2/{transport} ─┐
partner ──SFTP─► qbit-file-gateway INBOX ("edi") ─┤
VAN mailbox ◄─poll── SFTP poller (scheduled) ─┤ 1. store raw bytes (object storage, encrypted)
partner ──HTTPS► /qqq/hooks/edi/https/{transport} ─┤ 2. insert edi_inbox row (PENDING), return
email (qbit-email inbound, later) ─┘ (MDN / 200 / FileAcceptance.accepted)
│
edi_inbox worker (scheduled, claims rows, retries, dead letter)
▼
split interchanges ─► envelope check, TA1 ─► partner + agreement lookup ─► duplicate check
─► per ST: guide validation (StAEDI + guide rules) ─► 997/999 ─► map ─► apply as service account
─► statuses, timeline, exceptions (edi_exception) ─► ack reconciliation for inbound 997/999/TA1
host record change ─► ESB table event / record-change listener ─► edi_outbound_request (same tx)
─► outbound worker: map ─► guide validate ─► envelope (control numbers) ─► store raw
─► edi_outbound_message ─► transport send (AS2 / SFTP push / gateway OUTBOX / HTTPS / email)
─► MDN or delivery ─► expected ack due date ─► 997/999 reconcile or overdue alert
```
Package layout (`com.kingsrook.qbits.edi`):
| Package | Holds |
|---|---|
| (root) | `EdiQBitProducer`, `EdiQBitConfig`, `EdiRuntime` (the configured services) |
| `metadata` | the only scanned package: tables, PVS, joins, widgets, processes, app |
| `model` | table entities (`EdiPartner`, `EdiAgreement`, ...) |
| `x12` | StAEDI reading and writing, envelopes, separators, control numbers, splitting |
| `guide` | guide model, structural validator, rule engine, SNIP levels, built-in guide loader |
| `ack` | 997, 999 and TA1 builders, inbound ack reconciliation, overdue monitor |
| `map` | map model, path language, transforms, inbound apply, outbound build, sandbox preview |
| `flow` | inbox and outbound workers, state machines, reprocess and resend |
| `transport` | `EdiTransportPlugin` SPI and the plugins: `as2`, `sftp`, `gateway`, `https`, `email` |
| `storage` | `PayloadStore` (object storage with envelope encryption; database fallback) |
| `security` | secrets by environment-variable name, certificates, classification, service session |
| `hooks` | the `/qqq/hooks/edi/...` route provider |
| `uiapi` | spec'd `/qqq/v1/qbit/edi/...` extension (phase f) |
| `edifact` | EDIFACT envelopes and CONTRL (phase g) |
## 3. Platform and dependencies
| Dependency | Scope | Why |
|---|---|---|
| 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. |
| `io.xlate:staedi` 1.26.x (Apache-2.0) | compile | streaming X12 and EDIFACT reader and writer with schema validation |
| `com.helger.as2:as2-lib` 5.1.x (Apache-2.0) | compile | AS2 send, receive, MDN, S/MIME via BouncyCastle |
| `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 that arrive in RC.6, and how EDI bridges until then:
| Need | Core | Until RC.6 |
|---|---|---|
| `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,
removeBy=qbit-file-gateway 0.1.0-RC.1)`.
## 4. Trading partners and agreements
### 4.1 Tables
All tables have `id`, `createDate`, `modifyDate` and, where people edit them, `modifiedBy`; audit is
on (field-level for configuration tables); record screens have sections. SQL table names equal QQQ
table names; fields are camelCase in QQQ and snake_case in SQL. Labels in brackets.
**`edi_partner`** [Trading Partner] (main, nav, Quick Search, ESB events)
- `name` (unique), `code` (short slug, unique), `status` (TESTING, ACTIVE, SUSPENDED, RETIRED),
`contactName`, `contactEmail`, `contactPhone`, `notes`.
- `defaultTimezone` (for date transforms), `containsPhi` (boolean; set for healthcare partners,
drives classification defaults and the home's PHI badge).
- Children: identities, agreements, transports, certificates, documents, exceptions.
**`edi_identity`** [EDI Identity] (child of partner; "ours" when `partnerId` is null)
- `partnerId` (null = one of our own identities), `standard` (X12, EDIFACT),
`interchangeQualifier` (ISA05/07, e.g. `ZZ`, `01`, `12`; EDIFACT UNB 0007),
`interchangeId` (ISA06/08, padded to 15 on write; UNB 0004/0010), `applicationCode` (GS02/03),
`description`, `isActive`.
- Unique (standard, interchangeQualifier, interchangeId, applicationCode). Inbound routing matches
on these.
**`edi_agreement`** [Agreement] (main, nav; child list on partner)
One agreement is one (our identity, partner identity, standard, usage) combination.
- `partnerId`, `localIdentityId`, `partnerIdentityId`, `standard` (X12, EDIFACT), `usageIndicator`
(T test, P production; ISA15). Test and production are separate agreements, so test traffic never
touches production records unless the agreement says so.
- `interchangeVersion` (ISA12: `00401`, `00501`), `elementSeparator` (default `*`),
`segmentTerminator` (default `~`), `componentSeparator` (ISA16, default `:`),
`repetitionSeparator` (ISA11 for 00501, default `^`), `lineEnding` (NONE, LF, CRLF; outbound).
Inbound separators are always read from the ISA itself; these settings are for outbound.
- `requestTa1` (ISA14), `sendTa1` (NEVER, ON_REQUEST, ON_ERROR, ALWAYS; default: on request and on
error).
- `inboundTransportId`, `outboundTransportId` (section 5).
- `controlNumberScope` (AGREEMENT, LOCAL_IDENTITY; default AGREEMENT; section 4.3).
- `duplicateWindowDays` (default 365), `onDuplicate` (REJECT_TA1, IGNORE, PROCESS; default
REJECT_TA1 with an exception row).
- `maxInterchangeBytes` (default 50 MB), `applyTestDocuments` (boolean, default false: T documents
are validated and acknowledged but not applied).
- `isActive`, `notes`.
**`edi_agreement_document`** [Agreement Document] (child of agreement)
One row per document, version and direction that the agreement carries.
- `agreementId`, `direction` (INBOUND, OUTBOUND), `transactionSet` (`850`, `837`, ...),
`functionalIdCode` (GS01: PO, PR, SH, IN, IB, PC, OW, SW, QM, SM, HC, HP, HS, HB, HR, HN, BE, FA),
`version` (GS08: `004010`, `005010`, `005010X222A1`, ...), `guideId` (a base or override guide),
`mapId`, `ackExpectation` (NONE, 997, 999), `ackDueMinutes` (default 1440), `ackLevel`
(GROUP_ONLY, ALL_TRANSACTIONS, ERRORS_ONLY; for the acks we send), `snipLevel` (1-7; section 11;
default 2 for non-HIPAA, 4 for HIPAA), `containsPhi` (defaulted from the version),
`targetTableName` (inbound) or `sourceTableName` (outbound), `outboundTrigger` and
`triggerFilter` (section 9.3), `batchMode` (IMMEDIATE, BATCH; outbound), `batchSchedule` (cron,
when BATCH), `isActive`.
- Unique (agreementId, direction, transactionSet, version).
**`edi_control_number`** [Control Number] (child of agreement)
- `agreementId` (or `localIdentityId` when the scope is LOCAL_IDENTITY), `level` (ISA, GS),
`lastValue`, `minValue` (1), `maxValue` (999999999), `wrapAllowed` (default true).
- ST02 is assigned per group (0001 upward), so it needs no row.
### 4.2 Inbound routing
1. Read the ISA: 106 fixed characters; the element separator is character 4, the repetition
separator ISA11 (00501), the component separator ISA16 and the segment terminator character 106.
2. Find the partner identity by (ISA05, ISA06) and our identity by (ISA07, ISA08), then the agreement
by both plus ISA15. No match: an `UNKNOWN_PARTNER` exception, and TA1 code `006` or `007` when
we can address a reply. The raw document is always kept.
3. Per GS, match GS02/GS03 when application codes are configured; per ST, find the agreement
document by direction INBOUND, ST01 and GS08 (exact, then by the base version `005010`). No
match: the group is rejected in the 997/999 (AK5/IK5 `R`, "transaction set not supported") and a
`NOT_CONFIGURED` exception is opened.
### 4.3 Control numbers
- Allocation is an atomic compare-and-set on `edi_control_number` (`UPDATE ... SET last_value = n+1
WHERE id = ? AND last_value = n`, retried) in its own short transaction, so concurrent replicas
never hand out the same number. Past `maxValue` it restarts at `minValue` when wrapping is
allowed; otherwise the outbound fails with a plain error.
- Numbers are allocated when an interchange is enveloped, not when it is sent, so a resend of the
same interchange keeps its numbers and the partner can detect the duplicate. "Resend As New"
re-envelopes with new numbers (section 9.5).
- Inbound duplicate detection per agreement, within `duplicateWindowDays`:
- interchange: the same (sender, receiver, ISA13, ISA15). TA1 note code `025`.
- group: the same GS06 from the same sender. AK9/IK9 `R`.
- transaction: the same ST02 within the group. AK5/IK5 `R`.
- Byte-identical payloads (the same sha256 within the window) are caught earlier, at the inbox,
and marked `DUPLICATE` without processing.
### 4.4 Acknowledgement expectations and overdue alerts
- Each outbound functional group whose agreement document expects a 997 or 999 gets `ackDueAt`
(sent time + `ackDueMinutes`).
- The overdue monitor (a scheduled process, every 5 minutes) calls `QAlerts.raise(AlertSignal)`
with alert type `edi.ack.overdue`, severity WARNING, dedup key `edi_functional_group:<id>`, a
record reference to the group and a link to the interchange. qbit-alerts delivers it (inbox,
channels, quiet hours); without qbit-alerts, core's logging sink records it.
- 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.
- From RC.6, assignees of exceptions also get a `QAlerts.notify` notification.
## 5. Transports
### 5.1 Model
Transports are data. **`edi_transport`** [Transport] (main, nav):
- `name` (unique slug, used in hook URLs), `type`, `direction` (INBOUND, OUTBOUND, BOTH),
`partnerId` (null for a shared transport such as a VAN), `isActive`, `notes`.
- Typed settings in sections. Only the section for `type` is shown and validated (a pre-insert and
pre-update customizer calls the plugin's `validate`):
- **AS2:** `as2LocalId`, `as2PartnerId`, `as2Url`, `as2Subject`, `signAlgorithm` (SHA256, SHA384,
SHA512; SHA1 only when the partner requires it, flagged), `encryptAlgorithm` (AES128_CBC,
AES256_CBC, AES128_GCM, AES256_GCM; 3DES only when required, flagged), `compress`, `mdnMode`
(SYNC, ASYNC, NONE), `mdnSigned`, `localCertificateId`, `partnerCertificateId`,
`partnerTlsCertificateId` (optional pinning), `httpAuthUserEnvVar`, `httpAuthPasswordEnvVar`.
- **SFTP (push and poll):** `sftpHost`, `sftpPort`, `sftpUsername`, `sftpPasswordEnvVar` or
`sftpPrivateKeyEnvVar`, `sftpHostKeyFingerprint` (required; unknown host keys are refused),
`sftpOutboundPath`, `sftpInboundPath`, `sftpPollCron`, `sftpAfterPickup` (DELETE, MOVE),
`sftpArchivePath`, `sftpFilePattern`, `sftpTempSuffix` (upload as `.tmp`, then rename).
- **File gateway:** `gatewayEndpointName` (the `file_gateway_alias.name`: an INBOX endpoint for
inbound, an OUTBOX endpoint for partner-pull outbound).
- **HTTPS:** `httpsUrl`, `httpsAuthType` (NONE, BASIC, BEARER, HMAC, MTLS), `httpsSecretEnvVar`,
`httpsHeadersJson` (non-secret headers), `httpsInboundAuth` (for `/qqq/hooks/edi/https/{name}`:
BASIC, BEARER or HMAC, the secret by env var).
- **Email:** `emailProviderName` (qbit-email), `emailTo`, `emailFromAlias`,
`emailAttachmentName`.
- **VAN:** an AS2 or SFTP transport with `isVan` true and `vanName`. One VAN transport serves many
partners (`partnerId` null); routing is by ISA identities, never by transport.
- Retry policy: `maxAttempts` (default 8), backoff 1m, 5m, 15m, 1h, 3h, 6h, 12h, 24h.
**`edi_certificate`** [Certificate] (main, nav)
- `alias`, `partnerId` (null = ours), `usage` (SIGNING, ENCRYPTION, SIGNING_AND_ENCRYPTION, TLS),
`certificatePem` (the public certificate only), `subject`, `issuer`, `serialNumber`,
`fingerprintSha256`, `notBefore`, `notAfter` (all parsed from the PEM on save),
`privateKeyEnvVar` (ours only: the env var holding a PEM or base64 PKCS#12),
`privateKeyPasswordEnvVar`, `status` (ACTIVE, NEXT, RETIRED). `NEXT` supports rollover: inbound
accepts either key, outbound switches when "Promote Certificate" runs.
- Expiry: the monitor raises `edi.certificate.expiring` at 30, 14 and 7 days (dedup per
certificate and threshold), and `edi.certificate.expired` at expiry.
- Record actions: "Promote Certificate", "Export Public Certificate" (download the PEM to send to a
partner). Private keys never enter a table, a log or a response.
### 5.2 Plugin SPI
```java
public interface EdiTransportPlugin
{
String getType(); // "AS2", "SFTP", "GATEWAY", "HTTPS", "EMAIL"
void validate(EdiTransport transport, List<String> errors); // settings check on save
EdiSendResult send(EdiOutboundMessage message, PayloadRef payload, EdiTransport transport) throws EdiTransportException;
default void poll(EdiTransport transport, InboundSink sink) throws EdiTransportException { } // pull transports
default EdiTestResult test(EdiTransport transport) { ... } // the "Test Connection" record action
}
```
- Plugins register in `EdiTransportPluginsMetaData` (a `QSupplementalInstanceMetaData`). The
producer registers the built-in ones; a host can add its own (e.g. a VAN's proprietary API).
- `EdiSendResult`: SENT (with the transport message id), DELIVERED (sync MDN, or HTTP 2xx where the
transport confirms delivery), RETRY (with a reason), FAILED (permanent, with a reason).
- `InboundSink.receive(InboundPayload)` stores the bytes and inserts the inbox row (section 9.1).
Every inbound path, push or pull, ends in this one call.
- **Secrets:** settings name environment variables (`...EnvVar`). `EdiSecrets.resolve(name)` reads
them at use time; values are never stored, logged, cached beyond the call, or returned by any
API. Tests replace the resolver. A missing variable fails "Test Connection" and the send with
"environment variable X is not set" (the name only).
### 5.3 SFTP through qbit-file-gateway (inbound) and partner pull (outbound)
Settled with the qbit-file-gateway agent (its DESIGN.md section "Consumer and outbox API"):
- EDI registers `EdiFileGatewayConsumer` (name `edi`) with
`FileGatewayConsumersMetaData.ofOrWithNew(qInstance).withConsumer(new QCodeReference(...))`. An
INBOX endpoint (a `file_gateway_alias` row with `consumer_name = edi`) delivers each uploaded
file, at least once, after upload close and scan.
- `accept(InboundFile)` runs on the gateway worker as the SFTP account's user (channel `sftp`).
`InboundFile` carries `uploadJobId`, `endpointName`, `username`, `userId`, `remoteAddress`,
`path`, `fileName`, `sizeBytes`, `sha256`, `receivedAt`, `storageBucketAlias`, `storageKey` and
`openStream()` (always from object storage). EDI streams the bytes into its own encrypted payload
store, inserts an `edi_inbox` row keyed by (`GATEWAY`, `uploadJobId`) with the sha256, and returns
`FileAcceptance.accepted("edi_inbox:<id>")`; a storage failure returns `retryLater(reason,
duration)`. It never parses inside `accept()`.
- The inbox row records the endpoint, username, user id, file name and remote address. The partner
is still determined from the ISA. When the SFTP account is linked to a partner and the ISA sender
is a different partner, processing stops with a `PARTNER_MISMATCH` exception, so one partner
cannot send as another.
- Outbound to partners that pull from us: `FileGatewayOutbox.put(endpointName, fileName, stream,
sizeBytes)` writes to the partner's OUTBOX endpoint and returns an `OutboxFileRef`;
`onDownloaded(OutboxFileRef)` on our consumer marks the outbound message DELIVERED.
`FileGatewayOutbox.isAvailable()` false makes a GATEWAY transport fail validation with
"qbit-file-gateway is not installed".
### 5.4 SFTP push and VAN polling (remote servers)
For partners and VANs that host their own SFTP, EDI connects out with core's SFTP client
(`qqq-backend-module-filesystem`):
- **Push:** write `sftpOutboundPath/<fileName>.tmp`, then rename. File names follow a configurable
pattern, default `<partnerCode>_<transactionSet>_<isa13>_<yyyyMMddHHmmss>.edi`.
- **Poll:** a scheduled process per transport lists `sftpInboundPath` matching `sftpFilePattern`,
streams each file straight into `InboundSink` (no local file), and deletes or moves it only after
the inbox row commits. A crash between commit and delete re-reads the file on the next poll; the
inbox's sha256 dedupe absorbs it.
- qbit-sftp-data-integration stays the tool for generic file imports and report exports. EDI does
not depend on it (G1) but uses the same core client, so behaviour and host-key handling match.
### 5.5 AS2 (phase c)
Built on as2-lib (Apache-2.0):
- **Send:** `AS2Client` with partnership settings built from `edi_transport` and `edi_certificate`
(a custom partnership factory and certificate factory backed by our tables, keys from env vars, no
keystore file). Sign, then encrypt, optional compression; the `Message-ID` is recorded. Sync MDN:
verify the signature and the MIC against the sent content; a mismatch or a failed disposition
fails the send. Async MDN: `Receipt-Delivery-Option` is
`https://<host>/qqq/hooks/edi/as2/{transport}/mdn`; the message stays SENT until the MDN arrives,
then becomes DELIVERED or FAILED. An MDN not received within `ackDueMinutes` raises
`edi_mdn_overdue`.
- **Receive:** `POST /qqq/hooks/edi/as2/{transport}` (a route provider outside `/qqq/v1`). The
handler reads the body up to the agreement's `maxInterchangeBytes` (in memory up to 8 MB, then
streamed to object storage, never to a temp file), uses as2-lib's receive helpers to decrypt,
verify the signature against the partner's certificate, decompress and compute the MIC, then calls
`InboundSink.receive` and returns the signed sync MDN, or sends the async MDN after commit. A
message that cannot be stored gets a failure MDN (`processed/error:
unexpected-processing-error`) so the partner retries.
- The AS2 `Message-ID` is the inbox dedupe key: a retransmitted message gets the original MDN again
and is not reprocessed.
- MDNs, sent and received, are stored in **`edi_as2_mdn`** [AS2 MDN] (child of the outbound message
or the inbox row): `direction`, `messageId`, `originalMessageId`, `disposition`, `micAlgorithm`,
`micValue`, `signed`, `signatureValid`, `payloadRef` (the raw MDN in object storage),
`receivedAt`.
- TLS: outbound uses the JVM trust store plus optional pinning (`partnerTlsCertificateId`). Inbound
TLS terminates at the host's ingress.
### 5.6 HTTPS and email
- **HTTPS out:** POST the interchange (`application/edi-x12` or `application/EDIFACT`) with the
configured auth. 2xx is DELIVERED. Responses are not parsed as acks (acks arrive as documents).
- **HTTPS in:** `POST /qqq/hooks/edi/https/{transport}` with BASIC, BEARER or HMAC (Standard
Webhooks signatures, verified with core's webhook signing utilities), a body size limit, then
`InboundSink.receive`.
- **CAQH CORE** (real-time 270/271 and 276/277 over SOAP or MIME) is a later plugin that reuses the
same parser and map engine with a synchronous reply. Not in v1.
- **Email out:** qbit-email's `EmailService.send` with the interchange as an attachment, category
TRANSACTIONAL, linked record `edi_outbound_message`. Healthcare documents may only go through a
provider marked `phi_allowed` and are refused otherwise. EDI calls qbit-email through its
published API only; until that API ships, this transport is disabled.
- **Email in:** through qbit-email's phase 2 inbound (an `InboundEmailHandler` registered by EDI).
Designed here, built when qbit-email inbound ships.
## 6. Parsing (X12 now, EDIFACT later)
- 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.
- Envelope checks: ISA fixed width, IEA01 = group count, IEA02 = ISA13, GE01 = transaction count,
GE02 = GS06, SE01 = segment count, SE02 = ST02. Envelope errors produce TA1 or AK9/IK9 codes.
- Character set: the X12 basic and extended character sets, and UTF-8 where the agreement allows it
(005010 extended). Unrecognised bytes are an envelope error, never silently replaced.
- Writing uses an in-memory serializer with the agreement's separators, fixed-width ISA
padding, component/repetition handling, and optional line endings.
- EDIFACT: StAEDI reads UNA/UNB/UNG/UNH and validates against EDIFACT schemas. It is designed into
the model now (`standard` columns, UNB fields on `edi_identity`, CONTRL as an ack type) and built
in phase g.
## 7. Implementation guides as data
### 7.1 Tables
**`edi_guide`** [Implementation Guide] (main, nav)
- `name`, `standard`, `version` (`004010`, `005010`, `005010X222A1`, ...), `transactionSet`,
`functionalIdCode`, `partnerId` (null = a base guide), `parentGuideId` (an override's base),
`revision` (increments on activation), `status` (DRAFT, ACTIVE, RETIRED), `source` (BUILT_IN,
IMPORTED, CUSTOM), `description`, `compiledChecksum`.
- BUILT_IN rows are read-only (a record lock keyed on `source`; only the loader writes them).
**`edi_guide_segment`** [Guide Segment] (child of guide): `guideId`, `sequence`, `loopId` (e.g.
`2000A`, `N1`, `HL`), `parentLoopId`, `loopMaxRepeat`, `segmentId`, `position` (the X12 position,
e.g. `0100`), `usage` (REQUIRED, SITUATIONAL, NOT_USED), `minUse`, `maxUse`, `name`,
`situationalRuleId`, `notes`.
**`edi_guide_element`** [Guide Element] (child of guide segment): `guideSegmentId`, `position` (3
for BEG03), `componentPosition` (null or 1..n), `elementId` (the data element number), `name`,
`dataType` (AN, ID, N0-N9, R, DT, TM, B), `minLength`, `maxLength`, `usage`, `codeListId`,
`fixedValue`, `repeatMax`, `situationalRuleId`.
**`edi_guide_rule`** [Guide Rule] (child of guide)
- X12 syntax notes per segment: `type` PAIRED (P), REQUIRED (R), EXCLUSION (E), CONDITIONAL (C) or
LIST_CONDITIONAL (L), with the element positions.
- Situational and inter-segment rules: `type` SITUATIONAL with an `expression` in the rule language
(section 7.3), `snipLevel` (2, 4, 6 or 7), `severity` (ERROR, WARNING), `message`, and the
997/999 error codes to report.
**`edi_code_list`** [Code List] (main, nav) and **`edi_code_list_value`** [Code] (child): `name`,
`elementId`, `source` (BUILT_IN, CUSTOM, EXTERNAL), `externalCodeSet` (for licensed code sets,
section 11.4); values have `code`, `description`, `isActive`.
### 7.2 Guide validation
StAEDI tokenizes the stream and checks envelope syntax. The guide engine applies the guide data
to each buffered transaction: segment order, nested loops, usage and repetition limits, element
types and lengths, code lists, syntax notes, and situational and inter-segment expressions. Both
JSON resources and database guide revisions use this same in-memory engine; guide validation does
not generate a second StAEDI schema or write files.
- Every finding becomes an `edi_transaction_error` row: segment position, segment id, element
position, the X12 code for the ack (AK3/AK4 for 997, IK3/IK4/CTX for 999), the SNIP level and a
plain message. Element values go in `valueExcerpt` only for non-PHI documents (section 12).
### 7.3 Rule language
A deliberately small expression language over the buffered transaction set, evaluated without a
scripting engine:
- paths as in maps (section 8.2): `CLM02`, `2300/CLM05-1`, `HL[HL03=22]/NM1[NM101=IL]/NM109`;
- functions: `present(p)`, `absent(p)`, `count(p)`, `sum(p)`, `eq(a, b)`, `in(p, 'A', 'B')`,
`matches(p, regex)`, `date(p)`, `and(...)`, `or(...)`, `not(x)`, `when(condition, then)`.
- Example: "if the patient is not the subscriber, the 2000C loop is required" is
`when(eq(2000B/SBR02, '18'), absent(2000C))` together with its converse.
### 7.4 Built-in guides and licensing
X12 standards and the HIPAA TR3s are copyrighted and licensed by ASC X12. A public Apache-2.0 repo
must not ship their text. So:
- **Built-in guides are authored in-house** for the documents in scope: segment and element
structure, usage, lengths and the code values the maps use, with our own short names and no copied
notes, descriptions or complete code lists. They are enough to parse, validate structurally,
acknowledge and map typical traffic, and are labelled "QQQ baseline guide".
- **Imported guides:** the table action "Import Guide" on `edi_guide` loads a guide that a licensee
exports from their licensed tools (a documented JSON format, plus an adapter for a common X12
schema JSON). Imported guides live in the host's database, never in this repo.
- Partner companion-guide differences are **override guides** (section 7.5).
- Built-in guides ship as JSON resources in the jar and are upserted by the loader at startup when
their checksum changes (as a new revision; the previous revision stays for documents processed
under it).
### 7.5 Partner overrides
- "Create Partner Override" (record action on a guide) copies the guide into a CUSTOM guide with
`partnerId` and `parentGuideId`, in DRAFT.
- Edits are ordinary record edits of its segments, elements, rules and code lists.
- "Activate Guide" requires that the guide compiles, increments `revision`, and retires the previous
ACTIVE revision for that partner. Documents record the guide id and revision they were validated
with, so reprocessing an old document can use the original or the current revision (a choice on
the reprocess action).
- "Compare With Base" lists the differences as standard Result lines (segments, elements and rules
added, removed or changed).
## 8. Maps
### 8.1 Tables
**`edi_map`** [Map] (main, nav)
- `name`, `direction` (INBOUND, OUTBOUND), `standard`, `version`, `transactionSet`, `guideId`,
`partnerId` (null = shared), `tableName` (the target or source QQQ table), `matchKeyFields`
(comma list; the inbound upsert key, e.g. `partnerId,poNumber`), `onMatch` (UPDATE, SKIP,
EXCEPTION), `businessKeyPath`, `businessKeyType`, `secondaryKeyPath`, `revision`, `status`
(DRAFT, ACTIVE, RETIRED), `parentMapId`, `description`.
**`edi_map_rule`** [Map Rule] (child of map): `sequence`, `ediPath`, `qqqPath` (`field`,
`association.field`), `transform`, `transformArgs`, `required`, `condition` (rule language),
`defaultValue`, `notes`.
**`edi_map_sample`** [Map Sample] (child of map): `name`, `payloadRef` (stored like any payload,
encrypted), `expectedResultJson`, `lastPreviewAt`, `lastPreviewStatus`. Synthetic data only; the
record screen says so, and samples on PHI maps are classified PHI.
### 8.2 Paths and transforms
- **EDI paths:** segment plus element position (`BEG03`), components with `-` (`CLM05-1`), loops by
id (`2300/CLM01`) or by qualifier predicate (`N1[N101=ST]/N3/N301`), repeats with an index or `*`
(`PO1[*]/PO102`). Inbound, `*` loops map to associations; outbound, associations drive loops.
- **QQQ paths:** fields and associations of the map's table, checked against the table's metadata
on activation.
- **Transforms:** `DATE` (CCYYMMDD; YYMMDD with a century window), `TIME` (HHMM, HHMMSS and
fractions), `DATETIME` (date + time + the partner's timezone), `DECIMAL` (implied decimals for N
types), `QUALIFIER` (pick by a sibling qualifier), `CODE` (translate through a code list or a
map-local table), `UNIT` (unit-of-measure conversion, EA, CA, PK, LB, KG, ..., with pack sizes
from a host field), `CONSTANT`, `CONCAT`, `SPLIT`, `SUBSTRING`, `TRIM`, `UPPER`, `LOOKUP` (find a
QQQ record id by a key, such as an item by UPC or a ship-to by N104, read as the service session),
`SEQUENCE` (outbound line numbers), `HL` (outbound HL numbering for 856 hierarchies). Hosts can
register their own transforms by name.
- A rule failure is a mapping error naming the rule, the EDI path and the QQQ path (with no value
for PHI documents).
### 8.3 Sandbox (phase a as an action, phase f as UI)
"Preview Map" (record action on `edi_map` and `edi_map_sample`): pick a sample or upload one (the
upload is stored as a sample), and the standard Review/Result lines show:
1. the parse: interchanges, groups and transaction sets found;
2. validation findings by SNIP level;
3. the 997/999 that would be sent;
4. the map result (records and associations that would be written), downloadable as JSON;
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 f adds spec'd endpoints (`POST /qqq/v1/qbit/edi/sandbox/preview` and related) for the mapper
UI.
### 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` (CRUD on `edi_map_rule` plus the extension endpoints).
## 9. Flows
### 9.1 Inbox and inbound processing
**`edi_inbox`** [Inbound Message] (child list on the transport and the interchange; linked from the
home):
- `transportId`, `transportType`, `externalId` (AS2 Message-ID, gateway `uploadJobId`, poll path and
modification time, HTTPS request id, email message id), `payloadRef`, `payloadSha256`,
`sizeBytes`, `fileName`, `remoteAddress`, `receivedAt`, `status` (PENDING, PROCESSING, PROCESSED,
RETRY_WAIT, DEAD_LETTER, DUPLICATE, IGNORED), `attempts`, `nextAttemptAt`, `claimedAt`,
`processedAt`, `lastError` (no payload content), `partnerId` (once known).
- Unique (`transportType`, `externalId`); sha256 duplicates within the window become DUPLICATE.
- The worker (a scheduled process, every 15 seconds, batches of 20) claims rows with a
compare-and-set on `status` and `claimedAt`. Claims older than 5 minutes are reclaimed, so any
replica can process and a crashed one loses nothing. Backoff 1m, 5m, 15m, 1h, 6h; then
DEAD_LETTER, an `edi_inbox_dead_letter` alert and an exception.
Per interchange, group and transaction set (each transaction set in its own database transaction,
so one bad document does not block the others):
1. envelope checks and TA1 (section 6);
2. partner and agreement routing (4.2), duplicate checks (4.3);
3. validation (7.2) at the agreement document's SNIP level;
4. ack building: one 997 or 999 per received group, sent per the agreement (9.4);
5. if the transaction set is accepted (`A` or `E`): map and apply (9.2); a rejected set is not
applied;
6. statuses, timeline events and exceptions.
### 9.2 Apply as a service account
- The applier runs `InsertAction` and `UpdateAction` (with associations) on the map's table under a
**service session** from `EdiQBitConfig.withServiceSessionSupplier(Supplier<QSession>)`, which the
host provides (a user with exactly the permissions EDI should have). Permission enforcement stays
on, so a map cannot write to a table the service account cannot. When partner locks are on,
that partner's security key is added to the session for the call.
- Upsert by `matchKeyFields`: query for an existing record as the service session; `onMatch`
decides. The 860 (PO change) is a map onto the same order table with UPDATE.
- The target table's customizers, validation, audit and ESB events all run, because this is the
normal action path.
- The target record id is stored on `edi_transaction` (`targetTableName`, `targetRecordId`). A host
can show a record's EDI documents with a child-list widget on `edi_transaction` filtered by
target.
- A failure rolls back that transaction set only, sets status EXCEPTION, and opens an `APPLY`
exception with the error.
### 9.3 Outbound
- **Trigger** (per outbound agreement document, `outboundTrigger`):
- `ESB_EVENT` (preferred when qqq-esb is configured): the source table publishes ESB table
events; an `EsbTrigger` subscribed to them runs the process `edi_outbound_enqueue`, which
applies `triggerFilter` (a QQueryFilter as JSON, e.g. `status = SHIPPED`) and inserts an
**`edi_outbound_request`** row (`agreementDocumentId`, `sourceTableName`, `sourceRecordId`,
`eventId`, `status`, `attempts`, `nextAttemptAt`, `claimedAt`, `lastError`). It is idempotent on
(event id, agreement document).
- `RECORD_CHANGE` (no broker): a core `RecordChangeListenerInterface` registered by EDI inserts the
same row inside the writer's transaction (a transactional outbox: the request exists exactly
when the source change commits).
- `MANUAL`: the table action "Send As EDI" on the source table, added when the host opts in, with
the standard Review/Result summary.
- **Outbound worker** (claims requests like the inbox): loads the source record as the service
session, maps it (section 8), and validates it against the guide (an invalid outbound document is
never sent; it opens a `VALIDATION` exception). It then batches per agreement (IMMEDIATE or on
`batchSchedule`), envelopes (control numbers, 4.3), stores the raw interchange and creates an
**`edi_outbound_message`**.
- **`edi_outbound_message`** [Outbound Message]: `interchangeId`, `transportId`, `payloadRef`,
`status` (QUEUED, SENDING, SENT, DELIVERED, RETRY_WAIT, FAILED, DEAD_LETTER, CANCELLED),
`attempts`, `nextAttemptAt`, `transportMessageId`, `sentAt`, `deliveredAt`, `lastError`. The
sender claims rows the same way and retries per the transport's policy; DEAD_LETTER raises
`edi.send.failed` and opens a `TRANSPORT` exception.
- **Reconcile:** an MDN or delivery confirmation sets DELIVERED; the expected 997/999 sets the
functional group's `ackStatus` (4.4); the home and the timeline show both.
### 9.4 Acknowledgements we send
- **TA1:** per the agreement's `sendTa1`, with codes from the envelope checks (e.g. `001` control
number mismatch, `006`/`007` invalid sender or receiver, `022` invalid control structure, `025`
duplicate).
- **997** (non-HIPAA, and 004010) and **999** (005010 HIPAA, `005010X231A1`): AK1, AK2, AK3, AK4,
AK5 and AK9, or IK3, IK4, IK5 and CTX, at the agreement document's `ackLevel`, built from the
validation findings.
- Acks are outbound interchanges on the same agreement and transport, with their own control
numbers, and appear as `edi_transaction` rows (997, 999, TA1) linked to what they acknowledge.
- Acks are never themselves acknowledged.
### 9.5 Resend, reprocess, search and timeline
Record actions, all with the standard Review/Result summary:
- **Reprocess** (on `edi_inbox`, `edi_interchange`, `edi_transaction`, `edi_exception`): run again
from the stored raw payload, from the parse or from the map step, with the original or the current
guide and map revisions. Idempotent: an applied document is matched by its target record and
updated or skipped per the map's `onMatch`. Acks are re-sent only when chosen.
- **Resend** (on `edi_outbound_message`, `edi_interchange`): send the same bytes again (the same
control numbers). **Resend As New**: re-envelope with new control numbers (for partners that
rejected a duplicate).
- **Cancel** (queued outbound), **Assign**, **Ignore** and **Mark Resolved** (exceptions).
- **Search by business keys:** `edi_transaction.businessKey` (PO, ASN, invoice, load and shipment
numbers, from the map's `businessKeyPath`, e.g. BEG03, BSN02, BIG02, B204, W0506),
`businessKeyType`, and `secondaryKey` (e.g. the PO on an 856 or 810). Healthcare keys (claim and
patient control numbers, member ids) go to `phiBusinessKey` instead (section 12). All are in Quick
Search; the PHI field matches only for users with FULL access to it (secured-data).
- **Timeline:** `edi_event` [Event] (child of interchange and transaction): `at`, `type` (RECEIVED,
STORED, PARSED, VALIDATED, ACK_SENT, MAPPED, APPLIED, ENVELOPED, SENT, MDN, DELIVERED,
ACK_RECEIVED, EXCEPTION, REPROCESSED, RESENT), `detail` (no payload values), `userName`. The
interchange and transaction screens show it in time order next to the record audit.
### 9.6 Runtime tables
**`edi_interchange`** [Interchange] (main, nav, Quick Search on ISA13, ESB events): `direction`,
`partnerId`, `agreementId`, `transportId`, `inboxId` or `outboundMessageId`, `standard`,
`senderQualifier`, `senderId`, `receiverQualifier`, `receiverId`, `controlNumber` (ISA13),
`interchangeVersion`, `usageIndicator`, `interchangeDate`, `groupCount`, `payloadRef`,
`payloadSha256`, `sizeBytes`, `status` (RECEIVED, ACCEPTED, ACCEPTED_WITH_ERRORS, REJECTED,
DUPLICATE, ENVELOPED, SENT, DELIVERED, FAILED), `ta1Status`, `containsPhi`.
**`edi_functional_group`** [Functional Group] (child of interchange): `functionalIdCode`,
`controlNumber` (GS06), `version` (GS08), `transactionCount`, `ackStatus` (A, E, P, R, NONE,
PENDING), `ackDueAt`, `ackReceivedAt`, `ackTransactionId`.
**`edi_transaction`** [Document] (main, nav, Quick Search, ESB events): `interchangeId`,
`functionalGroupId`, `direction`, `partnerId`, `agreementDocumentId`, `transactionSet`, `version`,
`controlNumber` (ST02), `businessKeyType`, `businessKey`, `secondaryKey`, `phiBusinessKey`,
`status` (RECEIVED, VALID, INVALID, MAPPED, APPLIED, EXCEPTION, QUEUED, SENT, ACKNOWLEDGED,
REJECTED, IGNORED), `ackStatus`, `errorCount`, `warningCount`, `guideId`, `guideRevision`, `mapId`,
`mapRevision`, `targetTableName`, `targetRecordId`, `segmentCount`, `containsPhi`.
**`edi_transaction_error`** [Validation Error] (child of transaction): `segmentPosition`,
`segmentId`, `loopId`, `elementPosition`, `componentPosition`, `snipLevel`, `ackCode`, `severity`,
`message`, `valueExcerpt` (non-PHI documents only).
**`edi_exception`** [Exception] (main, nav; the work queue): `type` (UNKNOWN_PARTNER,
PARTNER_MISMATCH, NOT_CONFIGURED, DUPLICATE, ENVELOPE, VALIDATION, MAPPING, APPLY, TRANSPORT,
REJECTED_ACK, OVERDUE_ACK, DEAD_LETTER), `status` (OPEN, IN_PROGRESS, RESOLVED, IGNORED),
`partnerId`, `inboxId`, `interchangeId`, `transactionId`, `outboundMessageId`, `summary`, `detail`
(no payload values), `assignedToUserId`, `resolvedBy`, `resolvedAt`, `resolution`, `occurrences`
(repeated failures of the same item increment it instead of adding rows).
The 824 Application Advice and the 864 are later document types on the same model (outbound
documents generated from an exception), not new tables.
## 10. Payload storage (database and S3 only)
- **`PayloadStore`** writes raw payloads (inbound files, outbound interchanges, AS2 messages and
MDNs, samples) to qbit-object-storage: the bucket alias from
`EdiQBitConfig.withPayloadBucketAlias` (default `edi`), key
`edi/<yyyy>/<MM>/<dd>/<kind>/<uuid>`, through `ObjectStorage.asSystem("edi", runId)`.
- **Envelope encryption, always:** each payload is encrypted with AES-256-GCM under a random data
key; the data key is wrapped with the master key from env var `QQQ_EDI_PAYLOAD_KEY` (base64, 32
bytes) and stored with its key id in the object's metadata. `QQQ_EDI_PAYLOAD_KEY_PREVIOUS` allows
rotation (read with either, write with the current one); the table action "Rewrap Payloads"
re-wraps old payloads. Bucket-side SSE is recommended as well, not relied on.
- `payloadRef` columns hold `objectstorage:<bucket>:<key>`; the plaintext sha256 is stored next to
it.
- **Database fallback:** without qbit-object-storage, payloads go to **`edi_payload`** (`kind`,
`ciphertext` BLOB, `keyId`, `sha256`, `sizeBytes`, `createDate`), encrypted the same way. It suits
the demo and tests; production should use object storage (startup logs a WARN, and the home
shows it).
- Nothing touches local disk: parsing streams from the store, AS2 uses memory and object storage,
and certificates come from table rows and env vars. A test asserts that a full round trip creates
no file under the JVM temp directory.
- **Retention** (`EdiQBitConfig.withRetention...`; the nightly table action "Purge Payloads"): raw
payloads are kept 7 years for healthcare (HIPAA documentation retention is 6 years) and 2 years
otherwise, configurable. Rows and their sha256 stay after the payload is purged. The home shows the
policy.
## 11. Validation levels (SNIP) and code sets
### 11.1 What each level checks and what we support
| SNIP | Meaning | Support |
|---|---|---|
| 1 | EDI syntax integrity: envelopes, segment and element syntax, separators | **Full** (StAEDI and the envelope checks) |
| 2 | HIPAA syntax requirements: TR3 usage (R/S/N), repeats, lengths, TR3 code values | **Full** for the rules in the active guide (the built-in baseline or an imported TR3) |
| 3 | Balancing: totals across segments (837 CLM02 equals the sum of the SV1/SV2 line charges; 835 BPR02 equals the sum of CLP04 less PLB adjustments; claim and line adjustments balance) | **Full** for 837P, 837I and 835 (coded checks) |
| 4 | Situational inter-segment rules ("if X then Y") | **Full** through guide rules (7.3); baseline guides carry the common ones, imported TR3s the rest |
| 5 | External code sets: ICD-10, CPT, HCPCS, NDC, CARC/RARC, taxonomy, ZIP | **Partial:** a pluggable `ExternalCodeSetProvider` (11.4); no licensed code set ships (CPT is AMA-licensed). Format checks (ICD-10 shape, NPI check digit, ZIP format) are built in. |
| 6 | Product type or line of business (ambulance, chiropractic, ...) | **Through override guides and rules**; no built-in content |
| 7 | Trading-partner-specific requirements (companion guides) | **Through partner override guides** (7.5) |
### 11.2 Findings and acks
SNIP 1-2 errors reject the transaction set (AK5/IK5 `R`) unless the agreement document downgrades a
rule to WARNING. SNIP 3-7 findings default to `E` (accepted with errors) plus an exception, because
business rejection belongs in an 824 or a 277CA, not in a 999. Each rule can set its severity.
### 11.3 Defaults
Non-HIPAA documents validate at level 2 by default and HIPAA documents at level 4. An agreement
document can raise or lower its level.
### 11.4 External code sets
`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
so.
## 12. HIPAA and PHI
- **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
table column).
- PII: partner contact fields. CONFIDENTIAL: everything else in the `edi_` tables.
- Host target tables classify their own fields. The map writes through normal actions, so their
classification applies to what it writes.
- With qbit-secured-data installed, PHI fields follow its tiers: a customer-service user sees
`phiBusinessKey` masked and can find a claim by exact match only (REVEALABLE), which is how claim
lookup is meant to work for non-FULL users. Without it, the fields are FULL to anyone with the
table permission, so a host carrying PHI should install it (the home warns).
- **No PHI outside the encrypted payloads and PHI-classified fields:** for documents with
`containsPhi`, errors, events, exceptions, alert texts, log lines and Result lines carry segment
and element positions and codes, never values. The 999 we send carries positions and element
references only.
- **Encryption at rest:** payloads always (section 10). PHI columns rely on the database's
encryption at rest (a host responsibility, stated in the README) plus secured-data masking.
- **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): 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
App `edi` [EDI], optionally under the host's parent app. Nav (main tables only):
- **Activity:** Documents (`edi_transaction`), Interchanges, Exceptions.
- **Setup:** Trading Partners, Agreements, Transports, Certificates, Implementation Guides, Code
Lists, Maps.
Sub-tables (identities, agreement documents, control numbers, guide segments, elements and rules,
code list values, map rules and samples, functional groups, validation errors, events, inbox,
outbound requests and messages, MDNs) are child lists on their parents.
**Home** (typed widgets only, no HTML; every figure links to the filtered table):
1. Statistics: documents in and out today and over 7 days, open exceptions, overdue acks, dead
letters, certificates expiring within 30 days.
2. Table: volume by partner and document type over 7 days (in, out, rejected), each cell linking to
`edi_transaction` filtered.
3. Table: overdue acknowledgements (partner, group, sent, due, age).
4. Table: recent errors (validation and transport, last 24 hours).
5. Child-record list: the exception queue (OPEN and IN_PROGRESS, oldest first), with Assign and
Reprocess actions.
6. Statistics: system state (payload store: object storage or the database fallback; secured-data
present; external code-set provider present; inbox backlog and the oldest pending age).
All counts run as the viewing user, so permissions and partner record locks apply. Real data only.
## 14. Security and permissions
- Table permissions use the full name: `edi.partner.read|insert|edit|delete`, and likewise for each
table (`edi.agreement.*`, `edi.transport.*`, `edi.certificate.*`, `edi.guide.*`, `edi.map.*`,
`edi.interchange.read`, `edi.transaction.read`, `edi.exception.read|edit`, ...). Runtime tables
are read-only to people (no insert or delete permission is offered; the qbit writes them as the
system).
- Process permissions: `edi.reprocess`, `edi.resend`, `edi.exception.assign`, `edi.guide.activate`,
`edi.guide.import`, `edi.map.activate`, `edi.map.preview`, `edi.certificate.promote`,
`edi.transport.test`, `edi.raw.download`, `edi.payload.purge`, `edi.admin`.
- **Partner record locks** (optional, `EdiQBitConfig.withPartnerLocks(true)`): every partner-scoped
table carries a READ_AND_WRITE lock on `partnerId` with the security key type `edi_partner` and the
all-access key `edi.admin`, so a host can limit operators to some partners. The service session
gets the needed partner key per call.
- Hooks authenticate per transport (AS2 signature and certificate, HTTPS auth), not with user
sessions; they run as the system only to store and enqueue.
- Client channels: the producer registers its own denies (architecture review F17): the assistant
channel is denied `edi_payload`, `edi_certificate` and `edi_inbox`, and the PHI fields follow
secured-data.
## 15. Demo
Phase b includes a test-scope `EdiDemoServer` with the Next UI and synthetic H2 seed;
see [DEMO.md](DEMO.md) for launch and acceptance details. The broader host demo below remains
a deployment example.
No kof22 adoption. The demo is a module in `qqq-sample-project` (in QRun-IO/qqq): a small orders,
shipments and invoices schema; a retailer partner over the file gateway or SFTP and a 3PL over AS2
against a local as2-lib peer; seeded synthetic documents; and a healthcare clearinghouse partner with
synthetic 837/835 for the PHI walkthrough. It runs on H2 with the database payload fallback, or with
MinIO for object storage.
## 16. Phases and acceptance
| Phase | Scope | Acceptance |
|---|---|---|
| a | Partners, identities, agreements, agreement documents, control numbers, transports (model), payload store, inbox and worker, X12 parse and envelopes, guide model, compiler and rule engine, built-in guides for 850 (004010, 005010) and the acks, 997/999/TA1 generation, inbound SFTP through the file gateway (stand-in until its api ships), exceptions, events, home v1, Preview Map | An 850 from a gateway upload becomes an applied record and a 997; TA1 and 997 for envelope and syntax errors; duplicates caught at every level; control numbers unique under 8 concurrent allocators; every table has a record screen; home counts are real; `./mvnw verify` green |
| b | Retail 850, 855, 856, 810, 846, 860 (004010, 005010) guides and maps; outbound (ESB and record-change triggers, batching, envelope, send); SFTP push and VAN polling; gateway OUTBOX; HTTPS in and out; inbound ack reconciliation; overdue-ack alerts; resend, reprocess, cancel | 855/856/810 built from host records, validated, enveloped and sent; a 997 reconciles and resolves the overdue alert, and a missing one raises it; resend keeps control numbers, resend-as-new does not |
| c | AS2 send and receive, sync and async MDN, certificates and rollover, expiry alerts, VAN over AS2 | Round trip against a local as2-lib peer: signed and encrypted, sync and async MDN, MIC verified, a bad signature refused, a retransmit gets the original MDN |
| d | Logistics 940, 945, 214, 204 guides and maps | Round trips per document |
| e | Healthcare 837P, 837I, 835, 270/271, 276/277, 834 (005010 TR3 baselines), 999, SNIP 3-4, PHI classification, canary egress tests; the email transport when qbit-email's API ships | 837 batches validated to SNIP 4 with balancing errors reported; correct 999s; canary test green |
| f | Sandbox endpoints and the mapper UI (UI API extension and a Next page) | Fragment committed and contract-tested; the mapper edits a map and previews a sample |
| g | EDIFACT (UNA/UNB/UNH, ORDERS, ORDRSP, DESADV, INVOIC, CONTRL) | EDIFACT round trips and CONTRL |
Each phase is one or more PRs into `develop`, with tests and, for UI-visible changes, screenshots of
the home and key records in light and dark at 390 and 1440 px.
### 16.1 Tests (all phases)
- Synthetic sample documents per type and version from a seeded generator; no real data.
- Round trips: parse, map and apply, then build the outbound document from the applied record and
compare.
- 997, 999 and TA1 generation against expected acks for crafted errors at every level.
- AS2 against a local as2-lib peer, in-process, in both directions.
- Control-number concurrency, wrap and duplicate detection.
- Partner overrides change validation outcomes as intended; map revisions and the sandbox diff.
- No local files during a full round trip; no secret values in logs or responses.
- Canary PHI egress (phase e).
## 17. Open questions (decided as recommended, 2026-10-12)
| # | Question | Recommendation (accepted) |
|---|---|---|
| Q1 | Ship full X12 or TR3 guide content? | No. In-house baseline guides plus an "Import Guide" action for licensed content (7.4). |
| Q2 | Use qbit-sftp-data-integration for SFTP push and polling? | No code dependency (G1); use core's SFTP client directly, the same one it uses (5.4). |
| Q3 | Where do raw documents live without object storage? | Encrypted in `edi_payload` in the database, for the demo and tests; production uses object storage (10). |
| Q4 | Outbound trigger: ESB or a record-change listener? | Both: the ESB trigger when a broker is configured, otherwise the in-transaction record-change listener; MANUAL for one-offs (9.3). |
| Q5 | Which user applies inbound documents? | A host-supplied service session with enforcement on (9.2). |
| Q6 | Test (ISA15 = T) documents? | Separate agreements; validated and acknowledged, applied only with `applyTestDocuments` (4.1). |
| Q7 | Business rejections (824, 277CA)? | Later document types on the same model; v1 uses exceptions plus `E` acks (11.2). |
| Q8 | Are PHI business keys searchable? | Yes, in a PHI-classified field, by exact match for REVEALABLE users through secured-data (12). |
| Q9 | Payload retention? | 7 years for healthcare, 2 years otherwise, configurable (10). |
| Q10 | SHA-1 and 3DES for AS2? | Only when a partner requires them, flagged on the transport and the home (5.1). |
| Q11 | Partner record locks? | Optional (`withPartnerLocks`), off by default (14). |
| Q12 | Demo host? | `qqq-sample-project`, not kof22 (15). |
## 18. Owner steps (outside code)
- Protect `develop` and make `test_only` required (the coordinator).
- For production use: set `QQQ_EDI_PAYLOAD_KEY`, an object-storage bucket alias `edi`, the service
user for the applier, AS2 keys and passwords as environment variables, and load licensed TR3
guides or code sets if healthcare validation beyond the baseline is needed.
## Phase a2 implementation notes (2026-10-13)
The inbound pipeline, map/apply service boundary, typed app/home, actions, gateway stand-in and
queued acknowledgements are implemented. These RC.5 adaptations refine the executable contract:
- Core alert metadata rejects underscore names: use `edi.inbox.dead-letter`, `edi.ack.overdue`
and `edi.ack.rejected`.
- Queued replies emit `ACK_QUEUED`; actual sending remains phase b.
- `inboundPosition` on interchange/group/transaction distinguishes repeated controls within
a payload and makes retries resume the same occurrence.
- Reprocessing a descendant restarts its containing inbox using original/current definitions,
stable checkpoints and idempotent reply queuing; live claims cannot be reset.
- Targets and associations must share the EDI RDBMS backend for atomic ST application.
Cross-backend application fails as APPLY.
- RC.5 binds UTC LocalDateTime. DATE_TIME DDL uses zone-less UTC TIMESTAMP to prevent
host-timezone shifts in leases and due times. Existing timestamp-with-time-zone columns
require migration preserving UTC instant values.
- Typed TABLE supports a table-level link rather than individual count-cell URLs. Volume/queue
tables use filtered-table links; STATISTICS links each figure. No HTML widgets are used.
- `edi_preview_download` is a storage-only metadata alias bridging standard streams to encrypted
PayloadStore. It has no SQL table. Hosts configure it as the Javalin upload archive and avoid
multipart disk staging. Samples are synthetic only; downloads require session-registered refs.
- Preview compares structured before/after JSON against expected output and the previous revision.
PHI map results and acknowledgement values are suppressed. Selection/upload use the standard Review.
- [MAPS.md](MAPS.md) defines transform arguments. QUALIFIER selects via path predicates; CODE uses
map-local translations; UNIT uses configured factors/pack sizes; DATETIME uses an explicit timezone.
Host code-list/pack-field conversions use registered transforms. SEQUENCE/HL remain phase b.
- Both payload adapters buffer complete envelopes in memory and use no runtime filesystem storage.
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.
The object-storage facade is final with restricted constructors: key/encryption metadata are tested
without provider internals. Live S3, deployed gateway/scheduler and light/dark browser layout remain
deployment validation. Outbound messages intentionally stay QUEUED.
## Phase b implementation notes (2026-10-13)
RC.5 adaptations implemented in this worktree:
- Record-change listeners are registered in instance metadata and receive the source writer's transaction. Successful enqueues participate in that transaction (rollback is tested). RC.5 catches listener exceptions, so a failed enqueue cannot veto a source write; hosts requiring strict delivery guarantees must use the explicit enqueue boundary in their transaction.
- RC.5 ESB consumption requires JMS. `edi_outbound_enqueue` accepts `sourceTableName`, `sourceRecordId`, and `eventId`; a host EsbTrigger can invoke that process. Tests exercise that boundary without a broker.
- The filesystem module supplies Apache MINA SSHD. SFTP uses its streaming client directly with mandatory pinned host-key verification, remote temporary upload/rename, and durable inbox receipt before remote cleanup. No runtime local files are created.
- HTTPS uses core `WebhookSigner`/`WebhookVerifier` for Standard Webhooks HMAC. The optional Javalin provider exposes the non-UI `/qqq/hooks/edi/https/{transport}` endpoint with a 50 MiB limit.
- Worker claims use database row locks within the processing transaction. A crashed worker rolls back its claim; remote-send ambiguity remains at-least-once. Batch schedules are installed from active agreement documents during runtime initialization, with up to 100 transactions from one agreement document per envelope. Schedule changes require host scheduler refresh/restart. SFTP poll schedules are installed the same way.
- Acknowledgement deadlines start on the first successful send. Resend reuses the archived bytes; Resend As New re-envelopes the archive with new ISA/GS controls and linked tracking snapshots, without remapping changed source records.
- The test-scope demo uses `QApplicationJavalinServer`, Next UI 1.0.0-RC.14, fresh in-memory H2, anonymous demo authentication, synthetic certificates generated in memory, and an in-memory gateway OUTBOX provider. See `DEMO.md`. A host scheduler and production transport credentials remain host responsibilities.
-51
View File
@@ -1,51 +0,0 @@
# EDI guide JSON
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.
The root has `name`, `standard`, `version`, `transactionSet`, `functionalIdCode`, ordered
`segments`, and optional `rules`. A segment has:
- `segmentId`, `usage` (`REQUIRED`, `SITUATIONAL`, `NOT_USED`), `minUse`, `maxUse`;
- optional `loopId`, `parentLoopId`, and `loopMaxRepeat`; the first segment starts each loop
occurrence, with parents declared before children;
- optional boolean `condition`, `elements`, and `rules`.
An element has one-based `position` and optional `componentPosition`, `elementId`, `dataType`
(`AN`, `ID`, `N0`–`N9`, `R`, `DT`, `TM`, `B`), `minLength`, `maxLength`, `usage`, `fixedValue`,
`repeatMax`, and `condition`. `codes` is an optional array of permitted ID values. Omitted/null
means no code constraint; an explicitly empty array permits no values. DT accepts YYMMDD with
a 1950–2049 century window or CCYYMMDD; TM accepts HHMM, HHMMSS, and one/two fractional digits.
N types accept integer lexical values with an implied decimal point.
Rules use `type` (`PAIRED`, `REQUIRED`, `EXCLUSION`, `CONDITIONAL`, `LIST_CONDITIONAL`, or
`SITUATIONAL`), `positions` for syntax notes, or `expression` for situational rules. Optional
`snipLevel`, `severity`, `message`, `ak3Code`, `ak4Code`, `ik3Code`, and `ik4Code` customize findings.
Syntax-note positions are one-based elements. Segment/element `condition` means “required when
this expression is true.” Explicit `when(...)` rules express prohibitions and other relationships.
Paths include `BEG03`, `REF01-2`, `N1/N101`, `N1[N101=ST]/N301`, `PO1[*]/PO102`, and
`N1[2]/N101`. Indexes start at one; unindexed paths select all occurrences. Loop scopes come from
the guide, so a qualified parent selects its child segments. Functions are `present`, `absent`,
`count`, `sum`, `eq`, `in`, `matches`, `date`, `and`, `or`, `not`, and `when`. String literals use
single quotes; numeric literals use decimal notation. `date` tests calendar validity. Examples:
```text
when(eq(N101, 'ST'), present(N3))
eq(sum(PO102), 12)
and(in(BEG01, '00', '01'), date(BEG05))
```
No script engine runs. PHI validation suppresses all excerpts and custom rule messages. Functional
acks omit submitted values; situational 999 CTX records carry only trigger positions, following
[X12's CTX interpretation](https://x12.org/resources/requests-for-interpretation/rfi-1311-999ctx-context).
At host startup, after schema creation and service-session initialization, call
`EdiRuntime.current().initialize()`. Unchanged resource checksums are no-ops. A changed resource
creates a new revision and retires the former active built-in revision in one database transaction;
historical rows and their children remain addressable by id. A reverted resource also creates a
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.
-50
View File
@@ -1,50 +0,0 @@
# EDI maps
A map comprises an `edi_map` row and ordered `edi_map_rule` rows. EDI paths resolve through the
guide's loop tree; target paths support one association level. `condition`, `defaultValue` and
`required` apply within each loop occurrence. Missing optional values preserve child alignment.
`transformArgs` is a JSON object with string values. Omitted transforms copy the first value.
| Transform | Arguments / result |
| --- | --- |
| DATE | CCYYMMDD or YYMMDD; fixed 1950–2049 century window; QQQ date |
| TIME | HHMM, HHMMSS, fractional seconds; normalized string |
| DATETIME | Date and time values, or `time` argument; `timezone` defaults UTC |
| DECIMAL | `scale` implied decimal places, default 0 |
| QUALIFIER | Sibling predicate in `ediPath`, e.g. `N1[N101=ST]/N104` |
| CODE | Map-local translation object, e.g. `{"EA":"each","CA":"case"}` |
| UNIT | `factor`, `divisor`, `scale` (defaults 1, 1, 6), HALF_UP |
| CONSTANT | `value`; needs no EDI input |
| CONCAT | `separator`, default empty; joins selected values |
| SPLIT | Literal `separator` (comma), zero-based `index` (0) |
| SUBSTRING | Zero-based `start` inclusive, `end` exclusive |
| TRIM / UPPER | Whitespace trim / locale-independent uppercase |
| LOOKUP | `table`, `key`, `result` (default id); exactly one permitted row |
| SEQUENCE | Outbound loop occurrence, starting at `start` (default 1) |
| HL | Outbound HL01 counter, HL02 nearest parent, HL03 `code`, HL04 child indicator |
Supply the partner timezone explicitly for DATETIME. UNIT uses configured conversion factors/pack
sizes; host-dependent field lookups and code-list translation can register named transforms through
`EdiRuntime.current().transforms().register(name, transform)`. Transforms execute as the service
session. LOOKUP explicitly enforces permissions.
Samples reference encrypted PayloadStore objects. Preview JSON contains `values` and recursive
`associations`; `expectedResultJson` uses the same shape. Comparisons show structured before/after
JSON as Result lines. Preview never invokes target application. See the synthetic 850 resources
under `src/main/resources/edi/maps` and [GUIDE-FORMAT.md](GUIDE-FORMAT.md).
## Outbound maps
OUTBOUND rules are evaluated in sequence order. EDI path prefixes create nested loop scopes;
the directly mapped QQQ association drives each occurrence. Nested QQQ paths such as
`cartons.items.sku` are supported and fetched recursively by the worker. Constants within a
loop can carry an association field as `qqqPath` to identify their scope. See the 856 samples
for Shipment/Order/Pack/Item loops and parent numbering.
DATE/TIME format QQQ values as X12 dates/times; DECIMAL multiplies by the implied `scale`.
Other registered transforms operate on the source value. Conditions use the existing EDI rule
language over segments already emitted. A missing required mapping input fails mapping;
structurally invalid mapped output opens VALIDATION and is never sent. ST/SE are generated by
the mapper and group-local controls are assigned by the worker. Component-target paths are
currently rejected explicitly; the bundled retail maps use simple elements.
-540
View File
@@ -1,540 +0,0 @@
CREATE TABLE edi_agreement (
apply_test_documents BOOLEAN,
component_separator VARCHAR(255),
control_number_scope VARCHAR(255),
create_date TIMESTAMP,
duplicate_window_days INTEGER,
element_separator VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inbound_transport_id INTEGER,
interchange_version VARCHAR(255),
is_active BOOLEAN,
line_ending VARCHAR(255),
local_identity_id INTEGER,
max_interchange_bytes BIGINT,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
notes VARCHAR(10000),
on_duplicate VARCHAR(255),
outbound_transport_id INTEGER,
partner_id INTEGER,
partner_identity_id INTEGER,
repetition_separator VARCHAR(255),
request_ta1 BOOLEAN,
segment_terminator VARCHAR(255),
send_ta1 VARCHAR(255),
standard VARCHAR(255),
usage_indicator VARCHAR(255),
UNIQUE (local_identity_id, partner_identity_id, standard, usage_indicator)
);
CREATE TABLE edi_agreement_document (
ack_due_minutes INTEGER,
ack_expectation VARCHAR(255),
ack_level VARCHAR(255),
agreement_id INTEGER,
batch_mode VARCHAR(255),
batch_schedule VARCHAR(255),
contains_phi BOOLEAN,
create_date TIMESTAMP,
direction VARCHAR(255),
functional_id_code VARCHAR(255),
guide_id INTEGER,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
is_active BOOLEAN,
map_id INTEGER,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
outbound_trigger VARCHAR(255),
snip_level INTEGER,
source_table_name VARCHAR(255),
target_table_name VARCHAR(255),
transaction_set VARCHAR(255),
trigger_filter VARCHAR(10000),
version VARCHAR(255),
UNIQUE (agreement_id, direction, transaction_set, version)
);
CREATE TABLE edi_certificate (
alias VARCHAR(255),
certificate_pem VARCHAR(10000),
create_date TIMESTAMP,
fingerprint_sha256 VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
issuer VARCHAR(255),
modified_by VARCHAR(255),
modify_date TIMESTAMP,
not_after TIMESTAMP,
not_before TIMESTAMP,
partner_id INTEGER,
private_key_env_var VARCHAR(255),
private_key_password_env_var VARCHAR(255),
serial_number VARCHAR(255),
status VARCHAR(20),
subject VARCHAR(255),
usage VARCHAR(255)
);
CREATE TABLE edi_code_list (
create_date TIMESTAMP,
element_id VARCHAR(255),
external_code_set VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
name VARCHAR(255),
source VARCHAR(255)
);
CREATE TABLE edi_code_list_value (
code VARCHAR(255),
code_list_id INTEGER,
create_date TIMESTAMP,
description VARCHAR(10000),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
is_active BOOLEAN,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
UNIQUE (code_list_id, code)
);
CREATE TABLE edi_control_number (
agreement_id INTEGER,
create_date TIMESTAMP,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
last_value BIGINT,
level VARCHAR(255),
local_identity_id INTEGER,
max_value BIGINT,
min_value BIGINT,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
scope_key VARCHAR(255),
wrap_allowed BOOLEAN,
UNIQUE (scope_key, level)
);
CREATE TABLE edi_event (
at TIMESTAMP,
create_date TIMESTAMP,
detail VARCHAR(10000),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inbox_id INTEGER,
interchange_id INTEGER,
modify_date TIMESTAMP,
transaction_id INTEGER,
type VARCHAR(255),
user_name VARCHAR(255)
);
CREATE TABLE edi_exception (
assigned_to_user_id VARCHAR(255),
create_date TIMESTAMP,
detail VARCHAR(10000),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inbox_id INTEGER,
interchange_id INTEGER,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
occurrences INTEGER,
outbound_message_id INTEGER,
partner_id INTEGER,
resolution VARCHAR(10000),
resolved_at TIMESTAMP,
resolved_by VARCHAR(255),
status VARCHAR(255),
summary VARCHAR(255),
transaction_id INTEGER,
type VARCHAR(255)
);
CREATE TABLE edi_functional_group (
ack_due_at TIMESTAMP,
ack_received_at TIMESTAMP,
ack_status VARCHAR(255),
ack_transaction_id INTEGER,
control_number VARCHAR(255),
create_date TIMESTAMP,
functional_id_code VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inbound_position INTEGER,
interchange_id INTEGER,
modify_date TIMESTAMP,
sender_id VARCHAR(255),
transaction_count INTEGER,
version VARCHAR(255)
);
CREATE TABLE edi_guide (
compiled_checksum VARCHAR(255),
create_date TIMESTAMP,
description VARCHAR(10000),
functional_id_code VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
name VARCHAR(255),
parent_guide_id INTEGER,
partner_id INTEGER,
revision INTEGER,
source VARCHAR(255),
standard VARCHAR(255),
status VARCHAR(255),
transaction_set VARCHAR(255),
version VARCHAR(255),
UNIQUE (name, revision)
);
CREATE TABLE edi_guide_element (
code_list_id INTEGER,
component_position INTEGER,
create_date TIMESTAMP,
data_type VARCHAR(255),
element_id VARCHAR(255),
fixed_value VARCHAR(255),
guide_segment_id INTEGER,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
max_length INTEGER,
min_length INTEGER,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
name VARCHAR(255),
position INTEGER,
repeat_max INTEGER,
situational_rule_id INTEGER,
usage VARCHAR(255)
);
CREATE TABLE edi_guide_rule (
ak3_code VARCHAR(255),
ak4_code VARCHAR(255),
create_date TIMESTAMP,
expression VARCHAR(10000),
guide_id INTEGER,
guide_segment_id INTEGER,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
ik3_code VARCHAR(255),
ik4_code VARCHAR(255),
message VARCHAR(10000),
modified_by VARCHAR(255),
modify_date TIMESTAMP,
positions VARCHAR(255),
severity VARCHAR(255),
snip_level INTEGER,
type VARCHAR(255)
);
CREATE TABLE edi_guide_segment (
create_date TIMESTAMP,
guide_id INTEGER,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
loop_id VARCHAR(255),
loop_max_repeat INTEGER,
max_use INTEGER,
min_use INTEGER,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
name VARCHAR(255),
notes VARCHAR(10000),
parent_loop_id VARCHAR(255),
position VARCHAR(255),
segment_id VARCHAR(255),
sequence INTEGER,
situational_rule_id INTEGER,
usage VARCHAR(255)
);
CREATE TABLE edi_identity (
application_code VARCHAR(255),
create_date TIMESTAMP,
description VARCHAR(10000),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
interchange_id VARCHAR(255),
interchange_qualifier VARCHAR(255),
is_active BOOLEAN,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
partner_id INTEGER,
standard VARCHAR(255),
UNIQUE (standard, interchange_qualifier, interchange_id, application_code)
);
CREATE TABLE edi_inbox (
attempts INTEGER,
claimed_at TIMESTAMP,
create_date TIMESTAMP,
external_id VARCHAR(255),
file_name VARCHAR(255),
gateway_user_id VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
last_error VARCHAR(255),
modify_date TIMESTAMP,
next_attempt_at TIMESTAMP,
partner_id INTEGER,
payload_ref VARCHAR(255),
payload_sha256 VARCHAR(255),
processed_at TIMESTAMP,
received_at TIMESTAMP,
remote_address VARCHAR(255),
reprocess BOOLEAN,
size_bytes BIGINT,
status VARCHAR(255),
transport_id INTEGER,
transport_type VARCHAR(255),
use_current_revisions BOOLEAN,
username VARCHAR(255),
UNIQUE (transport_type, external_id)
);
CREATE TABLE edi_interchange (
agreement_id INTEGER,
contains_phi BOOLEAN,
control_number VARCHAR(255),
create_date TIMESTAMP,
direction VARCHAR(255),
group_count INTEGER,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inbound_position INTEGER,
inbox_id INTEGER,
interchange_date TIMESTAMP,
interchange_version VARCHAR(255),
modify_date TIMESTAMP,
original_interchange_id INTEGER,
outbound_message_id INTEGER,
partner_id INTEGER,
payload_ref VARCHAR(255),
payload_sha256 VARCHAR(255),
receiver_id VARCHAR(255),
receiver_qualifier VARCHAR(255),
sender_id VARCHAR(255),
sender_qualifier VARCHAR(255),
size_bytes BIGINT,
standard VARCHAR(255),
status VARCHAR(255),
ta1_status VARCHAR(255),
transport_id INTEGER,
usage_indicator VARCHAR(255)
);
CREATE TABLE edi_map (
business_key_path VARCHAR(255),
business_key_type VARCHAR(255),
create_date TIMESTAMP,
description VARCHAR(10000),
direction VARCHAR(255),
guide_id INTEGER,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
match_key_fields VARCHAR(255),
modify_date TIMESTAMP,
name VARCHAR(255),
on_match VARCHAR(255),
parent_map_id INTEGER,
partner_id INTEGER,
revision INTEGER,
secondary_key_path VARCHAR(255),
standard VARCHAR(255),
status VARCHAR(255),
table_name VARCHAR(255),
transaction_set VARCHAR(255),
version VARCHAR(255)
);
CREATE TABLE edi_map_rule (
condition VARCHAR(10000),
create_date TIMESTAMP,
default_value VARCHAR(255),
edi_path VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
map_id INTEGER,
modify_date TIMESTAMP,
notes VARCHAR(10000),
qqq_path VARCHAR(255),
required BOOLEAN,
sequence INTEGER,
transform VARCHAR(255),
transform_args VARCHAR(10000)
);
CREATE TABLE edi_map_sample (
create_date TIMESTAMP,
expected_result_json VARCHAR(10000),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
last_preview_at TIMESTAMP,
last_preview_status VARCHAR(255),
map_id INTEGER,
modify_date TIMESTAMP,
name VARCHAR(255),
payload_ref VARCHAR(255)
);
CREATE TABLE edi_outbound_message (
ack_for_group_id INTEGER,
ack_for_interchange_id INTEGER,
agreement_id INTEGER,
attempts INTEGER,
claimed_at TIMESTAMP,
create_date TIMESTAMP,
delivered_at TIMESTAMP,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
interchange_id INTEGER,
last_error VARCHAR(255),
modify_date TIMESTAMP,
next_attempt_at TIMESTAMP,
outbound_request_id INTEGER,
partner_id INTEGER,
payload_ref VARCHAR(255),
payload_sha256 VARCHAR(255),
sent_at TIMESTAMP,
size_bytes BIGINT,
status VARCHAR(255),
transport_id INTEGER,
transport_message_id VARCHAR(255)
);
CREATE TABLE edi_outbound_request (
agreement_document_id INTEGER,
attempts INTEGER,
claimed_at TIMESTAMP,
create_date TIMESTAMP,
event_id VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
last_error VARCHAR(255),
modify_date TIMESTAMP,
next_attempt_at TIMESTAMP,
source_record_id VARCHAR(255),
source_table_name VARCHAR(255),
status VARCHAR(255),
UNIQUE (agreement_document_id, event_id)
);
CREATE TABLE edi_partner (
code VARCHAR(255),
contact_email VARCHAR(255),
contact_name VARCHAR(255),
contact_phone VARCHAR(255),
contains_phi BOOLEAN,
create_date TIMESTAMP,
default_timezone VARCHAR(255),
gateway_username VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
name VARCHAR(255),
notes VARCHAR(10000),
status VARCHAR(255),
UNIQUE (name),
UNIQUE (code)
);
CREATE TABLE edi_payload (
ciphertext BYTEA,
create_date TIMESTAMP,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
key_id VARCHAR(255),
kind VARCHAR(255),
modify_date TIMESTAMP,
sha256 VARCHAR(255),
size_bytes BIGINT
);
CREATE TABLE edi_transaction (
ack_status VARCHAR(255),
agreement_document_id INTEGER,
business_key VARCHAR(255),
business_key_type VARCHAR(255),
contains_phi BOOLEAN,
control_number VARCHAR(255),
create_date TIMESTAMP,
direction VARCHAR(255),
error_count INTEGER,
functional_group_id INTEGER,
guide_id INTEGER,
guide_revision INTEGER,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inbound_position INTEGER,
interchange_id INTEGER,
map_id INTEGER,
map_revision INTEGER,
modify_date TIMESTAMP,
partner_id INTEGER,
phi_business_key VARCHAR(255),
secondary_key VARCHAR(255),
segment_count INTEGER,
status VARCHAR(255),
target_record_id VARCHAR(255),
target_table_name VARCHAR(255),
transaction_set VARCHAR(255),
version VARCHAR(255),
warning_count INTEGER
);
CREATE TABLE edi_transaction_error (
ack_code VARCHAR(255),
component_position INTEGER,
create_date TIMESTAMP,
element_position INTEGER,
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
loop_id VARCHAR(255),
message VARCHAR(10000),
modify_date TIMESTAMP,
segment_id VARCHAR(255),
segment_position INTEGER,
severity VARCHAR(255),
snip_level INTEGER,
transaction_id INTEGER,
value_excerpt VARCHAR(255)
);
CREATE TABLE edi_transport (
as2_local_id VARCHAR(255),
as2_partner_id VARCHAR(255),
as2_subject VARCHAR(255),
as2_url VARCHAR(255),
compress BOOLEAN,
create_date TIMESTAMP,
direction VARCHAR(255),
email_attachment_name VARCHAR(255),
email_from_alias VARCHAR(255),
email_provider_name VARCHAR(255),
email_to VARCHAR(255),
encrypt_algorithm VARCHAR(255),
gateway_endpoint_name VARCHAR(255),
http_auth_password_env_var VARCHAR(255),
http_auth_user_env_var VARCHAR(255),
https_auth_type VARCHAR(255),
https_headers_json VARCHAR(10000),
https_inbound_auth VARCHAR(255),
https_secret_env_var VARCHAR(255),
https_url VARCHAR(255),
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
is_active BOOLEAN,
is_van BOOLEAN,
local_certificate_id INTEGER,
max_attempts INTEGER,
mdn_mode VARCHAR(255),
mdn_signed BOOLEAN,
modified_by VARCHAR(255),
modify_date TIMESTAMP,
name VARCHAR(255),
notes VARCHAR(10000),
partner_certificate_id INTEGER,
partner_id INTEGER,
partner_tls_certificate_id INTEGER,
sftp_after_pickup VARCHAR(255),
sftp_archive_path VARCHAR(255),
sftp_file_pattern VARCHAR(255),
sftp_host VARCHAR(255),
sftp_host_key_fingerprint VARCHAR(255),
sftp_inbound_path VARCHAR(255),
sftp_outbound_path VARCHAR(255),
sftp_password_env_var VARCHAR(255),
sftp_poll_cron VARCHAR(255),
sftp_port INTEGER,
sftp_private_key_env_var VARCHAR(255),
sftp_temp_suffix VARCHAR(255),
sftp_username VARCHAR(255),
sign_algorithm VARCHAR(255),
type VARCHAR(255),
van_name VARCHAR(255),
UNIQUE (name)
);
Vendored
-295
View File
@@ -1,295 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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
#
# http://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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"
Vendored
-189
View File
@@ -1,189 +0,0 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+40 -180
View File
@@ -1,119 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2026 Kingsrook, LLC
~
~ 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
~
~ http://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.
-->
<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>
<revision>0.1.0-RC.1</revision>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<jacoco.version>0.8.15</jacoco.version>
</properties>
<parent>
<groupId>com.kingsrook</groupId>
<artifactId>qbit-build-parent</artifactId>
<version>2.0.0</version>
</parent>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-edi</artifactId>
<version>${revision}</version>
<packaging>jar</packaging>
<name>QBit EDI</name>
<description>QQQ QBit: EDI (X12 and EDIFACT) trading partners, transports (AS2, SFTP, VAN, HTTPS, email), acknowledgements, data-driven implementation guides and maps</description>
<url>https://github.com/QRun-IO/qbit-edi</url>
<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">
<modelVersion>4.0.0</modelVersion>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-example-app</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<developers>
<developer>
<id>kingsrook</id>
<name>Kingsrook Team</name>
<email>contact@kingsrook.com</email>
<organization>Kingsrook, LLC</organization>
<organizationUrl>https://kingsrook.com</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/QRun-IO/qbit-edi.git</connection>
<developerConnection>scm:git:ssh://github.com:QRun-IO/qbit-edi.git</developerConnection>
<url>https://github.com/QRun-IO/qbit-edi</url>
<tag>HEAD</tag>
</scm>
<name>QBit Example Application</name>
<description>Template for Application QBits - complete mini-applications with UI navigation</description>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<qqq.version>4.0.0</qqq.version>
</properties>
<dependencies>
<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.81</version><scope>test</scope></dependency>
<dependency><groupId>com.kingsrook.qqq</groupId><artifactId>qqq-backend-module-filesystem</artifactId></dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-esb</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-object-storage</artifactId>
<version>0.1.0-RC.2</version>
<optional>true</optional>
<exclusions>
<exclusion><groupId>com.kingsrook.qqq</groupId><artifactId>*</artifactId></exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.xlate</groupId>
<artifactId>staedi</artifactId>
<version>1.26.4</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.4.240</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-middleware-javalin</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-rdbms</artifactId>
</dependency>
<!-- QQQ Backend Core -->
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-core</artifactId>
<version>${qqq.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -122,109 +40,51 @@
<version>3.27.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-rdbms</artifactId>
<version>${qqq.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.12.6</version>
</dependency>
</dependencies>
<configuration>
<configLocation>checkstyle/config.xml</configLocation>
<headerLocation>checkstyle/license.txt</headerLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>false</failsOnError>
<failOnViolation>true</failOnViolation>
<violationSeverity>warning</violationSeverity>
<includeTestSourceDirectory>false</includeTestSourceDirectory>
</configuration>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<version>0.8.10</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.70</minimum>
</limit>
<limit>
<counter>CLASS</counter>
<value>COVEREDRATIO</value>
<minimum>0.90</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>qqq-snapshot</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<qqq.snapshot.version>4.1.0-RC.5</qqq.snapshot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-bom-pom</artifactId>
<version>${qqq.snapshot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</profile>
</profiles>
</project>
@@ -1,353 +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;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfig;
/*******************************************************************************
** Code configuration for the EDI qbit. Runtime settings (partners, agreements,
** transports, guides and maps) are data in the edi_ tables, not here.
*******************************************************************************/
public class EdiQBitConfig implements QBitConfig
{
public static final String TABLE_NAME_PREFIX = "edi_";
public static final String PERMISSION_BASE_NAME = "edi";
private final java.util.Set<String> sendAsEdiTables = new java.util.LinkedHashSet<>();
/*******************************************************************************
** Opt a source table into the manual Send As EDI action.
*******************************************************************************/
public EdiQBitConfig withSendAsEdiTable(String tableName)
{
sendAsEdiTables.add(tableName);
return (this);
}
/*******************************************************************************
** Explicitly opted-in manual source tables.
*******************************************************************************/
public java.util.Set<String> getSendAsEdiTables()
{
return (java.util.Set.copyOf(sendAsEdiTables));
}
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();
private String esbDestinationName;
private String schedulerName;
private Integer maxSegmentsPerTransaction = 100000;
private String payloadStore = "DATABASE";
private com.kingsrook.qbits.edi.security.EdiSecrets secretsResolver = System::getenv;
private Boolean partnerLocks = false;
private String backendName;
private String appName = "edi";
private String appLabel = "EDI";
private String parentAppName;
/*******************************************************************************
** Validate the configuration, collecting all errors.
*******************************************************************************/
@Override
public void validate(QInstance instance, List<String> errors)
{
if(backendName == null || instance.getBackend(backendName) == null)
{
errors.add("backendName must name an existing backend");
}
if(maxSegmentsPerTransaction == null || maxSegmentsPerTransaction < 2)
{
errors.add("maxSegmentsPerTransaction must be at least 2");
}
if(!List.of("DATABASE", "OBJECT_STORAGE").contains(payloadStore))
{
errors.add("payloadStore must be DATABASE or OBJECT_STORAGE");
}
if(secretsResolver == null || partnerLocks == null)
{
errors.add("secretsResolver and partnerLocks are required");
}
if(appName == null || appName.isBlank())
{
errors.add("appName is required");
}
}
/*******************************************************************************
** The backend for the qbit's tables.
*******************************************************************************/
@Override
public String getDefaultBackendNameForTables()
{
return (backendName);
}
/*******************************************************************************
** Getter for backendName
*******************************************************************************/
public String getBackendName()
{
return (this.backendName);
}
/*******************************************************************************
** Fluent setter for backendName
*******************************************************************************/
public EdiQBitConfig withBackendName(String backendName)
{
this.backendName = backendName;
return (this);
}
/*******************************************************************************
** Getter for appName
*******************************************************************************/
public String getAppName()
{
return (this.appName);
}
/*******************************************************************************
** Fluent setter for appName
*******************************************************************************/
public EdiQBitConfig withAppName(String appName)
{
this.appName = appName;
return (this);
}
/*******************************************************************************
** Getter for appLabel
*******************************************************************************/
public String getAppLabel()
{
return (this.appLabel);
}
/*******************************************************************************
** Fluent setter for appLabel
*******************************************************************************/
public EdiQBitConfig withAppLabel(String appLabel)
{
this.appLabel = appLabel;
return (this);
}
/*******************************************************************************
** Getter for parentAppName
*******************************************************************************/
public String getParentAppName()
{
return (this.parentAppName);
}
/*******************************************************************************
** Fluent setter for parentAppName
*******************************************************************************/
public EdiQBitConfig withParentAppName(String parentAppName)
{
this.parentAppName = parentAppName;
return (this);
}
/*******************************************************************************
** Get schedulerName.
*******************************************************************************/
public String getSchedulerName()
{
return (schedulerName);
}
/*******************************************************************************
** Configure schedulerName.
*******************************************************************************/
public EdiQBitConfig withSchedulerName(String value)
{
schedulerName = value;
return (this);
}
/*******************************************************************************
** Get maxSegmentsPerTransaction.
*******************************************************************************/
public Integer getMaxSegmentsPerTransaction()
{
return (maxSegmentsPerTransaction);
}
/*******************************************************************************
** Configure maxSegmentsPerTransaction.
*******************************************************************************/
public EdiQBitConfig withMaxSegmentsPerTransaction(Integer value)
{
maxSegmentsPerTransaction = value;
return (this);
}
/*******************************************************************************
** Get payloadStore.
*******************************************************************************/
@JsonIgnore
public String getPayloadStore()
{
return (payloadStore);
}
/*******************************************************************************
** Configure payloadStore.
*******************************************************************************/
public EdiQBitConfig withPayloadStore(String value)
{
payloadStore = value;
return (this);
}
/*******************************************************************************
** Get secretsResolver.
*******************************************************************************/
@JsonIgnore
public com.kingsrook.qbits.edi.security.EdiSecrets getSecretsResolver()
{
return (secretsResolver);
}
/*******************************************************************************
** Configure secretsResolver.
*******************************************************************************/
public EdiQBitConfig withSecretsResolver(com.kingsrook.qbits.edi.security.EdiSecrets value)
{
secretsResolver = value;
return (this);
}
/*******************************************************************************
** Get partnerLocks.
*******************************************************************************/
public Boolean getPartnerLocks()
{
return (partnerLocks);
}
/*******************************************************************************
** Configure partnerLocks.
*******************************************************************************/
public EdiQBitConfig withPartnerLocks(Boolean value)
{
partnerLocks = value;
return (this);
}
/*******************************************************************************
** Get payloadBucketAlias.
*******************************************************************************/
public String getPayloadBucketAlias()
{
return (payloadBucketAlias);
}
/*******************************************************************************
** Configure payloadBucketAlias.
*******************************************************************************/
public EdiQBitConfig withPayloadBucketAlias(String value)
{
payloadBucketAlias = value;
return (this);
}
/*******************************************************************************
** Get serviceSessionSupplier.
*******************************************************************************/
@JsonIgnore
public java.util.function.Supplier<com.kingsrook.qqq.backend.core.model.session.QSession> getServiceSessionSupplier()
{
return (serviceSessionSupplier);
}
/*******************************************************************************
** Configure serviceSessionSupplier.
*******************************************************************************/
public EdiQBitConfig withServiceSessionSupplier(java.util.function.Supplier<com.kingsrook.qqq.backend.core.model.session.QSession> value)
{
serviceSessionSupplier = value;
return (this);
}
/*******************************************************************************
** Get clock.
*******************************************************************************/
@JsonIgnore
public java.time.Clock getClock()
{
return (clock);
}
/*******************************************************************************
** Configure clock.
*******************************************************************************/
public EdiQBitConfig withClock(java.time.Clock value)
{
clock = value;
return (this);
}
/*******************************************************************************
** Optional destination for core table events on main tables.
*******************************************************************************/
public String getEsbDestinationName()
{
return (esbDestinationName);
}
/*******************************************************************************
** Enable standard table publications to a host-configured destination.
*******************************************************************************/
public EdiQBitConfig withEsbDestinationName(String value)
{
esbDestinationName = value;
return (this);
}
}
@@ -1,150 +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;
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.qbits.QBitMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaDataProducer;
/*******************************************************************************
** Entry point for the EDI qbit; component producers are scanned only in the
** metadata package.
*******************************************************************************/
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 = "0.1.0";
private EdiQBitConfig qBitConfig;
private String namespace;
/*******************************************************************************
** Fluent configuration setter.
*******************************************************************************/
public EdiQBitProducer withQBitConfig(EdiQBitConfig config)
{
qBitConfig = config;
return (this);
}
/*******************************************************************************
** Configuration for component producers.
*******************************************************************************/
@Override
public EdiQBitConfig getQBitConfig()
{
return (qBitConfig);
}
/*******************************************************************************
** Fluent namespace setter.
*******************************************************************************/
public EdiQBitProducer withNamespace(String namespace)
{
this.namespace = namespace;
return (this);
}
/*******************************************************************************
** Qbit namespace.
*******************************************************************************/
@Override
public String getNamespace()
{
return (namespace);
}
/*******************************************************************************
** Qbit identity.
*******************************************************************************/
@Override
public QBitMetaData getQBitMetaData()
{
return (new QBitMetaData().withGroupId(GROUP_ID).withArtifactId(ARTIFACT_ID).withVersion(VERSION).withNamespace(namespace).withConfig(qBitConfig));
}
/*******************************************************************************
** Unconfigured producers discovered by host scanning are disabled.
*******************************************************************************/
@Override
public boolean isEnabled()
{
return (qBitConfig != null);
}
/*******************************************************************************
** Only the metadata package holds component producers.
*******************************************************************************/
@Override
public String getPackageNameForFindingMetaDataProducers()
{
return ("com.kingsrook.qbits.edi.metadata");
}
/*******************************************************************************
** Validate the configuration and install the qbit's metadata.
*******************************************************************************/
@Override
public MetaDataProducerMultiOutput produce(QInstance instance) throws QException
{
qBitConfig.validate(instance);
MetaDataProducerMultiOutput output = QBitMetaDataProducer.super.produce(instance);
output.addSelfToInstance(instance);
EdiRuntime.install(instance, qBitConfig);
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()
.withPlugin(new com.kingsrook.qbits.edi.transport.gateway.GatewayTransport())
.withPlugin(new com.kingsrook.qbits.edi.transport.HttpsTransport()).withPlugin(new com.kingsrook.qbits.edi.transport.SftpTransport())
.withPlugin(new com.kingsrook.qbits.edi.transport.UnavailableTransport("AS2")).withPlugin(new com.kingsrook.qbits.edi.transport.UnavailableTransport("EMAIL")));
try
{
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));
}
catch(NoClassDefFoundError optionalMiddlewareAbsent)
{
// Machine hooks are installed only when optional Javalin is present.
}
return (new MetaDataProducerMultiOutput());
}
}
@@ -1,214 +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;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import com.kingsrook.qbits.edi.ack.Ack997Builder;
import com.kingsrook.qbits.edi.ack.Ack999Builder;
import com.kingsrook.qbits.edi.ack.InboundAckReader;
import com.kingsrook.qbits.edi.ack.TA1Builder;
import com.kingsrook.qbits.edi.guide.BuiltInGuideLoader;
import com.kingsrook.qbits.edi.guide.GuideValidator;
import com.kingsrook.qbits.edi.storage.DatabasePayloadStore;
import com.kingsrook.qbits.edi.storage.PayloadCrypto;
import com.kingsrook.qbits.edi.x12.ControlNumberAllocator;
import com.kingsrook.qbits.edi.x12.DuplicateDetector;
import com.kingsrook.qbits.edi.x12.X12EnvelopeReader;
import com.kingsrook.qbits.edi.x12.X12Writer;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
/*******************************************************************************
** Per-QInstance service holder; metadata installation does not access the database.
*******************************************************************************/
public final class EdiRuntime
{
private static final QLogger LOG = QLogger.getLogger(EdiRuntime.class);
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 = new GuideValidator();
private final BuiltInGuideLoader guides = new BuiltInGuideLoader();
private final com.kingsrook.qbits.edi.storage.PayloadStore payloads;
private final EdiQBitConfig config;
private final com.kingsrook.qbits.edi.map.TransformRegistry transforms = new com.kingsrook.qbits.edi.map.TransformRegistry();
private final DuplicateDetector duplicates = new DuplicateDetector();
private final ControlNumberAllocator controls;
private final Ack997Builder ack997 = new Ack997Builder();
private final Ack999Builder ack999 = new Ack999Builder();
private final TA1Builder ta1 = new TA1Builder();
private final InboundAckReader acknowledgements = new InboundAckReader();
/*******************************************************************************
** Construct services without resolving keys until content is stored or opened.
*******************************************************************************/
private EdiRuntime(QInstance instance, EdiQBitConfig config)
{
this.config = 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())
: new DatabasePayloadStore(new PayloadCrypto(config.getSecretsResolver()));
controls = instance.getBackend(config.getBackendName()) instanceof RDBMSBackendMetaData backend ? new ControlNumberAllocator(backend) : null;
}
/*******************************************************************************
** Install for a host instance.
*******************************************************************************/
public static void install(QInstance instance, EdiQBitConfig config)
{
INSTANCES.put(instance, new EdiRuntime(instance, config));
}
/*******************************************************************************
** Get services for the current QQQ context.
*******************************************************************************/
public static EdiRuntime current()
{
return (Objects.requireNonNull(INSTANCES.get(QContext.getQInstance()), "EDI runtime has not been installed"));
}
/*******************************************************************************
** Call at host startup after DDL, with the service session established.
*******************************************************************************/
public List<Integer> initialize() throws Exception
{
if("DATABASE".equals(config.getPayloadStore()))
{
LOG.warn("EDI database payload storage is enabled; object storage is recommended for production");
}
var ids = guides.load();
com.kingsrook.qbits.edi.processes.OutboundProcesses.installSchedules();
return (ids);
}
/*******************************************************************************
** Access reader.
*******************************************************************************/
public X12EnvelopeReader reader()
{
return (reader);
}
/*******************************************************************************
** Access writer.
*******************************************************************************/
public X12Writer writer()
{
return (writer);
}
/*******************************************************************************
** Access validator.
*******************************************************************************/
public GuideValidator validator()
{
return (validator);
}
/*******************************************************************************
** Access guides.
*******************************************************************************/
public BuiltInGuideLoader guides()
{
return (guides);
}
/*******************************************************************************
** Access payloads.
*******************************************************************************/
public com.kingsrook.qbits.edi.storage.PayloadStore payloads()
{
return (payloads);
}
/*******************************************************************************
** Access duplicates.
*******************************************************************************/
public DuplicateDetector duplicates()
{
return (duplicates);
}
/*******************************************************************************
** Access controls.
*******************************************************************************/
public ControlNumberAllocator controls()
{
return (Objects.requireNonNull(controls, "Control allocation requires an RDBMS backend"));
}
/*******************************************************************************
** Access ack997.
*******************************************************************************/
public Ack997Builder ack997()
{
return (ack997);
}
/*******************************************************************************
** Access ack999.
*******************************************************************************/
public Ack999Builder ack999()
{
return (ack999);
}
/*******************************************************************************
** Access ta1.
*******************************************************************************/
public TA1Builder ta1()
{
return (ta1);
}
/*******************************************************************************
** Access acknowledgements.
*******************************************************************************/
public InboundAckReader acknowledgements()
{
return (acknowledgements);
}
/*******************************************************************************
** Installed host configuration.
*******************************************************************************/
public EdiQBitConfig config()
{
return (config);
}
/*******************************************************************************
** Host-extensible inbound transformations.
*******************************************************************************/
public com.kingsrook.qbits.edi.map.TransformRegistry transforms()
{
return (transforms);
}
}
@@ -1,43 +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.ack;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.edi.guide.ValidationFinding;
import com.kingsrook.qbits.edi.x12.X12FunctionalGroup;
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
/*******************************************************************************
** Generate an X12 997 functional acknowledgement.
*******************************************************************************/
public class Ack997Builder
{
/*******************************************************************************
** Build transaction detail at the configured acknowledgement level.
*******************************************************************************/
public X12TransactionSet build(X12FunctionalGroup group, Map<String, List<ValidationFinding>> findings, AckLevel level, String controlNumber)
{
return (FunctionalAckBuilder.build(false, group, findings, level, controlNumber));
}
}
@@ -1,43 +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.ack;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.edi.guide.ValidationFinding;
import com.kingsrook.qbits.edi.x12.X12FunctionalGroup;
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
/*******************************************************************************
** Generate an X12 999 functional acknowledgement.
*******************************************************************************/
public class Ack999Builder
{
/*******************************************************************************
** Build transaction detail at the configured acknowledgement level.
*******************************************************************************/
public X12TransactionSet build(X12FunctionalGroup group, Map<String, List<ValidationFinding>> findings, AckLevel level, String controlNumber)
{
return (FunctionalAckBuilder.build(true, group, findings, level, controlNumber));
}
}
@@ -1,60 +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.ack;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import com.kingsrook.qbits.edi.x12.X12FunctionalGroup;
import com.kingsrook.qbits.edi.x12.X12Interchange;
import com.kingsrook.qbits.edi.x12.X12Segment;
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
/*******************************************************************************
** Address acknowledgements to the original sender using newly allocated controls.
*******************************************************************************/
public final class AckEnvelope
{
/*******************************************************************************
** Swap interchange identities while preserving standard version and usage.
*******************************************************************************/
public static X12Segment replyIsa(X12Segment original, String control, LocalDateTime at)
{
return (X12Segment.of("ISA", 1, "00", "", "00", "", original.value(7), original.value(8), original.value(5), original.value(6),
at.format(DateTimeFormatter.ofPattern("yyMMdd")), at.format(DateTimeFormatter.ofPattern("HHmm")), original.value(11), original.value(12),
"0".repeat(Math.max(0, 9 - control.length())) + control, "0", original.value(15), original.value(16)));
}
/*******************************************************************************
** Wrap an acknowledgement transaction in a reply functional group and interchange.
*******************************************************************************/
public static X12Interchange wrap(X12Interchange received, X12FunctionalGroup group, X12TransactionSet acknowledgement, String isaControl, String gsControl, LocalDateTime at)
{
X12Segment isa = replyIsa(received.isa(), isaControl, at);
String version = acknowledgement.st().value(1).equals("999") ? "005010X231A1" : group.gs().value(8);
X12Segment gs = X12Segment.of("GS", 2, "FA", group.gs().value(3), group.gs().value(2), at.format(DateTimeFormatter.BASIC_ISO_DATE),
at.format(DateTimeFormatter.ofPattern("HHmm")), gsControl, "X", version);
X12FunctionalGroup reply = new X12FunctionalGroup(gs, X12Segment.of("GE", 0, "1", gsControl), List.of(acknowledgement), List.of());
return (new X12Interchange(received.separators(), isa, X12Segment.of("IEA", 0, "1", isa.value(13)), List.of(reply), List.of(), List.of()));
}
}
@@ -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.ack;
/*******************************************************************************
** Transaction detail included in a functional acknowledgement.
*******************************************************************************/
public enum AckLevel
{
GROUP_ONLY, ALL_TRANSACTIONS, ERRORS_ONLY
}
@@ -1,137 +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.ack;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.edi.guide.ValidationFinding;
import com.kingsrook.qbits.edi.x12.EnvelopeFinding;
import com.kingsrook.qbits.edi.x12.X12FunctionalGroup;
import com.kingsrook.qbits.edi.x12.X12Segment;
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
/*******************************************************************************
** Shared 997/999 status, counts and detail generation.
*******************************************************************************/
final class FunctionalAckBuilder
{
/*******************************************************************************
** Build an acknowledgement transaction with its own ST control number.
*******************************************************************************/
static X12TransactionSet build(boolean implementation, X12FunctionalGroup group, Map<String, List<ValidationFinding>> findings, AckLevel level, String control)
{
List<X12Segment> segments = new ArrayList<>();
if(implementation)
{
add(segments, "ST", "999", control, "005010X231A1");
add(segments, "AK1", group.gs().value(1), group.gs().value(6), group.gs().value(8));
}
else
{
add(segments, "ST", "997", control);
add(segments, "AK1", group.gs().value(1), group.gs().value(6));
}
int accepted = 0;
boolean hasWarnings = false;
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()));
String status = rejected ? "R" : errors.isEmpty() ? "A" : "E";
if(!rejected)
{
accepted++;
}
hasWarnings |= status.equals("E");
if(level == AckLevel.GROUP_ONLY || level == AckLevel.ERRORS_ONLY && status.equals("A"))
{
continue;
}
if(implementation && !transaction.st().value(3).isEmpty())
{
add(segments, "AK2", transaction.st().value(1), transaction.st().value(2), transaction.st().value(3));
}
else
{
add(segments, "AK2", transaction.st().value(1), transaction.st().value(2));
}
for(ValidationFinding finding : errors)
{
add(segments, implementation ? "IK3" : "AK3", finding.segmentId(), String.valueOf(finding.segmentPosition()),
finding.loopId() == null ? "" : finding.loopId(), implementation ? finding.ik3Code() : finding.ak3Code());
if(finding.elementPosition() != null)
{
List<String> position = new ArrayList<>(List.of(finding.elementPosition().toString()));
if(finding.componentPosition() != null)
{
position.add(finding.componentPosition().toString());
}
segments.add(new X12Segment(implementation ? "IK4" : "AK4", segments.size() + 1,
List.of(List.of(position), List.of(List.of("")), List.of(List.of(implementation ? finding.ik4Code() : finding.ak4Code())))));
}
if(implementation && finding.context() != null)
{
var context = finding.context();
List<List<List<String>>> values = new ArrayList<>();
for(String value : List.of("SITUATIONAL TRIGGER", context.segmentId(), String.valueOf(context.segmentPosition()), context.loopId() == null ? "" : context.loopId()))
{
values.add(List.of(List.of(value)));
}
if(context.elementPosition() != null)
{
List<String> position = new ArrayList<>(List.of(context.elementPosition().toString()));
if(context.componentPosition() != null)
{
position.add(context.componentPosition().toString());
}
values.add(List.of(position));
}
segments.add(new X12Segment("CTX", segments.size() + 1, values));
}
}
List<String> statusElements = new ArrayList<>(List.of(status));
transaction.findings().stream().map(EnvelopeFinding::code).distinct().limit(5).forEach(statusElements::add);
if(rejected && statusElements.size() == 1)
{
statusElements.add("5");
}
add(segments, implementation ? "IK5" : "AK5", statusElements.toArray(String[]::new));
}
String status = !group.findings().isEmpty() || accepted == 0 && !group.transactions().isEmpty() ? "R"
: accepted < group.transactions().size() ? "P" : hasWarnings ? "E" : "A";
List<String> summary = new ArrayList<>(List.of(status, group.ge().value(1), String.valueOf(group.transactions().size()), String.valueOf(accepted)));
group.findings().stream().map(EnvelopeFinding::code).distinct().limit(5).forEach(summary::add);
add(segments, "AK9", summary.toArray(String[]::new));
add(segments, "SE", String.valueOf(segments.size() + 1), control);
return (new X12TransactionSet(segments.getFirst(), segments.getLast(), List.copyOf(segments), List.of()));
}
/*******************************************************************************
** Append a positioned simple segment.
*******************************************************************************/
private static void add(List<X12Segment> segments, String id, String... values)
{
segments.add(X12Segment.of(id, segments.size() + 1, values));
}
}
@@ -1,107 +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.ack;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.edi.x12.X12FunctionalGroup;
import com.kingsrook.qbits.edi.x12.X12Interchange;
import com.kingsrook.qbits.edi.x12.X12Segment;
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
/*******************************************************************************
** Read acknowledgement controls and outcomes for later reconciliation.
*******************************************************************************/
public class InboundAckReader
{
/*******************************************************************************
** Parsed functional or interchange acknowledgement.
*******************************************************************************/
public record Result(String type, String controlNumber, String status, Map<String, String> transactionStatuses, String noteCode)
{
}
/*******************************************************************************
** Read all received acknowledgements in an interchange.
*******************************************************************************/
public List<Result> read(X12Interchange interchange)
{
List<Result> results = new ArrayList<>();
for(X12Segment segment : interchange.serviceSegments())
{
if(segment.id().equals("TA1"))
{
results.add(new Result("TA1", segment.value(1), segment.value(4), Map.of(), segment.value(5)));
}
}
for(X12FunctionalGroup group : interchange.groups())
{
for(X12TransactionSet transaction : group.transactions())
{
if(List.of("997", "999").contains(transaction.st().value(1)))
{
results.add(read(transaction));
}
}
}
return (results);
}
/*******************************************************************************
** Extract AK1, AK2, AK5/IK5 and AK9 without acknowledging the ack.
*******************************************************************************/
public Result read(X12TransactionSet transaction)
{
String control = null;
String status = null;
String stControl = null;
Map<String, String> statuses = new LinkedHashMap<>();
for(X12Segment segment : transaction.segments())
{
switch(segment.id())
{
case "AK1" -> control = segment.value(2);
case "AK2" -> stControl = segment.value(2);
case "AK5", "IK5" ->
{
if(stControl != null)
{
statuses.put(stControl, segment.value(1));
}
}
case "AK9" -> status = segment.value(1);
default ->
{
// Detail segments do not change reconciliation status.
}
}
}
if(control == null || status == null)
{
throw new IllegalArgumentException("Incomplete functional acknowledgement");
}
return (new Result(transaction.st().value(1), control, status, Map.copyOf(statuses), null));
}
}
@@ -1,59 +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.ack;
import java.time.LocalDateTime;
import java.util.List;
import com.kingsrook.qbits.edi.x12.X12Interchange;
import com.kingsrook.qbits.edi.x12.X12Segment;
/*******************************************************************************
** TA1 is an interchange-level service segment, never an ST/SE transaction.
*******************************************************************************/
public class TA1Builder
{
/*******************************************************************************
** Select the first interchange finding, or acceptance when there are none.
*******************************************************************************/
public X12Segment build(X12Segment isa, List<com.kingsrook.qbits.edi.x12.EnvelopeFinding> findings)
{
return (build(isa, findings.stream().filter(f -> "TA1".equals(f.level())).map(com.kingsrook.qbits.edi.x12.EnvelopeFinding::code).findFirst().orElse("000")));
}
/*******************************************************************************
** Acknowledge original ISA control, date and time with a TA1 note code.
*******************************************************************************/
public X12Segment build(X12Segment isa, String noteCode)
{
return (X12Segment.of("TA1", 2, isa.value(13), isa.value(9), isa.value(10), "000".equals(noteCode) ? "A" : "R", noteCode));
}
/*******************************************************************************
** Generate a reply interchange with swapped identities and its own ISA13.
*******************************************************************************/
public X12Interchange interchange(X12Interchange received, String control, String noteCode, LocalDateTime at)
{
X12Segment isa = AckEnvelope.replyIsa(received.isa(), control, at);
return (new X12Interchange(received.separators(), isa, X12Segment.of("IEA", 3, "0", isa.value(13)), List.of(), List.of(build(received.isa(), noteCode)), List.of()));
}
}
@@ -1,478 +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.io.Serializable;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kingsrook.qbits.edi.EdiQBitConfig;
import com.kingsrook.qbits.edi.EdiRuntime;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
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.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;
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.StatisticsData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.TableData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
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.PermissionLevel;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Typed EDI home. Every data query explicitly enforces the viewing user's access.
*******************************************************************************/
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");
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("queue", "acks", "errors", "volume", "system");
static
{
DOCUMENT_NAMES.putAll(Map.ofEntries(Map.entry("810", "Invoice"), Map.entry("846", "Inventory Advice"), Map.entry("850", "Purchase Order"),
Map.entry("855", "PO Acknowledgment"), Map.entry("856", "Ship Notice"), Map.entry("860", "PO Change"), Map.entry("940", "Warehouse Shipping Order"),
Map.entry("945", "Warehouse Shipping Advice"), Map.entry("214", "Shipment Status"), Map.entry("204", "Load Tender"), Map.entry("837", "Claim"),
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("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("queue", "Exception queue");
LABELS.put("acks", "Overdue acknowledgements");
LABELS.put("errors", "Recent errors (24 hours)");
LABELS.put("volume", "Volume by partner and document (7 days)");
LABELS.put("system", "System");
}
/*******************************************************************************
** Activity and setup only; all other tables remain parent record children.
*******************************************************************************/
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(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));
app.setParentAppName(config.getParentAppName());
tables.stream().filter(t -> ACTIVITY.contains(t.getName()) || SETUP.contains(t.getName())).forEach(app::withChild);
return (app);
}
/*******************************************************************************
** 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) -> 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);
}
/*******************************************************************************
** Direct rendering also enforces table permissions, including nested process calls.
*******************************************************************************/
@Override
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
{
String name = input.getWidgetMetaData().getName().substring("edi_home_".length());
Instant now = EdiRuntime.current().config().getClock().instant();
return (switch(name)
{
case "in_today", "out_today" -> count("edi_transaction", new QQueryFilter()
.withCriteria("direction", QCriteriaOperator.EQUALS, name.startsWith("in_") ? "INBOUND" : "OUTBOUND")
.withCriteria("createDate", QCriteriaOperator.GREATER_THAN_OR_EQUALS, now.truncatedTo(ChronoUnit.DAYS)));
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 "certificates" -> count("edi_certificate", new QQueryFilter().withCriteria("notAfter", QCriteriaOperator.LESS_THAN_OR_EQUALS, now.plus(30, ChronoUnit.DAYS))
.withCriteria("status", QCriteriaOperator.EQUALS, "ACTIVE"));
case "queue" -> queue(now);
case "acks" -> acks(now);
case "errors" -> errors(now);
case "volume" -> volume(now);
case "system" -> system(now);
default -> throw new QException("Unknown EDI home widget " + name);
});
}
/*******************************************************************************
** A counted statistic linking to the same filter.
*******************************************************************************/
private static RenderWidgetOutput count(String table, QQueryFilter filter) throws QException
{
return (new RenderWidgetOutput(new StatisticsData().withCount(countOf(table, filter)).withCountURL(link(table, filter))));
}
/*******************************************************************************
** Count with enforcement.
*******************************************************************************/
private static Integer countOf(String table, QQueryFilter filter) throws QException
{
CountInput countInput = new CountInput(table).withFilter(filter);
countInput.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
return (new CountAction().execute(countInput).getCount());
}
/*******************************************************************************
** Open and in-progress exceptions.
*******************************************************************************/
private static QQueryFilter openExceptions()
{
return (new QQueryFilter().withCriteria("status", QCriteriaOperator.IN, "OPEN", "IN_PROGRESS"));
}
/*******************************************************************************
** Outbound groups whose expected acknowledgement is past due.
*******************************************************************************/
private static QQueryFilter overdueAcks(Instant now)
{
return (new QQueryFilter().withCriteria("ackStatus", QCriteriaOperator.EQUALS, "PENDING").withCriteria("ackDueAt", QCriteriaOperator.LESS_THAN, now));
}
/*******************************************************************************
** Open exceptions, oldest first, in four narrow columns.
*******************************************************************************/
private static RenderWidgetOutput queue(Instant now) throws QException
{
Map<Integer, String> partners = partnerNames();
List<Map<String, Object>> rows = new ArrayList<>();
for(QRecord row : query("edi_exception", openExceptions().withOrderBy(new QFilterOrderBy("createDate", true)).withOrderBy(new QFilterOrderBy("id", true)).withLimit(20)))
{
rows.add(row("Exception", plain(row.getValueString("type")) + ": " + row.getValueString("summary"),
"Partner", partnerName(partners, row.getValueInteger("partnerId")), "Age", humanize(Duration.between(row.getValueInstant("createDate"), now))));
}
return (table(List.of("Exception", "Partner", "Age"), rows, "edi_exception", openExceptions(), "View all exceptions"));
}
/*******************************************************************************
** Overdue acknowledgements with partner names and humanized ages.
*******************************************************************************/
private static RenderWidgetOutput acks(Instant now) throws QException
{
Map<Integer, String> partners = partnerNames();
List<Map<String, Object>> rows = new ArrayList<>();
for(QRecord group : query("edi_functional_group", overdueAcks(now).withOrderBy(new QFilterOrderBy("ackDueAt", true)).withLimit(20)))
{
Integer partnerId = null;
Instant sent = null;
List<QRecord> interchanges = query("edi_interchange", new QQueryFilter().withCriteria("id", QCriteriaOperator.EQUALS, group.getValue("interchangeId")));
if(!interchanges.isEmpty())
{
partnerId = interchanges.getFirst().getValueInteger("partnerId");
Serializable messageId = interchanges.getFirst().getValue("outboundMessageId");
if(messageId != null)
{
List<QRecord> messages = query("edi_outbound_message", new QQueryFilter().withCriteria("id", QCriteriaOperator.EQUALS, messageId));
sent = messages.isEmpty() ? null : messages.getFirst().getValueInstant("sentAt");
}
}
Instant due = group.getValueInstant("ackDueAt");
rows.add(row("Partner", partnerName(partners, partnerId), "Group", "GS " + group.getValueString("controlNumber"),
"Sent", sent == null ? "Not sent" : humanize(Duration.between(sent, now)) + " ago", "Overdue by", due == null ? "" : humanize(Duration.between(due, now))));
}
return (table(List.of("Partner", "Group", "Sent", "Overdue by"), rows, "edi_functional_group", overdueAcks(now), "View functional groups"));
}
/*******************************************************************************
** Exceptions raised in the last day, newest first.
*******************************************************************************/
private static RenderWidgetOutput errors(Instant now) throws QException
{
Map<Integer, String> partners = partnerNames();
List<Map<String, Object>> rows = new ArrayList<>();
for(QRecord row : query("edi_exception", lastDay(now).withOrderBy(new QFilterOrderBy("createDate", false)).withOrderBy(new QFilterOrderBy("id", false)).withLimit(20)))
{
rows.add(row("Error", plain(row.getValueString("type")) + ": " + row.getValueString("summary"),
"Partner", partnerName(partners, row.getValueInteger("partnerId")), "When", humanize(Duration.between(row.getValueInstant("createDate"), now)) + " ago"));
}
return (table(List.of("Error", "Partner", "When"), rows, "edi_exception", lastDay(now), "View exceptions"));
}
/*******************************************************************************
** Created within the last day.
*******************************************************************************/
private static QQueryFilter lastDay(Instant now)
{
return (new QQueryFilter().withCriteria("createDate", QCriteriaOperator.GREATER_THAN_OR_EQUALS, now.minus(1, ChronoUnit.DAYS)));
}
/*******************************************************************************
** Documents by partner and transaction set over seven days, with a total row.
*******************************************************************************/
private static RenderWidgetOutput volume(Instant now) throws QException
{
Map<Integer, String> partners = partnerNames();
QQueryFilter filter = new QQueryFilter().withCriteria("createDate", QCriteriaOperator.GREATER_THAN_OR_EQUALS, now.minus(7, ChronoUnit.DAYS));
Map<List<String>, int[]> counts = new TreeMap<>((a, b) -> a.get(0).equals(b.get(0)) ? a.get(1).compareTo(b.get(1)) : a.get(0).compareTo(b.get(0)));
for(QRecord row : query("edi_transaction", filter))
{
int[] c = counts.computeIfAbsent(List.of(partnerName(partners, row.getValueInteger("partnerId")), documentName(row.getValueString("transactionSet"))), k -> new int[3]);
c["INBOUND".equals(row.getValueString("direction")) ? 0 : 1]++;
if(List.of("INVALID", "REJECTED").contains(row.getValueString("status")))
{
c[2]++;
}
}
List<Map<String, Object>> rows = new ArrayList<>();
int[] totals = new int[3];
for(Map.Entry<List<String>, int[]> entry : counts.entrySet())
{
int[] c = entry.getValue();
rows.add(row("Partner", entry.getKey().get(0), "Document", entry.getKey().get(1), "In / out / rejected", c[0] + " / " + c[1] + " / " + c[2]));
for(int i = 0; i < 3; i++)
{
totals[i] += c[i];
}
}
rows.add(row("Partner", "Total", "Document", "", "In / out / rejected", totals[0] + " / " + totals[1] + " / " + totals[2]));
return (table(List.of("Partner", "Document", "In / out / rejected"), rows, "edi_transaction", filter, "View documents"));
}
/*******************************************************************************
** Integration and queue state as one compact two-column table.
*******************************************************************************/
private static RenderWidgetOutput system(Instant now) throws QException
{
boolean secured = com.kingsrook.qqq.backend.core.context.QContext.getQInstance().getQBits().values().stream().anyMatch(q -> "qbit-secured-data".equals(q.getArtifactId()));
String store = EdiRuntime.current().config().getPayloadStore();
List<QRecord> oldestIn = query("edi_inbox", inboxBacklog().withOrderBy(new QFilterOrderBy("receivedAt", true)).withLimit(1));
List<QRecord> oldestOut = query("edi_outbound_message", outboundQueue().withOrderBy(new QFilterOrderBy("createDate", true)).withLimit(1));
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", "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())
+ (oldestOut.isEmpty() ? "" : ", oldest " + humanize(Duration.between(oldestOut.getFirst().getValueInstant("createDate"), now)))));
return (table(List.of("Item", "Status"), rows, "edi_outbound_message", outboundQueue(), "View outbound queue"));
}
/*******************************************************************************
** Inbox rows not yet processed.
*******************************************************************************/
private static QQueryFilter inboxBacklog()
{
return (new QQueryFilter().withCriteria("status", QCriteriaOperator.IN, "PENDING", "PROCESSING", "RETRY_WAIT"));
}
/*******************************************************************************
** Outbound messages waiting to send.
*******************************************************************************/
private static QQueryFilter outboundQueue()
{
return (new QQueryFilter().withCriteria("status", QCriteriaOperator.IN, "QUEUED", "RETRY_WAIT"));
}
/*******************************************************************************
** Partner names readable by the viewing user.
*******************************************************************************/
private static Map<Integer, String> partnerNames() throws QException
{
Map<Integer, String> names = new HashMap<>();
for(QRecord partner : query("edi_partner", new QQueryFilter()))
{
names.put(partner.getValueInteger("id"), partner.getValueString("name"));
}
return (names);
}
/*******************************************************************************
** A partner's name, or a plain placeholder.
*******************************************************************************/
private static String partnerName(Map<Integer, String> names, Integer id)
{
return (id == null ? "Unrouted" : names.getOrDefault(id, "Partner " + id));
}
/*******************************************************************************
** A transaction set with its plain name.
*******************************************************************************/
static String documentName(String transactionSet)
{
String name = DOCUMENT_NAMES.get(transactionSet);
return (name == null ? String.valueOf(transactionSet) : transactionSet + " " + name);
}
/*******************************************************************************
** UPPER_SNAKE enum value as a sentence-case label.
*******************************************************************************/
static String plain(String value)
{
if(value == null || value.isEmpty())
{
return ("");
}
String lower = value.toLowerCase().replace('_', ' ');
return (Character.toUpperCase(lower.charAt(0)) + lower.substring(1));
}
/*******************************************************************************
** Compact human duration: 45 s, 12 m, 3 h 5 m, 2 d 4 h.
*******************************************************************************/
static String humanize(Duration duration)
{
long seconds = Math.max(0L, duration.getSeconds());
if(seconds < 60)
{
return (seconds + " s");
}
long minutes = seconds / 60;
if(minutes < 60)
{
return (minutes + " m");
}
long hours = minutes / 60;
if(hours < 24)
{
return (hours + " h" + (minutes % 60 == 0 ? "" : " " + (minutes % 60) + " m"));
}
return (hours / 24 + " d" + (hours % 24 == 0 ? "" : " " + (hours % 24) + " h"));
}
/*******************************************************************************
** An ordered row from alternating keys and values.
*******************************************************************************/
private static Map<String, Object> row(Object... keysAndValues)
{
Map<String, Object> row = new LinkedHashMap<>();
for(int i = 0; i < keysAndValues.length; i += 2)
{
row.put(String.valueOf(keysAndValues[i]), keysAndValues[i + 1]);
}
return (row);
}
/*******************************************************************************
** A typed table with a link to the filtered table.
*******************************************************************************/
private static RenderWidgetOutput table(List<String> columns, List<Map<String, Object>> rows, String linkTable, QQueryFilter linkFilter, String linkText) throws QException
{
return (new RenderWidgetOutput(tableData(columns, rows).withLinkURL(link(linkTable, linkFilter)).withLinkText(linkText)));
}
/*******************************************************************************
** Plain cells allow typed rendering and never contain HTML.
*******************************************************************************/
private static TableData tableData(List<String> columns, List<Map<String, Object>> rows)
{
return (new TableData(null, columns.stream().map(c -> new TableData.Column("text", c, c, null, "left")).toList(), rows));
}
/*******************************************************************************
** Explicit permission enforcement is required inside widget renderers.
*******************************************************************************/
private static List<QRecord> query(String table, QQueryFilter filter) throws QException
{
QueryInput query = new QueryInput(table).withFilter(filter);
query.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
return (new QueryAction().execute(query).getRecords());
}
/*******************************************************************************
** Standard filtered-table URL used by statistic and table navigation.
*******************************************************************************/
private static String link(String table, QQueryFilter filter) throws QException
{
try
{
return ("/app/" + table + "?filter=" + URLEncoder.encode(new ObjectMapper().findAndRegisterModules().writeValueAsString(filter), StandardCharsets.UTF_8));
}
catch(Exception e)
{
throw new QException("Could not create EDI table link");
}
}
}
@@ -1,103 +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.temporal.ChronoUnit;
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.AlertSeverity;
import com.kingsrook.qqq.backend.core.alerts.AlertSignal;
import com.kingsrook.qqq.backend.core.alerts.QAlerts;
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.data.QRecord;
/*******************************************************************************
** Five-minute acknowledgement and public-certificate expiry monitor.
*******************************************************************************/
public class AckMonitor
{
/*******************************************************************************
** Deduplicate alerts and open exceptions without embedding document values.
*******************************************************************************/
public Integer runOnce() throws QException
{
return (SystemWrites.call(() ->
{
int count = 0;
var now = EdiRuntime.current().config().getClock().instant();
for(QRecord group : EdiRecords.query("edi_functional_group", EdiRecords.equal("ackStatus", "PENDING")
.withCriteria("ackDueAt", QCriteriaOperator.LESS_THAN, now).withCriteria("ackReceivedAt", QCriteriaOperator.IS_BLANK)))
{
QRecord interchange = OutboundWorker.row("edi_interchange", group.getValue("interchangeId"));
if(!"OUTBOUND".equals(interchange.getValueString("direction")))
{
continue;
}
String key = "edi_functional_group:" + group.getValue("id");
QAlerts.raise(new AlertSignal("edi.ack.overdue").withSeverity(AlertSeverity.WARNING).withDedupKey(key)
.withRecordRef(new com.kingsrook.qqq.backend.core.alerts.AlertRecordRef("edi_functional_group", group.getValue("id")))
.withTitle("EDI acknowledgement overdue").withSummary("Review the outbound functional group"));
if(EdiRecords.query("edi_exception", EdiRecords.equal("interchangeId", interchange.getValue("id")).withCriteria("type", QCriteriaOperator.EQUALS, "OVERDUE_ACK")
.withCriteria("status", QCriteriaOperator.IN, "OPEN", "IN_PROGRESS")).isEmpty())
{
EdiJournal.exception(null, interchange.getValueInteger("id"), null, interchange.getValueInteger("partnerId"), "OVERDUE_ACK", "Expected acknowledgement has not arrived");
}
count++;
}
for(QRecord certificate : EdiRecords.query("edi_certificate", EdiRecords.equal("status", "ACTIVE")))
{
if(certificate.getValueInstant("notAfter") == null)
{
continue;
}
long days = ChronoUnit.DAYS.between(now, certificate.getValueInstant("notAfter"));
if(!certificate.getValueInstant("notAfter").isAfter(now))
{
expiry(certificate, "edi.certificate.expired", "expired", AlertSeverity.ERROR);
}
else
{
for(int threshold : List.of(30, 14, 7))
{
if(days < threshold)
{
expiry(certificate, "edi.certificate.expiring", String.valueOf(threshold), AlertSeverity.WARNING);
}
}
}
}
return (count);
}));
}
/*******************************************************************************
** Each certificate threshold has its own stable deduplication key.
*******************************************************************************/
private static void expiry(QRecord certificate, String type, String threshold, AlertSeverity severity)
{
QAlerts.raise(new AlertSignal(type).withSeverity(severity).withDedupKey("edi_certificate:" + certificate.getValue("id") + ":" + threshold)
.withRecordRef(new com.kingsrook.qqq.backend.core.alerts.AlertRecordRef("edi_certificate", certificate.getValue("id")))
.withTitle("EDI certificate expiry").withSummary("Review the certificate validity dates"));
}
}
@@ -1,69 +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.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.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;
/*******************************************************************************
** Value-free operational timeline and coalesced exception work queue.
*******************************************************************************/
public final class EdiJournal
{
/*******************************************************************************
** Events contain only step names, record references and timestamps.
*******************************************************************************/
public static void event(Integer inbox, Integer interchange, Integer transaction, String type) throws Exception
{
SystemWrites.insert("edi_event", new QRecord().withValue("inboxId", inbox).withValue("interchangeId", interchange)
.withValue("transactionId", transaction).withValue("at", EdiRuntime.current().config().getClock().instant())
.withValue("type", type).withValue("detail", type).withValue("userName", "edi"));
}
/*******************************************************************************
** Repeated failures of the same item increment occurrences without exposing values.
*******************************************************************************/
public static void exception(Integer inbox, Integer interchange, Integer transaction, Integer partner, String type, String summary) throws Exception
{
QQueryFilter filter = EdiRecords.equal("type", type);
for(var pair : List.of(new Object[] {"inboxId", inbox}, new Object[] {"interchangeId", interchange}, new Object[] {"transactionId", transaction}))
{
filter.withCriteria(pair[0].toString(), pair[1] == null ? QCriteriaOperator.IS_BLANK : QCriteriaOperator.EQUALS, (Integer) pair[1]);
}
List<QRecord> matches = EdiRecords.query("edi_exception", filter);
if(matches.isEmpty())
{
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();
SystemWrites.update("edi_exception", new QRecord().withValue("id", row.getValue("id")).withValue("occurrences", row.getValueInteger("occurrences") + 1).withValue("status", "OPEN"));
}
event(inbox, interchange, transaction, "EXCEPTION");
}
}
@@ -1,38 +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.io.InputStream;
/*******************************************************************************
** Transport-neutral stream. The receiving transport owns and closes the stream.
*******************************************************************************/
public record InboundPayload(String transportType, String externalId, String fileName, String remoteAddress,
InputStream stream, String gatewayUserId, String username)
{
/*******************************************************************************
** Payload without gateway identity.
*******************************************************************************/
public InboundPayload(String transportType, String externalId, String fileName, String remoteAddress, InputStream stream)
{
this(transportType, externalId, fileName, remoteAddress, stream, null, null);
}
}
@@ -1,590 +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.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
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.AckEnvelope;
import com.kingsrook.qbits.edi.ack.AckLevel;
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.map.InboundApplier;
import com.kingsrook.qbits.edi.map.InboundMapper;
import com.kingsrook.qbits.edi.storage.EdiRecords;
import com.kingsrook.qbits.edi.storage.PayloadKind;
import com.kingsrook.qbits.edi.storage.PayloadRef;
import com.kingsrook.qbits.edi.storage.PayloadStore;
import com.kingsrook.qbits.edi.support.SystemWrites;
import com.kingsrook.qbits.edi.x12.X12FunctionalGroup;
import com.kingsrook.qbits.edi.x12.X12Interchange;
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
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.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Routing, validation, acknowledgements and service-session application checkpoints.
*******************************************************************************/
public class InboundProcessor
{
private final QRecord inbox;
private final Instant lease;
private final PayloadStore store;
private final EdiRuntime runtime = EdiRuntime.current();
/*******************************************************************************
** One processor per claimed payload; database checkpoints fence every side effect.
*******************************************************************************/
public InboundProcessor(QRecord inbox, Instant lease, PayloadStore store)
{
this.inbox = inbox;
this.lease = lease;
this.store = store;
}
/*******************************************************************************
** Route before mapping; unknown and mismatched identities never reach target writes.
*******************************************************************************/
public void process(X12Interchange x12, int position) throws Exception
{
QRecord agreement = route(x12);
QRecord interchange = InboxWorker.fenced(inbox, lease, () -> interchange(x12, agreement, position));
Integer id = interchange.getValueInteger("id");
if(agreement == null)
{
problem(id, null, null, "UNKNOWN_PARTNER", "No active agreement matches the interchange identities and usage");
return;
}
Integer partner = agreement.getValueInteger("partnerId");
if(inbox.getValueString("username") != null)
{
var linked = EdiRecords.query("edi_partner", EdiRecords.equal("gatewayUsername", inbox.getValue("username")));
if(linked.stream().anyMatch(p -> !partner.equals(p.getValueInteger("id"))))
{
problem(id, null, partner, "PARTNER_MISMATCH", "Gateway identity does not match the routed trading partner");
return;
}
}
String code = x12.findings().stream().filter(f -> "TA1".equals(f.level())).map(f -> f.code()).findFirst().orElse("000");
boolean duplicate = "DUPLICATE".equals(interchange.getValueString("status"));
if(duplicate)
{
code = "025";
problem(id, null, partner, "DUPLICATE", "Duplicate interchange control number");
}
boolean ackInput = x12.serviceSegments().stream().anyMatch(s -> "TA1".equals(s.id())) || x12.groups().stream().anyMatch(g -> !g.transactions().isEmpty()) && x12.groups().stream().flatMap(g -> g.transactions().stream()).allMatch(t -> List.of("997", "999").contains(t.st().value(1)));
String ta1Code = code;
String policy = agreement.getValueString("sendTa1");
boolean send = !ackInput && ("ALWAYS".equals(policy) || "ON_REQUEST".equals(policy) && "1".equals(x12.isa().value(14))
|| "ON_ERROR".equals(policy) && !"000".equals(code));
if(duplicate && "REJECT_TA1".equals(agreement.getValueString("onDuplicate")))
{
send = true;
}
if(send)
{
InboxWorker.fenced(inbox, lease, () ->
{
if(EdiRecords.query("edi_outbound_message", EdiRecords.equal("ackForInterchangeId", id)).isEmpty())
{
queue(runtime.ta1().interchange(x12, control(agreement, "ISA"), ta1Code, LocalDateTime.now(runtime.config().getClock())), agreement, id, null, "TA1");
}
return (null);
});
}
if(duplicate && !"PROCESS".equals(agreement.getValueString("onDuplicate")))
{
return;
}
if(!"000".equals(code) && !duplicate)
{
problem(id, null, partner, "ENVELOPE", "Interchange envelope failed validation");
return;
}
int groupPosition = 0;
for(X12FunctionalGroup group : x12.groups())
{
int currentGroupPosition = ++groupPosition;
QRecord groupRow = InboxWorker.fenced(inbox, lease, () -> group(group, interchange, agreement, currentGroupPosition));
Map<String, List<ValidationFinding>> findings = new LinkedHashMap<>();
QRecord ackDocument = null;
int transactionPosition = 0;
for(X12TransactionSet transaction : group.transactions())
{
QRecord document = document(agreement, transaction, group);
if(document != null && !"NONE".equals(document.getValueString("ackExpectation")))
{
ackDocument = document;
}
QRecord configuredDocument = document;
int currentTransactionPosition = ++transactionPosition;
QRecord tx = InboxWorker.fenced(inbox, lease, () -> transaction(transaction, groupRow, interchange, configuredDocument, currentTransactionPosition));
if(List.of("997", "999").contains(transaction.st().value(1)))
{
continue;
}
document = revisions(document, tx);
QRecord effectiveDocument = document;
List<ValidationFinding> result = validate(transaction, group, document, groupRow);
findings.put(transaction.st().value(2), result);
InboxWorker.fenced(inbox, lease, () ->
{
validation(tx, result, effectiveDocument);
return (null);
});
if(document == null)
{
problem(id, tx.getValueInteger("id"), partner, "NOT_CONFIGURED", "Inbound document has no active agreement configuration");
continue;
}
if(result.stream().anyMatch(f -> "ERROR".equals(f.severity()) && f.snipLevel() <= 2))
{
continue;
}
if("T".equals(x12.isa().value(15)) && !Boolean.TRUE.equals(agreement.getValueBoolean("applyTestDocuments")))
{
continue;
}
apply(transaction, tx, document, agreement);
}
if(!ackInput && ackDocument != null)
{
QRecord config = ackDocument;
InboxWorker.fenced(inbox, lease, () ->
{
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 = 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");
}
return (null);
});
}
}
if(ackInput)
{
InboxWorker.fenced(inbox, lease, () ->
{
if(x12.serviceSegments().stream().anyMatch(s -> "TA1".equals(s.id()))
&& EdiRecords.query("edi_transaction", EdiRecords.equal("interchangeId", id)).isEmpty())
{
SystemWrites.insert("edi_transaction", new QRecord().withValue("interchangeId", id).withValue("partnerId", partner)
.withValue("direction", "INBOUND").withValue("transactionSet", "TA1").withValue("controlNumber", x12.isa().value(13)).withValue("status", "RECEIVED"));
}
reconcile(x12, agreement, id);
return (null);
});
}
}
/*******************************************************************************
** Route on sender, receiver, qualifier, standard and test/production indicator.
*******************************************************************************/
private QRecord route(X12Interchange x12) throws Exception
{
for(QRecord agreement : EdiRecords.query("edi_agreement", EdiRecords.equal("isActive", true).withCriteria("standard", QCriteriaOperator.EQUALS, "X12")
.withCriteria("usageIndicator", QCriteriaOperator.EQUALS, x12.isa().value(15))))
{
QRecord sender = row("edi_identity", agreement.getValueInteger("partnerIdentityId"));
QRecord receiver = row("edi_identity", agreement.getValueInteger("localIdentityId"));
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")))
{
return (agreement);
}
}
return (null);
}
/*******************************************************************************
** Persist each interchange once; serialize duplicate checks on its agreement.
*******************************************************************************/
private QRecord interchange(X12Interchange x12, 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);
SystemWrites.update("edi_inbox", new QRecord().withValue("id", inbox.getValue("id")).withValue("partnerId", agreement.getValue("partnerId")));
}
return (existing);
}
boolean duplicate = false;
if(agreement != null)
{
SystemWrites.guard("UPDATE edi_agreement SET id=id WHERE id=?", agreement.getValue("id"));
duplicate = runtime.duplicates().interchange(agreement.getValueInteger("id"), x12.isa(), agreement.getValueInteger("duplicateWindowDays"), runtime.config().getClock().instant());
SystemWrites.update("edi_inbox", new QRecord().withValue("id", inbox.getValue("id")).withValue("partnerId", agreement.getValue("partnerId")));
}
QRecord row = SystemWrites.insert("edi_interchange", new QRecord().withValue("inboundPosition", position).withValue("inboxId", inbox.getValue("id")).withValue("direction", "INBOUND")
.withValue("agreementId", agreement == null ? null : agreement.getValue("id")).withValue("partnerId", agreement == null ? null : agreement.getValue("partnerId"))
.withValue("standard", "X12").withValue("senderQualifier", x12.isa().value(5)).withValue("senderId", x12.isa().value(6).trim())
.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("status", duplicate ? "DUPLICATE" : x12.findings().isEmpty() ? "ACCEPTED" : "REJECTED"));
EdiJournal.event(inbox.getValueInteger("id"), row.getValueInteger("id"), null, "PARSED");
return (row);
}
/*******************************************************************************
** Group checkpoints retain sender and control duplicate evidence.
*******************************************************************************/
private QRecord group(X12FunctionalGroup group, QRecord interchange, QRecord agreement, int position) throws Exception
{
QQueryFilter filter = EdiRecords.equal("interchangeId", interchange.getValue("id")).withCriteria("inboundPosition", QCriteriaOperator.EQUALS, position);
var prior = EdiRecords.query("edi_functional_group", filter);
if(!prior.isEmpty())
{
return (prior.getFirst());
}
boolean duplicate = runtime.duplicates().group(group.gs().value(2), group.gs().value(6), agreement.getValueInteger("duplicateWindowDays"), runtime.config().getClock().instant());
return (SystemWrites.insert("edi_functional_group", new QRecord().withValue("inboundPosition", position).withValue("interchangeId", interchange.getValue("id")).withValue("senderId", group.gs().value(2))
.withValue("functionalIdCode", group.gs().value(1)).withValue("controlNumber", group.gs().value(6)).withValue("version", group.gs().value(8))
.withValue("transactionCount", group.transactions().size()).withValue("ackStatus", duplicate && !"PROCESS".equals(agreement.getValueString("onDuplicate")) ? "R" : "NONE")));
}
/*******************************************************************************
** Select the inbound document; outbound ack expectation never controls our reply.
*******************************************************************************/
private QRecord document(QRecord agreement, X12TransactionSet tx, X12FunctionalGroup group) throws Exception
{
var rows = EdiRecords.query("edi_agreement_document", EdiRecords.equal("agreementId", agreement.getValue("id"))
.withCriteria("direction", QCriteriaOperator.EQUALS, "INBOUND").withCriteria("transactionSet", QCriteriaOperator.EQUALS, tx.st().value(1))
.withCriteria("version", QCriteriaOperator.EQUALS, group.gs().value(8)).withCriteria("isActive", QCriteriaOperator.EQUALS, true));
return (rows.isEmpty() ? null : rows.getFirst());
}
/*******************************************************************************
** Transaction checkpoints preserve guide and map references across worker retries.
*******************************************************************************/
private QRecord transaction(X12TransactionSet tx, QRecord group, QRecord interchange, QRecord document, int position) throws Exception
{
var rows = EdiRecords.query("edi_transaction", EdiRecords.equal("functionalGroupId", group.getValue("id")).withCriteria("inboundPosition", QCriteriaOperator.EQUALS, position));
if(!rows.isEmpty())
{
return (rows.getFirst());
}
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", document != null && Boolean.TRUE.equals(document.getValueBoolean("containsPhi")))
.withValue("status", "RECEIVED")));
}
/*******************************************************************************
** Replays select original persisted definitions or the currently active revision.
*******************************************************************************/
private QRecord revisions(QRecord document, QRecord transaction) throws Exception
{
if(document == null)
{
return (null);
}
QRecord effective = new QRecord();
effective.setValues(new java.util.HashMap<>(document.getValues()));
for(String kind : List.of("guide", "map"))
{
Integer originalId = transaction.getValueInteger(kind + "Id");
boolean current = Boolean.TRUE.equals(inbox.getValueBoolean("useCurrentRevisions"));
if(originalId != null && !current)
{
effective.withValue(kind + "Id", originalId);
}
else if(current)
{
QRecord referenced = row("edi_" + kind, document.getValueInteger(kind + "Id"));
if(referenced != null)
{
var active = EdiRecords.query("edi_" + kind, EdiRecords.equal("name", referenced.getValue("name")).withCriteria("status", QCriteriaOperator.EQUALS, "ACTIVE"));
active.stream().filter(r -> Objects.equals(r.getValue("partnerId"), referenced.getValue("partnerId"))).findFirst()
.ifPresent(r -> effective.withValue(kind + "Id", r.getValue("id")));
}
}
}
return (effective);
}
/*******************************************************************************
** Gather positional findings, including group and repeated transaction controls.
*******************************************************************************/
private List<ValidationFinding> validate(X12TransactionSet tx, X12FunctionalGroup group, QRecord document, QRecord groupRow) throws Exception
{
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"))));
int snip = Objects.requireNonNullElse(document.getValueInteger("snipLevel"), 2);
findings.removeIf(f -> f.snipLevel() > snip);
}
else
{
findings.add(finding("No implementation guide is configured", "3"));
}
if("R".equals(groupRow.getValueString("ackStatus")) || runtime.duplicates().transactions(group).contains(tx.st().value(2)))
{
findings.add(finding("Duplicate control number", "7"));
}
if(!group.findings().isEmpty() || !tx.findings().isEmpty())
{
findings.add(finding("Envelope counts or controls failed validation", "7"));
}
return (findings);
}
/*******************************************************************************
** Construct a value-free structural error.
*******************************************************************************/
private static ValidationFinding finding(String message, String code)
{
return (new ValidationFinding(1, "ST", null, null, null, 1, code, null, code, null, "ERROR", message, null));
}
/*******************************************************************************
** Persist findings without repeatedly adding error rows on storage retries.
*******************************************************************************/
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")))
{
QRecord guide = document == null ? null : row("edi_guide", document.getValueInteger("guideId"));
SystemWrites.update("edi_transaction", new QRecord().withValue("id", tx.getValue("id")).withValue("status", rejected ? "INVALID" : "VALID")
.withValue("ackStatus", rejected ? "R" : findings.isEmpty() ? "A" : "E").withValue("errorCount", (int) findings.stream().filter(f -> "ERROR".equals(f.severity())).count())
.withValue("warningCount", (int) findings.stream().filter(f -> !"ERROR".equals(f.severity())).count())
.withValue("guideId", guide == null ? null : guide.getValue("id")).withValue("guideRevision", guide == null ? null : guide.getValue("revision"))
.withValue("mapId", document == null ? null : document.getValue("mapId"))
.withValue("mapRevision", document == null || row("edi_map", document.getValueInteger("mapId")) == null ? null : row("edi_map", document.getValueInteger("mapId")).getValue("revision")));
}
if(Boolean.TRUE.equals(inbox.getValueBoolean("reprocess")))
{
new com.kingsrook.qqq.backend.core.actions.tables.DeleteAction().execute(new com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput("edi_transaction_error")
.withQueryFilter(EdiRecords.equal("transactionId", tx.getValue("id"))).withTransaction(com.kingsrook.qqq.backend.core.context.QContext.getQBackendTransaction()));
}
if(EdiRecords.query("edi_transaction_error", EdiRecords.equal("transactionId", tx.getValue("id"))).isEmpty())
{
for(ValidationFinding f : findings)
{
SystemWrites.insert("edi_transaction_error", new QRecord().withValue("transactionId", tx.getValue("id")).withValue("segmentPosition", f.segmentPosition())
.withValue("segmentId", f.segmentId()).withValue("loopId", f.loopId()).withValue("elementPosition", f.elementPosition()).withValue("componentPosition", f.componentPosition())
.withValue("snipLevel", f.snipLevel()).withValue("ackCode", f.ak4Code() == null ? f.ak3Code() : f.ak4Code()).withValue("severity", f.severity())
.withValue("message", f.message()).withValue("valueExcerpt", Boolean.TRUE.equals(tx.getValueBoolean("containsPhi")) ? null : f.valueExcerpt()));
}
}
EdiJournal.event(inbox.getValueInteger("id"), tx.getValueInteger("interchangeId"), tx.getValueInteger("id"), "VALIDATED");
if(!findings.isEmpty())
{
EdiJournal.exception(inbox.getValueInteger("id"), tx.getValueInteger("interchangeId"), tx.getValueInteger("id"), tx.getValueInteger("partnerId"), "VALIDATION", "Document validation has findings");
}
}
/*******************************************************************************
** A failed target write rolls back only this set, then records an APPLY exception.
*******************************************************************************/
private void apply(X12TransactionSet x12, QRecord tx, QRecord document, QRecord agreement) throws Exception
{
if("APPLIED".equals(tx.getValueString("status")) && !Boolean.TRUE.equals(inbox.getValueBoolean("reprocess")))
{
return;
}
QRecord map = row("edi_map", document.getValueInteger("mapId"));
if(map == null || !("ACTIVE".equals(map.getValueString("status")) || Boolean.TRUE.equals(inbox.getValueBoolean("reprocess")) && !Boolean.TRUE.equals(inbox.getValueBoolean("useCurrentRevisions"))))
{
return;
}
String[] stage = {"MAPPING"};
try
{
InboxWorker.fenced(inbox, lease, () ->
{
Guide guide = new GuideRepository().load(document.getValueInteger("guideId"));
List<QRecord> rules = EdiRecords.query("edi_map_rule", EdiRecords.equal("mapId", map.getValue("id")));
rules.sort(java.util.Comparator.comparing(r -> r.getValueInteger("sequence")));
if(runtime.config().getServiceSessionSupplier() == null)
{
stage[0] = "APPLY";
}
InboundMapper.Result mapped = InboundApplier.asPartner(runtime.config().getServiceSessionSupplier(), agreement.getValueInteger("partnerId"), () -> new InboundMapper(runtime.transforms()).map(x12, guide, map, rules));
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(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())
.withValue("targetTableName", map.getValue("tableName")).withValue("targetRecordId", target.getValueString(primaryKey)));
EdiJournal.event(inbox.getValueInteger("id"), tx.getValueInteger("interchangeId"), tx.getValueInteger("id"), "MAPPED");
EdiJournal.event(inbox.getValueInteger("id"), tx.getValueInteger("interchangeId"), tx.getValueInteger("id"), "APPLIED");
return (null);
});
}
catch(Exception e)
{
InboxWorker.fenced(inbox, lease, () ->
{
SystemWrites.update("edi_transaction", new QRecord().withValue("id", tx.getValue("id")).withValue("status", "EXCEPTION"));
EdiJournal.exception(inbox.getValueInteger("id"), tx.getValueInteger("interchangeId"), tx.getValueInteger("id"), agreement.getValueInteger("partnerId"), stage[0],
runtime.config().getServiceSessionSupplier() == null ? "no service session configured" : "Inbound " + stage[0].toLowerCase(java.util.Locale.ROOT) + " failed");
return (null);
});
}
}
/*******************************************************************************
** Allocate controls from the agreement or local identity's configured scope.
*******************************************************************************/
private String control(QRecord agreement, String level) throws Exception
{
boolean local = "LOCAL_IDENTITY".equals(agreement.getValueString("controlNumberScope"));
return (String.valueOf(runtime.controls().allocate(local ? null : agreement.getValueInteger("id"), local ? agreement.getValueInteger("localIdentityId") : null, level, 1, 999999999, true)));
}
/*******************************************************************************
** Persist an encrypted, fully enveloped acknowledgement; sending is phase b.
*******************************************************************************/
private void queue(X12Interchange ack, QRecord agreement, Integer receivedId, Integer groupId, String type) throws Exception
{
PayloadRef payload = store.put(PayloadKind.OUTBOUND, new ByteArrayInputStream(runtime.writer().write(ack).getBytes(StandardCharsets.US_ASCII)));
QRecord message = SystemWrites.insert("edi_outbound_message", new QRecord().withValue("agreementId", agreement.getValue("id")).withValue("partnerId", agreement.getValue("partnerId"))
.withValue("transportId", agreement.getValue("outboundTransportId")).withValue("payloadRef", payload.value()).withValue("payloadSha256", payload.sha256()).withValue("sizeBytes", payload.sizeBytes())
.withValue("status", "QUEUED").withValue("attempts", 0).withValue("ackForGroupId", groupId).withValue("ackForInterchangeId", groupId == null ? receivedId : null));
QRecord interchange = SystemWrites.insert("edi_interchange", new QRecord().withValue("outboundMessageId", message.getValue("id")).withValue("direction", "OUTBOUND")
.withValue("agreementId", agreement.getValue("id")).withValue("partnerId", agreement.getValue("partnerId")).withValue("controlNumber", ack.isa().value(13))
.withValue("senderQualifier", ack.isa().value(5)).withValue("senderId", ack.isa().value(6).trim()).withValue("receiverQualifier", ack.isa().value(7)).withValue("receiverId", ack.isa().value(8).trim())
.withValue("standard", "X12").withValue("interchangeVersion", ack.isa().value(12)).withValue("usageIndicator", ack.isa().value(15)).withValue("groupCount", ack.groups().size())
.withValue("payloadRef", payload.value()).withValue("payloadSha256", payload.sha256()).withValue("sizeBytes", payload.sizeBytes()).withValue("status", "ENVELOPED"));
SystemWrites.update("edi_outbound_message", new QRecord().withValue("id", message.getValue("id")).withValue("interchangeId", interchange.getValue("id")));
Integer outboundGroup = null;
if(!ack.groups().isEmpty())
{
var gs = ack.groups().getFirst().gs();
outboundGroup = SystemWrites.insert("edi_functional_group", new QRecord().withValue("interchangeId", interchange.getValue("id")).withValue("functionalIdCode", "FA")
.withValue("senderId", gs.value(2)).withValue("controlNumber", gs.value(6)).withValue("version", gs.value(8)).withValue("transactionCount", 1).withValue("ackStatus", "NONE")).getValueInteger("id");
}
SystemWrites.insert("edi_transaction", new QRecord().withValue("interchangeId", interchange.getValue("id")).withValue("functionalGroupId", outboundGroup)
.withValue("direction", "OUTBOUND").withValue("partnerId", agreement.getValue("partnerId")).withValue("transactionSet", type).withValue("status", "QUEUED")
.withValue("controlNumber", outboundGroup == null ? ack.isa().value(13) : "0001").withValue("version", ack.groups().isEmpty() ? ack.isa().value(12) : ack.groups().getFirst().gs().value(8)));
EdiJournal.event(inbox.getValueInteger("id"), receivedId, null, "ACK_QUEUED");
}
/*******************************************************************************
** Reconcile only outbound controls on this agreement; acknowledgements get no ack.
*******************************************************************************/
private void reconcile(X12Interchange x12, QRecord agreement, Integer received) throws Exception
{
for(var ack : runtime.acknowledgements().read(x12))
{
var receivedAcks = EdiRecords.query("edi_transaction", EdiRecords.equal("interchangeId", received).withCriteria("transactionSet", QCriteriaOperator.EQUALS, ack.type()));
Integer ackTransactionId = receivedAcks.isEmpty() ? null : receivedAcks.getFirst().getValueInteger("id");
if(ackTransactionId != null)
{
SystemWrites.update("edi_transaction", new QRecord().withValue("id", ackTransactionId).withValue("status", "ACKNOWLEDGED").withValue("ackStatus", ack.status()));
}
for(QRecord interchange : EdiRecords.query("edi_interchange", EdiRecords.equal("agreementId", agreement.getValue("id")).withCriteria("direction", QCriteriaOperator.EQUALS, "OUTBOUND")))
{
boolean ta1 = "TA1".equals(ack.type());
if(ta1 && !ack.controlNumber().equals(interchange.getValueString("controlNumber")))
{
continue;
}
if(ta1)
{
SystemWrites.update("edi_interchange", new QRecord().withValue("id", interchange.getValue("id")).withValue("ta1Status", ack.status()));
continue;
}
for(QRecord group : EdiRecords.query("edi_functional_group", EdiRecords.equal("interchangeId", interchange.getValue("id"))))
{
if(!ta1 && !ack.controlNumber().equals(group.getValueString("controlNumber")))
{
continue;
}
SystemWrites.update("edi_functional_group", new QRecord().withValue("id", group.getValue("id")).withValue("ackStatus", ack.status()).withValue("ackReceivedAt", runtime.config().getClock().instant()).withValue("ackTransactionId", ackTransactionId));
for(QRecord transaction : EdiRecords.query("edi_transaction", EdiRecords.equal("functionalGroupId", group.getValue("id"))))
{
String status = ack.transactionStatuses().getOrDefault(transaction.getValueString("controlNumber"), ack.status());
SystemWrites.update("edi_transaction", new QRecord().withValue("id", transaction.getValue("id")).withValue("ackStatus", status)
.withValue("status", "R".equals(status) ? "REJECTED" : "ACKNOWLEDGED"));
}
QAlerts.resolve("edi.ack.overdue", "edi_functional_group:" + group.getValue("id"), "Acknowledgement received");
for(QRecord 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", runtime.config().getClock().instant()).withValue("resolution", "Acknowledgement received"));
}
if(List.of("R", "P").contains(ack.status()))
{
EdiJournal.exception(inbox.getValueInteger("id"), received, null, agreement.getValueInteger("partnerId"), "REJECTED_ACK", "Partner rejected an outbound group");
QAlerts.raise(new AlertSignal("edi.ack.rejected").withSeverity(AlertSeverity.ERROR).withDedupKey(group.getValueString("id"))
.withTitle("EDI acknowledgement rejected").withSummary("Review the outbound functional group"));
}
}
}
}
EdiJournal.event(inbox.getValueInteger("id"), received, null, "ACK_RECEIVED");
}
/*******************************************************************************
** Persist safe failures with the current claim held.
*******************************************************************************/
private void problem(Integer interchange, Integer transaction, Integer partner, String type, String summary) throws Exception
{
InboxWorker.fenced(inbox, lease, () ->
{
EdiJournal.exception(inbox.getValueInteger("id"), interchange, transaction, partner, type, summary);
return (null);
});
}
/*******************************************************************************
** Optional foreign-key lookup.
*******************************************************************************/
private static QRecord row(String table, Integer id) throws Exception
{
if(id == null)
{
return (null);
}
var rows = EdiRecords.query(table, EdiRecords.equal("id", id));
return (rows.isEmpty() ? null : rows.getFirst());
}
}
@@ -1,97 +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.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import com.kingsrook.qbits.edi.EdiRuntime;
import com.kingsrook.qbits.edi.storage.EdiRecords;
import com.kingsrook.qbits.edi.storage.PayloadKind;
import com.kingsrook.qbits.edi.storage.PayloadRef;
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.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Store first, then acknowledge durable intake. Never parse in the receive path.
*******************************************************************************/
public class InboundSink
{
private final PayloadStore store;
/*******************************************************************************
** Configure the storage boundary.
*******************************************************************************/
public InboundSink(PayloadStore store)
{
this.store = store;
}
/*******************************************************************************
** Unique external ids survive retries; plaintext digests catch byte duplicates.
*******************************************************************************/
public QRecord receive(InboundPayload payload) throws Exception
{
if(payload.transportType() == null || payload.externalId() == null || payload.externalId().isBlank())
{
throw new IllegalArgumentException("Transport and external id are required");
}
return (SystemWrites.call(() ->
{
QQueryFilter key = EdiRecords.equal("transportType", payload.transportType()).withCriteria("externalId", QCriteriaOperator.EQUALS, payload.externalId());
List<QRecord> existing = EdiRecords.query("edi_inbox", key);
if(!existing.isEmpty())
{
return (existing.getFirst());
}
try
{
return (SystemWrites.transaction(() ->
{
PayloadRef ref = store.put(PayloadKind.INBOUND, payload.stream());
Instant now = EdiRuntime.current().config().getClock().instant();
boolean duplicate = !EdiRecords.query("edi_inbox", EdiRecords.equal("payloadSha256", ref.sha256())
.withCriteria("receivedAt", QCriteriaOperator.GREATER_THAN_OR_EQUALS, now.minus(365, ChronoUnit.DAYS))).isEmpty();
QRecord row = SystemWrites.insert("edi_inbox", new QRecord().withValue("transportType", payload.transportType()).withValue("externalId", payload.externalId())
.withValue("fileName", payload.fileName()).withValue("remoteAddress", payload.remoteAddress()).withValue("gatewayUserId", payload.gatewayUserId())
.withValue("username", payload.username()).withValue("payloadRef", ref.value()).withValue("payloadSha256", ref.sha256()).withValue("sizeBytes", ref.sizeBytes())
.withValue("receivedAt", now).withValue("status", duplicate ? "DUPLICATE" : "PENDING").withValue("attempts", 0).withValue("nextAttemptAt", now));
EdiJournal.event(row.getValueInteger("id"), null, null, "RECEIVED");
EdiJournal.event(row.getValueInteger("id"), null, null, "STORED");
return (row);
}));
}
catch(Exception e)
{
// A competing receiver may have committed the unique external id.
existing = EdiRecords.query("edi_inbox", key);
if(!existing.isEmpty())
{
return (existing.getFirst());
}
throw e;
}
}));
}
}
@@ -1,176 +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.io.InputStream;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
import com.kingsrook.qbits.edi.EdiRuntime;
import com.kingsrook.qbits.edi.storage.EdiRecords;
import com.kingsrook.qbits.edi.storage.PayloadRef;
import com.kingsrook.qbits.edi.storage.PayloadStore;
import com.kingsrook.qbits.edi.support.SystemWrites;
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.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Bounded durable worker with database leases and independently committed sets.
*******************************************************************************/
public class InboxWorker
{
public static final String DEAD_LETTER_ALERT = "edi.inbox.dead-letter";
private static final List<Long> BACKOFF = List.of(60L, 300L, 900L, 3600L, 21600L);
private final PayloadStore store;
/*******************************************************************************
** Default runtime storage.
*******************************************************************************/
public InboxWorker()
{
this(EdiRuntime.current().payloads());
}
/*******************************************************************************
** Injectable storage boundary for transport/storage failure tests.
*******************************************************************************/
public InboxWorker(PayloadStore store)
{
this.store = store;
}
/*******************************************************************************
** Process at most twenty due messages as EDI's internal writer.
*******************************************************************************/
public Integer runOnce() throws QException
{
return (SystemWrites.call(() ->
{
Instant now = now();
QQueryFilter due = EdiRecords.equal("status", "RETRY_WAIT")
.withSubFilter(new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria("nextAttemptAt", QCriteriaOperator.IS_BLANK).withCriteria("nextAttemptAt", QCriteriaOperator.LESS_THAN_OR_EQUALS, now));
QQueryFilter stale = EdiRecords.equal("status", "PROCESSING").withCriteria("claimedAt", QCriteriaOperator.LESS_THAN, now.minusSeconds(300));
List<QRecord> rows = EdiRecords.query("edi_inbox", new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR).withSubFilter(due).withSubFilter(EdiRecords.equal("status", "PENDING")).withSubFilter(stale)
.withOrderBy(new QFilterOrderBy("receivedAt")).withLimit(20));
int completed = 0;
for(QRecord row : rows)
{
Instant lease = now().truncatedTo(ChronoUnit.MILLIS);
if(!claim(row, lease))
{
continue;
}
try(InputStream stream = store.open(new PayloadRef(row.getValueString("payloadRef"), row.getValueString("payloadSha256"), row.getValueLong("sizeBytes"))))
{
var interchanges = EdiRuntime.current().reader().read(stream);
int position = 0;
for(var interchange : interchanges)
{
new InboundProcessor(row, lease, store).process(interchange, ++position);
}
fenced(row, lease, () ->
{
SystemWrites.update("edi_inbox", new QRecord().withValue("id", row.getValue("id")).withValue("status", "PROCESSED")
.withValue("processedAt", now()).withValue("nextAttemptAt", null).withValue("lastError", null).withValue("reprocess", false));
return (null);
});
completed++;
}
catch(Exception e)
{
fail(row, lease);
}
}
return (completed);
}));
}
/*******************************************************************************
** Compare selected status and lease before claiming; only the winner proceeds.
*******************************************************************************/
private boolean claim(QRecord row, Instant lease) throws QException
{
return (SystemWrites.transaction(() ->
{
boolean won = row.getValue("claimedAt") == null
? SystemWrites.guard("UPDATE edi_inbox SET id=id WHERE id=? AND status=? AND claimed_at IS NULL", row.getValue("id"), row.getValue("status"))
: SystemWrites.guard("UPDATE edi_inbox SET id=id WHERE id=? AND status=? AND claimed_at=?", row.getValue("id"), row.getValue("status"), row.getValueInstant("claimedAt"));
if(won)
{
SystemWrites.update("edi_inbox", new QRecord().withValue("id", row.getValue("id")).withValue("status", "PROCESSING").withValue("claimedAt", lease));
}
return (won);
}));
}
/*******************************************************************************
** Every checkpoint holds the inbox lock and verifies ownership until commit.
*******************************************************************************/
public static <T> T fenced(QRecord inbox, Instant lease, SystemWrites.Work<T> work) throws QException
{
return (SystemWrites.transaction(() ->
{
if(!SystemWrites.guard("UPDATE edi_inbox SET id=id WHERE id=? AND status='PROCESSING' AND claimed_at=?", inbox.getValue("id"), lease))
{
throw new QException("Inbox claim was lost");
}
return (work.run());
}));
}
/*******************************************************************************
** Persist retry outcome separately from failed set writes, with no exception text.
*******************************************************************************/
private void fail(QRecord row, Instant lease) throws QException
{
fenced(row, lease, () ->
{
int attempts = Objects.requireNonNullElse(row.getValueInteger("attempts"), 0) + 1;
boolean dead = attempts > BACKOFF.size();
SystemWrites.update("edi_inbox", new QRecord().withValue("id", row.getValue("id")).withValue("attempts", attempts)
.withValue("status", dead ? "DEAD_LETTER" : "RETRY_WAIT").withValue("nextAttemptAt", dead ? null : now().plusSeconds(BACKOFF.get(attempts - 1)))
.withValue("lastError", "Inbound processing failed; retry the stored payload"));
if(dead)
{
EdiJournal.exception(row.getValueInteger("id"), null, null, row.getValueInteger("partnerId"), "DEAD_LETTER", "Inbound processing exhausted retries");
QAlerts.raise(new AlertSignal(DEAD_LETTER_ALERT).withSeverity(AlertSeverity.ERROR).withDedupKey(row.getValueString("id"))
.withTitle("EDI inbox exhausted retries").withSummary("Review the dead-letter inbox record"));
}
return (null);
});
}
/*******************************************************************************
** Host clock makes retry and stale-claim behavior deterministic.
*******************************************************************************/
private static Instant now()
{
return (EdiRuntime.current().config().getClock().instant());
}
}
@@ -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.flow;
import java.util.List;
import com.kingsrook.qbits.edi.EdiRuntime;
import com.kingsrook.qbits.edi.storage.EdiRecords;
import com.kingsrook.qbits.edi.storage.PayloadRef;
import com.kingsrook.qbits.edi.support.SystemWrites;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Guarded resend/cancel operations used after the standard Review step.
*******************************************************************************/
public final class OutboundActions
{
/*******************************************************************************
** Call within SystemWrites.transaction so validation and mutation share a lock.
*******************************************************************************/
public static void execute(String table, QRecord selected, String operation) throws Exception
{
QRecord message = table.equals("edi_interchange") ? OutboundWorker.row("edi_outbound_message", selected.getValue("outboundMessageId")) : selected;
if(!SystemWrites.guard("UPDATE edi_outbound_message SET id=id WHERE id=? AND status=?", message.getValue("id"), message.getValue("status")))
{
throw new IllegalStateException("Outbound message changed; refresh the record");
}
message = OutboundWorker.row("edi_outbound_message", message.getValue("id"));
if(operation.equals("cancel"))
{
if(!List.of("QUEUED", "RETRY_WAIT").contains(message.getValueString("status")))
{
throw new IllegalStateException("Only queued or retrying messages can be cancelled");
}
SystemWrites.update("edi_outbound_message", new QRecord().withValue("id", message.getValue("id")).withValue("status", "CANCELLED").withValue("nextAttemptAt", null));
}
else if(operation.equals("resend"))
{
SystemWrites.update("edi_outbound_message", new QRecord().withValue("id", message.getValue("id")).withValue("status", "QUEUED")
.withValue("attempts", 0).withValue("nextAttemptAt", null).withValue("lastError", null));
}
else if(operation.equals("new"))
{
reEnvelope(message);
}
else
{
throw new IllegalArgumentException("Unknown outbound operation");
}
EdiJournal.event(null, message.getValueInteger("interchangeId"), null, operation.toUpperCase(java.util.Locale.ROOT));
}
/*******************************************************************************
** Re-envelope the archived payload and tracking snapshots, including TA1/997/999.
** Maps are not re-run: source edits require a new outbound request instead.
*******************************************************************************/
private static void reEnvelope(QRecord message) throws Exception
{
var runtime = EdiRuntime.current();
QRecord originalRow = OutboundWorker.row("edi_interchange", message.getValue("interchangeId"));
QRecord agreement = OutboundWorker.row("edi_agreement", message.getValue("agreementId"));
boolean local = "LOCAL_IDENTITY".equals(agreement.getValueString("controlNumberScope"));
String control = String.format("%09d", runtime.controls().allocate(local ? null : agreement.getValueInteger("id"), local ? agreement.getValueInteger("localIdentityId") : null, "ISA", 1, 999999999, true));
com.kingsrook.qbits.edi.x12.X12Interchange original;
try(var stream = runtime.payloads().open(new PayloadRef(message.getValueString("payloadRef"), message.getValueString("payloadSha256"), message.getValueLong("sizeBytes"))))
{
original = runtime.reader().read(stream).getFirst();
}
var groups = new java.util.ArrayList<com.kingsrook.qbits.edi.x12.X12FunctionalGroup>();
for(var group : original.groups())
{
String groupControl = String.valueOf(runtime.controls().allocate(local ? null : agreement.getValueInteger("id"), local ? agreement.getValueInteger("localIdentityId") : null, "GS", 1, 999999999, true));
groups.add(new com.kingsrook.qbits.edi.x12.X12FunctionalGroup(replace(group.gs(), 6, groupControl), replace(group.ge(), 2, groupControl), group.transactions(), List.of()));
}
var rebuilt = new com.kingsrook.qbits.edi.x12.X12Interchange(original.separators(), replace(original.isa(), 13, control), replace(original.iea(), 2, control), groups, original.serviceSegments(), List.of());
var payload = runtime.payloads().put(com.kingsrook.qbits.edi.storage.PayloadKind.OUTBOUND,
new java.io.ByteArrayInputStream(runtime.writer().write(rebuilt).getBytes(java.nio.charset.StandardCharsets.UTF_8)));
QRecord newInterchange = copy(originalRow).withValue("originalInterchangeId", originalRow.getValue("id")).withValue("controlNumber", control)
.withValue("status", "ENVELOPED").withValue("ta1Status", null).withValue("outboundMessageId", null).withValue("payloadRef", payload.value()).withValue("payloadSha256", payload.sha256()).withValue("sizeBytes", payload.sizeBytes());
newInterchange = SystemWrites.insert("edi_interchange", newInterchange);
var groupIds = new java.util.HashMap<Integer, Integer>();
for(QRecord oldGroup : EdiRecords.query("edi_functional_group", EdiRecords.equal("interchangeId", originalRow.getValue("id"))))
{
int index = -1;
for(int i = 0; i < original.groups().size(); i++)
{
if(original.groups().get(i).gs().value(6).equals(oldGroup.getValueString("controlNumber")))
{
index = i;
break;
}
}
if(index < 0)
{
throw new IllegalStateException("Stored group does not match archived envelope");
}
QRecord newGroup = SystemWrites.insert("edi_functional_group", copy(oldGroup).withValue("interchangeId", newInterchange.getValue("id"))
.withValue("controlNumber", groups.get(index).gs().value(6)).withValue("ackDueAt", null).withValue("ackReceivedAt", null).withValue("ackTransactionId", null)
.withValue("ackStatus", "NONE".equals(oldGroup.getValueString("ackStatus")) ? "NONE" : "PENDING"));
groupIds.put(oldGroup.getValueInteger("id"), newGroup.getValueInteger("id"));
}
for(QRecord transaction : EdiRecords.query("edi_transaction", EdiRecords.equal("interchangeId", originalRow.getValue("id"))))
{
SystemWrites.insert("edi_transaction", copy(transaction).withValue("interchangeId", newInterchange.getValue("id"))
.withValue("functionalGroupId", groupIds.get(transaction.getValueInteger("functionalGroupId"))).withValue("status", "QUEUED").withValue("ackStatus", null));
}
QRecord newMessage = SystemWrites.insert("edi_outbound_message", copy(message).withValue("interchangeId", newInterchange.getValue("id"))
.withValue("payloadRef", payload.value()).withValue("payloadSha256", payload.sha256()).withValue("sizeBytes", payload.sizeBytes())
.withValue("status", "QUEUED").withValue("attempts", 0).withValue("nextAttemptAt", null).withValue("sentAt", null).withValue("deliveredAt", null).withValue("claimedAt", null)
.withValue("transportMessageId", null).withValue("lastError", null).withValue("ackForGroupId", null).withValue("ackForInterchangeId", null));
SystemWrites.update("edi_interchange", new QRecord().withValue("id", newInterchange.getValue("id")).withValue("outboundMessageId", newMessage.getValue("id")));
}
/*******************************************************************************
** Preserve components and repetitions while replacing one envelope element.
*******************************************************************************/
private static com.kingsrook.qbits.edi.x12.X12Segment replace(com.kingsrook.qbits.edi.x12.X12Segment segment, int position, String value)
{
var elements = new java.util.ArrayList<>(segment.elements());
elements.set(position - 1, List.of(List.of(value)));
return (new com.kingsrook.qbits.edi.x12.X12Segment(segment.id(), segment.position(), elements));
}
/*******************************************************************************
** Clone business metadata without identity and audit timestamps.
*******************************************************************************/
private static QRecord copy(QRecord source)
{
QRecord copy = new QRecord();
copy.getValues().putAll(source.getValues());
for(String field : List.of("id", "createDate", "modifyDate"))
{
copy.getValues().remove(field);
}
return (copy);
}
}
@@ -1,116 +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.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
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.actions.tables.listeners.RecordChangeEvent;
import com.kingsrook.qqq.backend.core.actions.tables.listeners.RecordChangeListenerInterface;
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.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;
/*******************************************************************************
** Transactional source listener and explicit enqueue boundary for MANUAL/ESB.
*******************************************************************************/
public class OutboundEnqueue implements RecordChangeListenerInterface
{
/*******************************************************************************
** Ignore internal tables and deletes; definitions are read at event time.
*******************************************************************************/
public boolean appliesTo(String table, RecordChangeType type)
{
return (!table.startsWith("edi_") && type != RecordChangeType.DELETE);
}
/*******************************************************************************
** Use the event transaction, even when the caller did not put it in QContext.
*******************************************************************************/
public void onRecordsChanged(RecordChangeEvent event) throws QException
{
var previous = QContext.getQBackendTransaction();
QContext.setTransaction(event.getTransaction());
try
{
for(QRecord record : event.getRecords())
{
enqueue(event.getTableName(), record, "RECORD_CHANGE", null);
}
}
finally
{
QContext.setTransaction(previous);
}
}
/*******************************************************************************
** Enqueue matching active outbound definitions using the writer's transaction.
*******************************************************************************/
public static Integer enqueue(String table, QRecord source, String trigger, String eventId) throws QException
{
return (SystemWrites.call(() ->
{
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)
{
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++;
}
return (count);
}));
}
}
@@ -1,260 +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.io.ByteArrayInputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.kingsrook.qbits.edi.EdiRuntime;
import com.kingsrook.qbits.edi.guide.GuideRepository;
import com.kingsrook.qbits.edi.map.InboundApplier;
import com.kingsrook.qbits.edi.map.OutboundMapper;
import com.kingsrook.qbits.edi.storage.EdiRecords;
import com.kingsrook.qbits.edi.storage.PayloadKind;
import com.kingsrook.qbits.edi.support.SystemWrites;
import com.kingsrook.qbits.edi.x12.X12FunctionalGroup;
import com.kingsrook.qbits.edi.x12.X12Interchange;
import com.kingsrook.qbits.edi.x12.X12Segment;
import com.kingsrook.qbits.edi.x12.X12Separators;
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
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.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Transactional map/validate/envelope worker. Row locks fence replicas; a crash
** rolls the whole envelope back, leaving requests available for the next run.
*******************************************************************************/
public class OutboundWorker
{
/*******************************************************************************
** Process immediate definitions; batch processes call runDocument explicitly.
*******************************************************************************/
public Integer runOnce() throws QException
{
return (SystemWrites.call(() ->
{
int count = 0;
for(QRecord document : EdiRecords.query("edi_agreement_document", EdiRecords.equal("direction", "OUTBOUND").withCriteria("isActive", QCriteriaOperator.EQUALS, true)))
{
if(!"BATCH".equals(document.getValueString("batchMode")))
{
count += runDocument(document.getValueInteger("id"));
}
}
return (count);
}));
}
/*******************************************************************************
** A scheduled batch invocation cuts one bounded batch for its definition.
*******************************************************************************/
public Integer runDocument(Integer id) throws QException
{
return (SystemWrites.call(() -> SystemWrites.transaction(() ->
{
QRecord document = row("edi_agreement_document", id);
QRecord agreement = row("edi_agreement", document.getValueInteger("agreementId"));
if(!Boolean.TRUE.equals(document.getValueBoolean("isActive")) || !Boolean.TRUE.equals(agreement.getValueBoolean("isActive")))
{
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<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>();
for(QRecord rule : rules)
{
String path = rule.getValueString("qqqPath");
if(path != null)
{
for(int dot = path.indexOf('.'); dot >= 0; dot = path.indexOf('.', dot + 1))
{
associations.add(path.substring(0, dot));
}
}
}
var guide = new GuideRepository().load(document.getValueInteger("guideId"));
for(QRecord request : requests)
{
if(!SystemWrites.guard("UPDATE edi_outbound_request SET id=id WHERE id=? AND status='PENDING'", request.getValue("id")))
{
continue;
}
try
{
X12TransactionSet set = InboundApplier.asPartner(EdiRuntime.current().config().getServiceSessionSupplier(), agreement.getValueInteger("partnerId"), () ->
{
String table = request.getValueString("sourceTableName");
String primaryKey = QContext.getQInstance().getTable(table).getPrimaryKeyField();
var query = new QueryInput(table).withFilter(EdiRecords.equal(primaryKey, request.getValue("sourceRecordId")))
.withIncludeAssociations(true).withAssociationNamesToInclude(associations).withTransaction(QContext.getQBackendTransaction());
query.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
var records = new QueryAction().execute(query).getRecords();
if(records.size() != 1 || (!"ACTIVE".equals(map.getValueString("status")) || !table.equals(map.getValueString("tableName"))))
{
throw new IllegalArgumentException("Source record or active map unavailable");
}
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())))
{
reject(request, agreement, "VALIDATION");
continue;
}
accepted.add(request);
sets.add(set);
}
catch(Exception e)
{
reject(request, agreement, "MAPPING");
}
if(!"BATCH".equals(document.getValueString("batchMode")) && !sets.isEmpty())
{
envelope(agreement, document, map, accepted, sets, null);
accepted.clear();
sets.clear();
}
}
if(!sets.isEmpty())
{
envelope(agreement, document, map, accepted, sets, null);
}
return (requests.size());
})));
}
/*******************************************************************************
** Invalid documents are terminal requests and never enter the send queue.
*******************************************************************************/
private void reject(QRecord request, QRecord agreement, String type) throws Exception
{
SystemWrites.update("edi_outbound_request", new QRecord().withValue("id", request.getValue("id")).withValue("status", "FAILED").withValue("lastError", type));
EdiJournal.exception(null, null, null, agreement.getValueInteger("partnerId"), type, "Outbound request failed " + type.toLowerCase(java.util.Locale.ROOT));
}
/*******************************************************************************
** Allocate controls once, store exact bytes, then persist all tracking records.
*******************************************************************************/
public static QRecord envelope(QRecord agreement, QRecord document, QRecord map, List<QRecord> requests, List<X12TransactionSet> sets, Integer original) throws Exception
{
var runtime = EdiRuntime.current();
QRecord local = row("edi_identity", agreement.getValueInteger("localIdentityId"));
QRecord partner = row("edi_identity", agreement.getValueInteger("partnerIdentityId"));
LocalDateTime at = LocalDateTime.ofInstant(runtime.config().getClock().instant(), ZoneOffset.UTC);
String isaControl = String.format("%09d", allocate(agreement, "ISA"));
String gsControl = String.valueOf(allocate(agreement, "GS"));
String version = Objects.requireNonNullElse(agreement.getValueString("interchangeVersion"), "00401");
String component = value(agreement, "componentSeparator", ":");
String repetition = value(agreement, "repetitionSeparator", "^");
X12Segment isa = X12Segment.of("ISA", 1, "00", "", "00", "", local.getValueString("interchangeQualifier"), local.getValueString("interchangeId"),
partner.getValueString("interchangeQualifier"), partner.getValueString("interchangeId"), at.format(DateTimeFormatter.ofPattern("yyMMdd")), at.format(DateTimeFormatter.ofPattern("HHmm")),
version.compareTo("00501") >= 0 ? repetition : "U", version, isaControl, Boolean.TRUE.equals(agreement.getValueBoolean("requestTa1")) ? "1" : "0", agreement.getValueString("usageIndicator"), component);
X12Segment gs = X12Segment.of("GS", 2, document.getValueString("functionalIdCode"), local.getValueString("applicationCode"), partner.getValueString("applicationCode"), at.format(DateTimeFormatter.BASIC_ISO_DATE),
at.format(DateTimeFormatter.ofPattern("HHmm")), gsControl, "X", document.getValueString("version"));
var interchange = new X12Interchange(new X12Separators(value(agreement, "elementSeparator", "*").charAt(0), value(agreement, "segmentTerminator", "~").charAt(0), component.charAt(0), version.compareTo("00501") >= 0 ? repetition.charAt(0) : null),
isa, X12Segment.of("IEA", 0, "1", isaControl), List.of(new X12FunctionalGroup(gs, X12Segment.of("GE", 0, String.valueOf(sets.size()), gsControl), List.copyOf(sets), List.of())), List.of(), List.of());
String ending = switch(value(agreement, "lineEnding", "NONE"))
{
case "LF" -> "\n";
case "CRLF" -> "\r\n";
default -> "";
};
var payload = runtime.payloads().put(PayloadKind.OUTBOUND, new ByteArrayInputStream(runtime.writer().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", "X12")
.withValue("senderQualifier", isa.value(5)).withValue("senderId", isa.value(6)).withValue("receiverQualifier", isa.value(7)).withValue("receiverId", isa.value(8))
.withValue("controlNumber", isaControl).withValue("interchangeVersion", version).withValue("usageIndicator", isa.value(15)).withValue("interchangeDate", runtime.config().getClock().instant())
.withValue("groupCount", 1).withValue("payloadRef", payload.value()).withValue("payloadSha256", payload.sha256()).withValue("sizeBytes", payload.sizeBytes())
.withValue("status", "ENVELOPED").withValue("containsPhi", document.getValue("containsPhi")).withValue("originalInterchangeId", original));
boolean expectsAck = document.getValueString("ackExpectation") != null && !"NONE".equals(document.getValueString("ackExpectation"));
QRecord group = SystemWrites.insert("edi_functional_group", new QRecord().withValue("interchangeId", persisted.getValue("id")).withValue("senderId", gs.value(2))
.withValue("functionalIdCode", gs.value(1)).withValue("controlNumber", gsControl).withValue("version", gs.value(8)).withValue("transactionCount", sets.size())
.withValue("ackStatus", expectsAck ? "PENDING" : "NONE"));
for(int i = 0; i < sets.size(); i++)
{
var set = sets.get(i);
String businessKey = map.getValueString("businessKeyPath") == null ? null : new com.kingsrook.qbits.edi.guide.RuleEvaluator(set.segments(), java.util.Map.of()).values(map.getValueString("businessKeyPath")).stream().findFirst().orElse(null);
SystemWrites.insert("edi_transaction", new QRecord().withValue("interchangeId", persisted.getValue("id")).withValue("functionalGroupId", group.getValue("id"))
.withValue("direction", "OUTBOUND").withValue("partnerId", agreement.getValue("partnerId")).withValue("agreementDocumentId", document.getValue("id"))
.withValue("transactionSet", set.st().value(1)).withValue("version", gs.value(8)).withValue("controlNumber", set.st().value(2)).withValue("status", "QUEUED")
.withValue("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)
.withValue("containsPhi", document.getValue("containsPhi")).withValue("segmentCount", set.segments().size()));
if(i < requests.size())
{
SystemWrites.update("edi_outbound_request", new QRecord().withValue("id", requests.get(i).getValue("id")).withValue("status", "ENVELOPED"));
}
}
QRecord message = 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("payloadRef", payload.value()).withValue("payloadSha256", payload.sha256()).withValue("sizeBytes", payload.sizeBytes()));
SystemWrites.update("edi_interchange", new QRecord().withValue("id", persisted.getValue("id")).withValue("outboundMessageId", message.getValue("id")));
EdiJournal.event(null, persisted.getValueInteger("id"), null, "ENVELOPED");
return (message);
}
/*******************************************************************************
** Allocate in the configured agreement or identity scope.
*******************************************************************************/
private static long allocate(QRecord agreement, String level) throws Exception
{
boolean local = "LOCAL_IDENTITY".equals(agreement.getValueString("controlNumberScope"));
return (EdiRuntime.current().controls().allocate(local ? null : agreement.getValueInteger("id"), local ? agreement.getValueInteger("localIdentityId") : null, level, 1, 999999999, true));
}
/*******************************************************************************
** Lookup required configuration without leaking submitted values.
*******************************************************************************/
public static QRecord row(String table, Serializable id) throws QException
{
if(id == null)
{
throw new QException("Required EDI record reference is absent");
}
return (EdiRecords.query(table, EdiRecords.equal("id", id)).getFirst());
}
/*******************************************************************************
** Default optional envelope settings.
*******************************************************************************/
private static String value(QRecord record, String field, String fallback)
{
return (Objects.requireNonNullElse(record.getValueString(field), fallback));
}
}
@@ -1,155 +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.Instant;
import java.util.List;
import java.util.Objects;
import com.kingsrook.qbits.edi.EdiRuntime;
import com.kingsrook.qbits.edi.model.EdiOutboundMessage;
import com.kingsrook.qbits.edi.model.EdiTransport;
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.transport.EdiSendResult;
import com.kingsrook.qbits.edi.transport.EdiTransportPluginsMetaData;
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.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;
/*******************************************************************************
** Durable at-least-once sender. A database row lock fences concurrent senders;
** transport timeouts bound each attempt. Stable bytes/ids make retries detectable.
*******************************************************************************/
public class SendWorker
{
public static final List<Long> BACKOFF = List.of(60L, 300L, 900L, 3600L, 10800L, 21600L, 43200L, 86400L);
/*******************************************************************************
** Process only queued or due retry rows, including inbound-generated replies.
*******************************************************************************/
public Integer runOnce() throws QException
{
return (SystemWrites.call(() ->
{
int count = 0;
var due = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR).withCriteria("nextAttemptAt", QCriteriaOperator.IS_BLANK)
.withCriteria("nextAttemptAt", QCriteriaOperator.LESS_THAN_OR_EQUALS, now());
for(QRecord message : EdiRecords.query("edi_outbound_message", new QQueryFilter().withCriteria("status", QCriteriaOperator.IN, "QUEUED", "RETRY_WAIT").withSubFilter(due).withLimit(20)))
{
count += send(message);
}
return (count);
}));
}
/*******************************************************************************
** Compare selected status and attempts under lock before performing remote I/O.
*******************************************************************************/
private Integer send(QRecord selected) throws QException
{
return (SystemWrites.transaction(() ->
{
if(!SystemWrites.guard("UPDATE edi_outbound_message SET id=id WHERE id=? AND status=? AND COALESCE(attempts,0)=?", selected.getValue("id"), selected.getValue("status"), Objects.requireNonNullElse(selected.getValueInteger("attempts"), 0)))
{
return (0);
}
QRecord message = OutboundWorker.row("edi_outbound_message", selected.getValue("id"));
int attempts = Objects.requireNonNullElse(message.getValueInteger("attempts"), 0) + 1;
int maximum = 8;
EdiSendResult result;
try
{
QRecord transport = OutboundWorker.row("edi_transport", message.getValue("transportId"));
maximum = Objects.requireNonNullElse(transport.getValueInteger("maxAttempts"), 8);
if(!Boolean.TRUE.equals(transport.getValueBoolean("isActive")) || "INBOUND".equals(transport.getValueString("direction")))
{
result = new EdiSendResult("FAILED", null, "Outbound transport is disabled");
}
else
{
result = EdiTransportPluginsMetaData.current().get(transport.getValueString("type")).send(message.toEntity(EdiOutboundMessage.class),
new PayloadRef(message.getValueString("payloadRef"), message.getValueString("payloadSha256"), message.getValueLong("sizeBytes")), transport.toEntity(EdiTransport.class));
}
}
catch(Exception e)
{
result = new EdiSendResult("RETRY", null, "Transport attempt failed");
}
boolean sent = List.of("SENT", "DELIVERED").contains(result.status());
String status = sent ? result.status() : "FAILED".equals(result.status()) ? "FAILED" : attempts >= maximum ? "DEAD_LETTER" : "RETRY_WAIT";
QRecord update = new QRecord().withValue("id", message.getValue("id")).withValue("status", status).withValue("attempts", attempts)
.withValue("claimedAt", null).withValue("lastError", sent ? null : "Transport delivery failed")
.withValue("nextAttemptAt", "RETRY_WAIT".equals(status) ? now().plusSeconds(BACKOFF.get(Math.min(attempts - 1, BACKOFF.size() - 1))) : null);
if(sent)
{
update.withValue("transportMessageId", result.messageId()).withValue("sentAt", now());
if("DELIVERED".equals(status))
{
update.withValue("deliveredAt", now());
}
}
SystemWrites.update("edi_outbound_message", update);
Integer interchangeId = message.getValueInteger("interchangeId");
if(interchangeId != null && sent)
{
SystemWrites.update("edi_interchange", new QRecord().withValue("id", interchangeId).withValue("status", status));
for(QRecord group : EdiRecords.query("edi_functional_group", EdiRecords.equal("interchangeId", interchangeId)))
{
for(QRecord transaction : EdiRecords.query("edi_transaction", EdiRecords.equal("functionalGroupId", group.getValue("id"))))
{
if("QUEUED".equals(transaction.getValueString("status")))
{
SystemWrites.update("edi_transaction", new QRecord().withValue("id", transaction.getValue("id")).withValue("status", "SENT"));
}
if("PENDING".equals(group.getValueString("ackStatus")) && group.getValue("ackDueAt") == null)
{
QRecord document = OutboundWorker.row("edi_agreement_document", transaction.getValue("agreementDocumentId"));
SystemWrites.update("edi_functional_group", new QRecord().withValue("id", group.getValue("id"))
.withValue("ackDueAt", now().plusSeconds(60L * Objects.requireNonNullElse(document.getValueInteger("ackDueMinutes"), 1440))));
}
}
}
}
if(List.of("FAILED", "DEAD_LETTER").contains(status))
{
SystemWrites.insert("edi_exception", new QRecord().withValue("outboundMessageId", message.getValue("id")).withValue("interchangeId", interchangeId)
.withValue("partnerId", message.getValue("partnerId")).withValue("type", "TRANSPORT").withValue("status", "OPEN").withValue("occurrences", 1).withValue("summary", "Outbound delivery failed"));
QAlerts.raise(new AlertSignal("edi.send.failed").withSeverity(AlertSeverity.ERROR).withDedupKey("edi_outbound_message:" + message.getValue("id"))
.withTitle("EDI delivery failed").withSummary("Review the outbound message"));
}
EdiJournal.event(null, interchangeId, null, status);
return (1);
}));
}
/*******************************************************************************
** Clock controlled by the host and deterministic tests.
*******************************************************************************/
private static Instant now()
{
return (EdiRuntime.current().config().getClock().instant());
}
}
@@ -1,31 +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;
/*******************************************************************************
** Positional situational trigger for 999 CTX; never includes business or element values.
*******************************************************************************/
public record AckContext(String segmentId, int segmentPosition, String loopId, Integer elementPosition, Integer componentPosition)
{
}
@@ -1,105 +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.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kingsrook.qbits.edi.storage.EdiRecords;
import com.kingsrook.qbits.edi.storage.PayloadCrypto;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Checksum-based installation of original baseline guide resources.
*******************************************************************************/
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");
/*******************************************************************************
** Read the documented JSON format in memory.
*******************************************************************************/
public Guide read(InputStream input) throws IOException
{
return (new ObjectMapper().readValue(input, Guide.class));
}
/*******************************************************************************
** Read one bundled baseline.
*******************************************************************************/
public Guide resource(String name) throws IOException
{
try(InputStream input = BuiltInGuideLoader.class.getResourceAsStream("/edi/guides/" + name + ".json"))
{
if(input == null)
{
throw new IOException("Unknown built-in guide");
}
return (read(input));
}
}
/*******************************************************************************
** Install changed guides after the host schema and service session are ready.
*******************************************************************************/
public List<Integer> load() throws Exception
{
List<Integer> ids = new ArrayList<>();
for(String resource : RESOURCES)
{
try(InputStream input = BuiltInGuideLoader.class.getResourceAsStream("/edi/guides/" + resource + ".json"))
{
ids.add(upsert(input.readAllBytes()));
}
}
return (ids);
}
/*******************************************************************************
** Retain previous revisions; identical content is a no-op.
*******************************************************************************/
public Integer upsert(byte[] json) throws Exception
{
Guide guide = read(new ByteArrayInputStream(json));
String checksum = PayloadCrypto.digest(json);
List<QRecord> revisions = EdiRecords.query("edi_guide", EdiRecords.equal("name", guide.name()));
int revision = 0;
QRecord latest = null;
for(QRecord row : revisions)
{
if("BUILT_IN".equals(row.getValueString("source")) && (latest == null || row.getValueInteger("revision") > latest.getValueInteger("revision")))
{
latest = row;
}
revision = Math.max(revision, row.getValueInteger("revision"));
}
if(latest != null && checksum.equals(latest.getValueString("compiledChecksum")))
{
return (latest.getValueInteger("id"));
}
return (new GuideRepository().save(guide, checksum, revision + 1));
}
}
@@ -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.guide;
import java.util.List;
/*******************************************************************************
** In-house or imported implementation guide. Segment order is significant.
*******************************************************************************/
public record Guide(String name, String standard, String version, String transactionSet, String functionalIdCode, List<GuideSegment> segments, List<GuideRule> rules)
{
}
@@ -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.guide;
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)
{
}
@@ -1,232 +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.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import com.kingsrook.qbits.edi.storage.EdiRecords;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Load and persist versioned normalized guide rows through standard QQQ actions.
*******************************************************************************/
public class GuideRepository
{
/*******************************************************************************
** Read a guide and all its ordered definitions.
*******************************************************************************/
public Guide load(Integer id) throws Exception
{
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);
for(QRecord definition : definitions)
{
Integer segmentId = definition.getValueInteger("id");
List<GuideElement> elements = new ArrayList<>();
for(QRecord element : rows("edi_guide_element", "guideSegmentId", segmentId))
{
List<String> codes = null;
if(element.getValueInteger("codeListId") != null)
{
codes = new ArrayList<>();
for(QRecord code : rows("edi_code_list_value", "codeListId", element.getValueInteger("codeListId")))
{
if(Boolean.TRUE.equals(code.getValueBoolean("isActive")))
{
codes.add(code.getValueString("code"));
}
}
}
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"))));
}
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"),
definition.getValueInteger("loopMaxRepeat"), definition.getValueString("usage"), definition.getValueInteger("minUse"), definition.getValueInteger("maxUse"),
elements, segmentRules, condition(rules, definition.getValueInteger("situationalRuleId"))));
}
return (new Guide(guide.getValueString("name"), guide.getValueString("standard"), guide.getValueString("version"), guide.getValueString("transactionSet"),
guide.getValueString("functionalIdCode"), segments, rules.stream().filter(r -> r.getValueInteger("guideSegmentId") == null).map(GuideRepository::rule).toList()));
}
/*******************************************************************************
** Insert a complete new revision atomically, retaining every previous revision.
*******************************************************************************/
public Integer save(Guide guide, String checksum, int revision) throws Exception
{
try(QBackendTransaction transaction = QBackendTransaction.openFor(new InsertInput("edi_guide")))
{
try
{
QRecord row = insert("edi_guide", new QRecord().withValue("name", guide.name()).withValue("standard", guide.standard()).withValue("version", guide.version())
.withValue("transactionSet", guide.transactionSet()).withValue("functionalIdCode", guide.functionalIdCode()).withValue("revision", revision)
.withValue("status", "ACTIVE").withValue("source", "BUILT_IN").withValue("compiledChecksum", checksum), transaction);
Integer id = row.getValueInteger("id");
int sequence = 0;
for(GuideSegment segment : guide.segments())
{
QRecord segmentRow = insert("edi_guide_segment", new QRecord().withValue("guideId", id).withValue("sequence", ++sequence)
.withValue("segmentId", segment.segmentId()).withValue("name", segment.segmentId()).withValue("position", String.format("%04d", sequence * 10))
.withValue("loopId", segment.loopId()).withValue("parentLoopId", segment.parentLoopId()).withValue("loopMaxRepeat", segment.loopMaxRepeat())
.withValue("usage", segment.usage()).withValue("minUse", segment.minUse()).withValue("maxUse", segment.maxUse()), transaction);
Integer segmentId = segmentRow.getValueInteger("id");
for(GuideElement element : list(segment.elements()))
{
Integer codeListId = null;
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", "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);
}
}
insert("edi_guide_element", new QRecord().withValue("guideSegmentId", segmentId).withValue("position", element.position())
.withValue("componentPosition", element.componentPosition()).withValue("elementId", element.elementId()).withValue("dataType", element.dataType())
.withValue("minLength", element.minLength()).withValue("maxLength", element.maxLength()).withValue("usage", element.usage())
.withValue("codeListId", codeListId).withValue("fixedValue", element.fixedValue()).withValue("repeatMax", element.repeatMax())
.withValue("situationalRuleId", saveCondition(id, segmentId, element.condition(), transaction)), transaction);
}
if(segment.condition() != null)
{
new UpdateAction().execute(new UpdateInput("edi_guide_segment").withTransaction(transaction).withRecord(new QRecord().withValue("id", segmentId)
.withValue("situationalRuleId", saveCondition(id, segmentId, segment.condition(), transaction))));
}
for(GuideRule rule : list(segment.rules()))
{
saveRule(id, segmentId, rule, transaction);
}
}
for(GuideRule rule : list(guide.rules()))
{
saveRule(id, null, rule, transaction);
}
for(QRecord previous : EdiRecords.query("edi_guide", EdiRecords.equal("name", guide.name())))
{
if("BUILT_IN".equals(previous.getValueString("source")) && "ACTIVE".equals(previous.getValueString("status")) && !id.equals(previous.getValueInteger("id")))
{
QRecord updated = new UpdateAction().execute(new UpdateInput("edi_guide").withTransaction(transaction)
.withRecord(new QRecord().withValue("id", previous.getValueInteger("id")).withValue("status", "RETIRED"))).getRecords().getFirst();
if(updated.getErrors() != null && !updated.getErrors().isEmpty())
{
throw new QException("Previous guide revision could not be retired");
}
}
}
transaction.commit();
return (id);
}
catch(Exception e)
{
transaction.rollback();
throw e;
}
}
}
/*******************************************************************************
** Store a condition as a referenced guide rule.
*******************************************************************************/
private static Integer saveCondition(Integer guideId, Integer segmentId, String expression, QBackendTransaction transaction) throws Exception
{
if(expression == null)
{
return (null);
}
return (saveRule(guideId, segmentId, new GuideRule("SITUATIONAL", List.of(), expression, 2, "ERROR", "condition", "8", "2", "8", "2"), transaction));
}
/*******************************************************************************
** Store syntax and situational rules without losing ack codes.
*******************************************************************************/
private static Integer saveRule(Integer guideId, Integer segmentId, GuideRule rule, QBackendTransaction transaction) throws Exception
{
return (insert("edi_guide_rule", new QRecord().withValue("guideId", guideId).withValue("guideSegmentId", segmentId).withValue("type", rule.type())
.withValue("positions", list(rule.positions()).stream().map(Object::toString).collect(Collectors.joining(","))).withValue("expression", rule.expression())
.withValue("snipLevel", rule.snipLevel()).withValue("severity", rule.severity()).withValue("message", rule.message())
.withValue("ak3Code", rule.ak3Code()).withValue("ak4Code", rule.ak4Code()).withValue("ik3Code", rule.ik3Code()).withValue("ik4Code", rule.ik4Code()), transaction).getValueInteger("id"));
}
/*******************************************************************************
** Reconstruct a syntax or situational rule.
*******************************************************************************/
private static GuideRule rule(QRecord row)
{
String positions = row.getValueString("positions");
return (new GuideRule(row.getValueString("type"), positions == null || positions.isBlank() ? List.of() : Arrays.stream(positions.split(",")).map(Integer::valueOf).toList(),
row.getValueString("expression"), row.getValueInteger("snipLevel"), row.getValueString("severity"), row.getValueString("message"), row.getValueString("ak3Code"),
row.getValueString("ak4Code"), row.getValueString("ik3Code"), row.getValueString("ik4Code")));
}
/*******************************************************************************
** Resolve a condition by its explicit row id.
*******************************************************************************/
private static String condition(List<QRecord> rules, Integer id)
{
return (rules.stream().filter(r -> Objects.equals(r.getValueInteger("id"), id)).map(r -> r.getValueString("expression")).findFirst().orElse(null));
}
/*******************************************************************************
** Query normalized child records.
*******************************************************************************/
private static List<QRecord> rows(String table, String field, Integer id) throws Exception
{
return (EdiRecords.query(table, EdiRecords.equal(field, id)));
}
/*******************************************************************************
** Insert within the owning guide transaction, checking validation errors.
*******************************************************************************/
private static QRecord insert(String table, QRecord row, QBackendTransaction transaction) throws Exception
{
QRecord result = new InsertAction().execute(new InsertInput(table).withTransaction(transaction).withRecord(row)).getRecords().getFirst();
if(result.getErrors() != null && !result.getErrors().isEmpty())
{
throw new QException("Guide record validation failed for " + table);
}
return (result);
}
/*******************************************************************************
** Treat omitted optional lists as empty.
*******************************************************************************/
private static <T> List<T> list(List<T> list)
{
return (list == null ? List.of() : list);
}
}
@@ -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.guide;
import java.util.List;
/*******************************************************************************
** A syntax note or small-language expression; no executable host code.
*******************************************************************************/
public record GuideRule(String type, List<Integer> positions, String expression, Integer snipLevel, String severity, String message, String ak3Code, String ak4Code, String ik3Code, String ik4Code)
{
}
@@ -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.guide;
import java.util.List;
/*******************************************************************************
** Ordered segment definition; the first segment of a loop starts each occurrence.
*******************************************************************************/
public record GuideSegment(String segmentId, String loopId, String parentLoopId, Integer loopMaxRepeat, String usage, Integer minUse, Integer maxUse, List<GuideElement> elements, List<GuideRule> rules, String condition)
{
}
@@ -1,412 +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.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.edi.x12.X12Segment;
import com.kingsrook.qbits.edi.x12.X12TransactionSet;
/*******************************************************************************
** SNIP 1-2 structural and semantic validation over StAEDI-tokenized segments.
*******************************************************************************/
public class GuideValidator
{
/*******************************************************************************
** Validate one transaction; diagnostic text never contains submitted values.
*******************************************************************************/
public List<ValidationFinding> validate(X12TransactionSet transaction, Guide guide, boolean containsPhi)
{
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())
{
X12Segment segment = state.segments.get(state.index++);
boolean known = guide.segments().stream().anyMatch(s -> s.segmentId().equals(segment.id()));
state.add(segment, null, null, known ? "7" : "2", null, known ? "Segment out of order" : "Unexpected segment", null);
}
RuleEvaluator evaluator = new RuleEvaluator(state.segments, state.loops);
for(GuideSegment definition : guide.segments())
{
if(definition.condition() != null)
{
List<List<X12Segment>> scopes = definition.loopId() == null ? List.of(state.segments) : state.loops.getOrDefault(definition.loopId(), List.of(state.segments));
for(List<X12Segment> scope : scopes)
{
RuleEvaluator scoped = new RuleEvaluator(scope, state.loops);
if(safeTest(scoped, definition.condition()) && state.bindings.stream().noneMatch(b -> b.definition() == definition && scope.contains(b.segment())))
{
state.add(X12Segment.of(definition.segmentId(), scope.isEmpty() ? 1 : scope.getLast().position() + 1), definition.loopId(), null, "3", null, "Situational segment required", null);
int last = state.findings.size() - 1;
state.findings.set(last, state.findings.get(last).withContext(scoped.context(definition.condition())));
}
}
}
}
for(GuideRule rule : list(guide.rules()))
{
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);
for(GuideRule rule : list(definition.rules()))
{
rule(rule, segment, definition.loopId(), evaluator, state);
}
}
return (List.copyOf(state.findings));
}
/*******************************************************************************
** Resolve guide-defined loop occurrences for the mapper using the validator grammar.
*******************************************************************************/
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> 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);
return (state.loops);
}
/*******************************************************************************
** Consume ordered segments and nested loop occurrences.
*******************************************************************************/
private static void consume(List<Node> nodes, State state, boolean loopOccurrence)
{
for(Node node : nodes)
{
if(node.definition == null)
{
GuideSegment head = node.children.getFirst().definition;
int count = 0;
while(state.matches(head.segmentId()))
{
int start = state.index;
consume(node.children, state, true);
if(start == state.index)
{
break;
}
state.loops.computeIfAbsent(node.loopId, key -> new ArrayList<>()).add(state.segments.subList(start, state.index));
if(++count > number(head.loopMaxRepeat(), 1))
{
state.add(state.segments.get(start), node.loopId, null, "4", null, "Loop repeat limit exceeded", null);
}
}
if(count == 0 && "REQUIRED".equals(head.usage()))
{
state.add(X12Segment.of(head.segmentId(), state.position()), node.loopId, null, "3", null, "Required loop missing", null);
}
}
else
{
GuideSegment definition = node.definition;
int count = 0;
int maximum = number(definition.maxUse(), 1);
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));
count++;
if("NOT_USED".equals(definition.usage()))
{
state.add(segment, definition.loopId(), null, "6", null, "Segment is not used", null);
}
if(count > maximum)
{
state.add(segment, definition.loopId(), null, "5", null, "Segment use limit exceeded", null);
}
}
int minimum = Math.max(number(definition.minUse(), 0), "REQUIRED".equals(definition.usage()) ? 1 : 0);
if(count < minimum)
{
state.add(X12Segment.of(definition.segmentId(), state.position()), definition.loopId(), null, "3", null, "Required segment missing", null);
}
}
}
}
/*******************************************************************************
** Apply usage, lengths, data types, code lists, fixed values and repetition bounds.
*******************************************************************************/
private static void elements(X12Segment segment, GuideSegment definition, RuleEvaluator evaluator, State state)
{
for(GuideElement element : list(definition.elements()))
{
int repetitions = element.position() <= segment.elements().size() ? segment.elements().get(element.position() - 1).size() : 1;
if(repetitions > number(element.repeatMax(), 1))
{
state.add(segment, definition.loopId(), element, "8", "12", "Element repetition limit exceeded", null);
}
for(int r = 1; r <= repetitions; r++)
{
String value = segment.value(element.position(), r, number(element.componentPosition(), 1));
boolean required = "REQUIRED".equals(element.usage()) || element.condition() != null && safeTest(evaluator, element.condition());
if(value.isEmpty())
{
if(required)
{
state.add(segment, definition.loopId(), element, "8", "1", "Required element missing", null);
if(element.condition() != null)
{
int last = state.findings.size() - 1;
state.findings.set(last, state.findings.get(last).withContext(evaluator.context(element.condition())));
}
}
continue;
}
if("NOT_USED".equals(element.usage()))
{
state.add(segment, definition.loopId(), element, "8", "10", "Element is not used", value);
}
if(value.length() < number(element.minLength(), 0))
{
state.add(segment, definition.loopId(), element, "8", "4", "Element too short", value);
}
if(value.length() > number(element.maxLength(), Integer.MAX_VALUE))
{
state.add(segment, definition.loopId(), element, "8", "5", "Element too long", 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)
|| "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);
}
}
}
}
/*******************************************************************************
** Validate X12 lexical data types without coercing bad values.
*******************************************************************************/
public static boolean validType(String type, String value)
{
if(type == null || type.equals("AN") || type.equals("ID") || type.equals("B"))
{
return (value.chars().noneMatch(Character::isISOControl));
}
if(type.matches("N[0-9]"))
{
return (value.matches("-?[0-9]+"));
}
if(type.equals("R"))
{
return (value.matches("-?(?:[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+)"));
}
if(type.equals("DT"))
{
return (RuleEvaluator.validDate(value));
}
if(type.equals("TM"))
{
return (value.matches("(?:[01][0-9]|2[0-3])[0-5][0-9](?:[0-5][0-9](?:[0-9]{1,2})?)?"));
}
throw new IllegalArgumentException("Unknown guide data type");
}
/*******************************************************************************
** Evaluate syntax notes and explicit expressions; PHI rules use a safe message.
*******************************************************************************/
private static void rule(GuideRule rule, X12Segment segment, String loopId, RuleEvaluator evaluator, State state)
{
List<Integer> positions = list(rule.positions());
long present = positions.stream().filter(p -> !segment.value(p).isEmpty()).count();
boolean first = !positions.isEmpty() && !segment.value(positions.getFirst()).isEmpty();
boolean passed = switch(rule.type())
{
case "PAIRED", "P" -> present == 0 || present == positions.size();
case "REQUIRED", "R" -> present > 0;
case "EXCLUSION", "E" -> present <= 1;
case "CONDITIONAL", "C" -> !first || present == positions.size();
case "LIST_CONDITIONAL", "L" -> !first || present > 1;
case "SITUATIONAL" -> safeTest(evaluator, rule.expression());
default -> throw new IllegalArgumentException("Unknown syntax note");
};
if(!passed)
{
state.findings.add(new ValidationFinding(segment.position(), segment.id(), loopId, positions.isEmpty() ? null : positions.getFirst(), null,
number(rule.snipLevel(), 2), fallback(rule.ak3Code(), "8"), fallback(rule.ak4Code(), "2"), fallback(rule.ik3Code(), "8"), fallback(rule.ik4Code(), "2"),
fallback(rule.severity(), "ERROR"), state.phi ? "Guide rule failed" : fallback(rule.message(), "Guide rule failed"), null)
.withContext("SITUATIONAL".equals(rule.type()) ? evaluator.context(rule.expression()) : null));
}
}
/*******************************************************************************
** Bad data cannot leak through numeric or regular-expression exception messages.
*******************************************************************************/
private static boolean safeTest(RuleEvaluator evaluator, String expression)
{
try
{
return (evaluator.test(expression));
}
catch(IllegalArgumentException e)
{
return (false);
}
}
/*******************************************************************************
** Use a default for omitted optional numeric constraints.
*******************************************************************************/
private static int number(Integer value, int fallback)
{
return (value == null ? fallback : value);
}
/*******************************************************************************
** Use a default for omitted optional text.
*******************************************************************************/
private static String fallback(String value, String fallback)
{
return (value == null ? fallback : value);
}
/*******************************************************************************
** Normalize omitted JSON lists.
*******************************************************************************/
private static <T> List<T> list(List<T> value)
{
return (value == null ? List.of() : value);
}
/*******************************************************************************
** A guide tree node.
*******************************************************************************/
private static class Node
{
private final String loopId;
private final GuideSegment definition;
private final List<Node> children = new ArrayList<>();
/*******************************************************************************
** Create a loop or segment node.
*******************************************************************************/
private Node(String loopId, GuideSegment definition)
{
this.loopId = loopId;
this.definition = definition;
}
}
/*******************************************************************************
** Bind a parsed segment to its guide definition.
*******************************************************************************/
private record Binding(X12Segment segment, GuideSegment definition)
{
}
/*******************************************************************************
** Per-call state; validator instances are safe to share.
*******************************************************************************/
private static class State
{
private final List<X12Segment> segments;
private final boolean phi;
private final List<ValidationFinding> findings = new ArrayList<>();
private final List<Binding> bindings = new ArrayList<>();
private final Map<String, List<List<X12Segment>>> loops = new LinkedHashMap<>();
private int index;
/*******************************************************************************
** Initialize a transaction validation.
*******************************************************************************/
private State(List<X12Segment> segments, boolean phi)
{
this.segments = segments;
this.phi = phi;
}
/*******************************************************************************
** Check the next segment.
*******************************************************************************/
private boolean matches(String id)
{
return (index < segments.size() && segments.get(index).id().equals(id));
}
/*******************************************************************************
** Locate a missing segment relative to the buffered transaction.
*******************************************************************************/
private int position()
{
return (index < segments.size() ? segments.get(index).position() : segments.size() + 1);
}
/*******************************************************************************
** Add a diagnostic with a bounded, optionally suppressed excerpt.
*******************************************************************************/
private void add(X12Segment segment, String loopId, GuideElement element, String segmentCode, String elementCode, String message, String value)
{
findings.add(new ValidationFinding(segment.position(), segment.id(), loopId, element == null ? null : element.position(), element == null ? null : element.componentPosition(),
2, segmentCode, elementCode, segmentCode, elementCode, "ERROR", message, phi || value == null ? null : value.substring(0, Math.min(64, value.length()))));
}
}
}
@@ -1,363 +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.DateTimeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.kingsrook.qbits.edi.x12.X12Segment;
/*******************************************************************************
** Parser and evaluator for the closed EDI rule language. No scripting or reflection.
*******************************************************************************/
public class RuleEvaluator
{
private final List<X12Segment> segments;
private final Map<String, List<List<X12Segment>>> loops;
/*******************************************************************************
** Bind document segments and guide-derived loop occurrences.
*******************************************************************************/
public RuleEvaluator(List<X12Segment> segments, Map<String, List<List<X12Segment>>> loops)
{
this.segments = segments;
this.loops = loops;
}
/*******************************************************************************
** Require the expression to return a boolean.
*******************************************************************************/
public boolean test(String expression)
{
Object result = evaluate(expression.trim());
if(!(result instanceof Boolean))
{
throw new IllegalArgumentException("Rule must return a boolean");
}
return ((Boolean) result);
}
/*******************************************************************************
** Evaluate one expression recursively with explicit functions only.
*******************************************************************************/
public Object evaluate(String expression)
{
String text = expression.trim();
if(text.startsWith("'") && text.endsWith("'"))
{
return (text.substring(1, text.length() - 1).replace("\'", "'"));
}
if(text.matches("-?[0-9]+(?:\\.[0-9]+)?"))
{
return (new BigDecimal(text));
}
int open = text.indexOf('(');
if(open < 0)
{
return (values(text));
}
if(!text.endsWith(")"))
{
throw new IllegalArgumentException("Unclosed rule function");
}
String function = text.substring(0, open).trim();
List<String> args = arguments(text.substring(open + 1, text.length() - 1));
int arity = switch(function)
{
case "present", "absent", "count", "sum", "date", "not" -> 1;
case "eq", "matches", "when" -> 2;
case "in" -> -2;
case "and", "or" -> -1;
default -> throw new IllegalArgumentException("Unknown rule function");
};
if(arity > 0 && args.size() != arity || arity < 0 && args.size() < -arity)
{
throw new IllegalArgumentException("Invalid rule argument count");
}
return (switch(function)
{
case "present" -> !asValues(evaluate(arg(args, 0))).isEmpty();
case "absent" -> asValues(evaluate(arg(args, 0))).isEmpty();
case "count" -> BigDecimal.valueOf(asValues(evaluate(arg(args, 0))).size());
case "sum" -> asValues(evaluate(arg(args, 0))).stream().map(BigDecimal::new).reduce(BigDecimal.ZERO, BigDecimal::add);
case "eq" -> equal(evaluate(arg(args, 0)), evaluate(arg(args, 1)));
case "in" -> args.subList(1, args.size()).stream().anyMatch(a -> equal(evaluate(arg(args, 0)), evaluate(a)));
case "matches" -> asValues(evaluate(arg(args, 0))).stream().allMatch(v -> v.matches(scalar(evaluate(arg(args, 1)))));
case "date" -> validDate(scalar(evaluate(arg(args, 0))));
case "and" -> args.stream().allMatch(this::test);
case "or" -> args.stream().anyMatch(this::test);
case "not" -> !test(arg(args, 0));
case "when" -> !test(arg(args, 0)) || test(arg(args, 1));
default -> throw new IllegalArgumentException("Unknown rule function");
});
}
/*******************************************************************************
** Extract a required argument.
*******************************************************************************/
private static String arg(List<String> args, int index)
{
if(index >= args.size())
{
throw new IllegalArgumentException("Missing rule argument");
}
return (args.get(index));
}
/*******************************************************************************
** Split function arguments outside nested calls, predicates and quoted strings.
*******************************************************************************/
private static List<String> arguments(String text)
{
List<String> result = new ArrayList<>();
boolean quoted = false;
int depth = 0;
int start = 0;
for(int i = 0; i < text.length(); i++)
{
char c = text.charAt(i);
if(c == '\'' && (i == 0 || text.charAt(i - 1) != '\\'))
{
quoted = !quoted;
}
if(!quoted)
{
if(c == '(' || c == '[')
{
depth++;
}
if(c == ')' || c == ']')
{
depth--;
}
if(c == ',' && depth == 0)
{
result.add(text.substring(start, i).trim());
start = i + 1;
}
}
}
if(quoted || depth != 0)
{
throw new IllegalArgumentException("Invalid rule syntax");
}
result.add(text.substring(start).trim());
return (result);
}
/*******************************************************************************
** Resolve loop paths, segment predicates, one-based indexes and components.
*******************************************************************************/
public List<String> values(String path)
{
List<X12Segment> scope = segments;
String[] steps = path.split("/");
for(int i = 0; i < steps.length; i++)
{
String step = steps[i];
Matcher selector = Pattern.compile("([^\\[]+)(?:\\[(.*)])?").matcher(step);
if(!selector.matches())
{
throw new IllegalArgumentException("Invalid EDI path");
}
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())
{
List<X12Segment> selected = select(scope, element.group(1), qualifier);
int position = Integer.parseInt(element.group(2));
int component = element.group(3) == null ? 1 : Integer.parseInt(element.group(3));
List<String> result = new ArrayList<>();
for(X12Segment segment : selected)
{
if(position <= segment.elements().size())
{
for(int r = 1; r <= segment.elements().get(position - 1).size(); r++)
{
String value = segment.value(position, r, component);
if(!value.isEmpty())
{
result.add(value);
}
}
}
}
return (result);
}
List<List<X12Segment>> occurrences = loops.get(name);
if(occurrences == null)
{
List<List<X12Segment>> byAnchor = loops.values().stream().flatMap(List::stream)
.filter(o -> !o.isEmpty() && o.getFirst().id().equals(name)).distinct()
.sorted(java.util.Comparator.comparingInt(o -> o.getFirst().position())).toList();
if(!byAnchor.isEmpty())
{
occurrences = byAnchor;
}
}
if(occurrences != null)
{
List<X12Segment> priorScope = scope;
occurrences = occurrences.stream().filter(o -> !o.isEmpty() && priorScope.contains(o.getFirst())).toList();
List<X12Segment> heads = select(occurrences.stream().map(List::getFirst).toList(), null, qualifier);
scope = occurrences.stream().filter(o -> heads.contains(o.getFirst())).flatMap(List::stream).toList();
if(i == steps.length - 1)
{
return (heads.stream().map(X12Segment::id).toList());
}
}
else
{
scope = select(scope, name, qualifier);
}
}
return (scope.stream().map(X12Segment::id).toList());
}
/*******************************************************************************
** Apply segment selection without evaluating arbitrary expressions.
*******************************************************************************/
private static List<X12Segment> select(List<X12Segment> scope, String name, String qualifier)
{
List<X12Segment> result = scope.stream().filter(s -> name == null || name.equals(s.id())).toList();
if(qualifier == null || qualifier.equals("*"))
{
return (result);
}
if(qualifier.matches("[0-9]+"))
{
int index = Integer.parseInt(qualifier) - 1;
return (index >= 0 && index < result.size() ? List.of(result.get(index)) : List.of());
}
Matcher predicate = Pattern.compile("[A-Z][A-Z0-9]{1,2}?([0-9]{2})(?:-([0-9]+))?=(.*)").matcher(qualifier);
if(!predicate.matches())
{
throw new IllegalArgumentException("Invalid path predicate");
}
int position = Integer.parseInt(predicate.group(1));
int component = predicate.group(2) == null ? 1 : Integer.parseInt(predicate.group(2));
String expected = predicate.group(3).replace("'", "");
return (result.stream().filter(s -> s.value(position, 1, component).equals(expected)).toList());
}
/*******************************************************************************
** Convert values to a scalar for scalar-only functions.
*******************************************************************************/
private static String scalar(Object value)
{
List<String> values = asValues(value);
return (values.isEmpty() ? "" : values.getFirst());
}
/*******************************************************************************
** Normalize scalar and path results.
*******************************************************************************/
private static List<String> asValues(Object value)
{
if(value instanceof List<?> list)
{
return (list.stream().map(Object::toString).toList());
}
return (List.of(value.toString()));
}
/*******************************************************************************
** Compare numerically when either argument is numeric, otherwise by value.
*******************************************************************************/
private static boolean equal(Object left, Object right)
{
if(left instanceof BigDecimal || right instanceof BigDecimal)
{
try
{
return (new BigDecimal(scalar(left)).compareTo(new BigDecimal(scalar(right))) == 0);
}
catch(NumberFormatException e)
{
return (false);
}
}
return (asValues(left).stream().anyMatch(asValues(right)::contains));
}
/*******************************************************************************
** Validate six- or eight-digit calendar dates; YY uses the 1950-2049 window.
*******************************************************************************/
public static boolean validDate(String value)
{
if(!value.matches("[0-9]{6}|[0-9]{8}"))
{
return (false);
}
if(value.length() == 6)
{
value = (Integer.parseInt(value.substring(0, 2)) >= 50 ? "19" : "20") + value;
}
try
{
LocalDate.parse(value, DateTimeFormatter.BASIC_ISO_DATE);
return (true);
}
catch(DateTimeException e)
{
return (false);
}
}
/*******************************************************************************
** Locate the first present trigger referenced by a situational condition; values never leave the evaluator.
*******************************************************************************/
public AckContext context(String expression)
{
String condition = expression.trim();
if(condition.startsWith("when("))
{
condition = arguments(condition.substring(5, condition.length() - 1)).getFirst();
}
// Remove literals before recognizing paths so quoted business text cannot become context.
condition = condition.replaceAll("'[^']*'", "");
Matcher matcher = Pattern.compile("([A-Z][A-Z0-9]{1,2}?)([0-9]{2})(?:-([0-9]+))?").matcher(condition);
while(matcher.find())
{
String id = matcher.group(1);
int position = Integer.parseInt(matcher.group(2));
Integer component = matcher.group(3) == null ? null : Integer.valueOf(matcher.group(3));
for(X12Segment segment : segments)
{
if(segment.id().equals(id) && !segment.value(position, 1, component == null ? 1 : component).isEmpty())
{
String loop = loops.entrySet().stream().filter(e -> e.getValue().stream().anyMatch(o -> o.contains(segment))).map(Map.Entry::getKey).findFirst().orElse(null);
return (new AckContext(id, segment.position(), loop, position, component));
}
}
}
return (null);
}
}
@@ -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.guide;
/*******************************************************************************
** Structured validation result; PHI values must never appear in text or excerpts.
*******************************************************************************/
public record ValidationFinding(int segmentPosition, String segmentId, String loopId, Integer elementPosition, Integer componentPosition, int snipLevel, String ak3Code, String ak4Code, String ik3Code, String ik4Code, String severity, String message, String valueExcerpt, AckContext context)
{
/*******************************************************************************
** Construct a finding without situational trigger context.
*******************************************************************************/
public ValidationFinding(int segmentPosition, String segmentId, String loopId, Integer elementPosition, Integer componentPosition, int snipLevel,
String ak3Code, String ak4Code, String ik3Code, String ik4Code, String severity, String message, String valueExcerpt)
{
this(segmentPosition, segmentId, loopId, elementPosition, componentPosition, snipLevel, ak3Code, ak4Code, ik3Code, ik4Code, severity, message, valueExcerpt, null);
}
/*******************************************************************************
** Attach only positional acknowledgement context.
*******************************************************************************/
public ValidationFinding withContext(AckContext context)
{
return (new ValidationFinding(segmentPosition, segmentId, loopId, elementPosition, componentPosition, snipLevel, ak3Code, ak4Code, ik3Code, ik4Code, severity, message, valueExcerpt, context));
}
}
@@ -1,166 +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.List;
import java.util.Map;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
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.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.session.QSession;
/*******************************************************************************
** Permission-enforced target writes, including association writes and upsert reads.
*******************************************************************************/
public class InboundApplier
{
/*******************************************************************************
** Run mapping and applying with the supplied least-privilege service identity.
*******************************************************************************/
public static <T> T asService(Supplier<QSession> supplier, com.kingsrook.qbits.edi.support.SystemWrites.Work<T> work) throws Exception
{
if(supplier == null)
{
throw new IllegalStateException("no service session configured");
}
QSession session = supplier.get();
if(session == null)
{
throw new IllegalStateException("no service session configured");
}
QSession previous = QContext.getQSession();
QContext.setQSession(session);
try
{
return (work.run());
}
finally
{
QContext.setQSession(previous);
}
}
/*******************************************************************************
** Add this transaction's partner key to a copy of the configured session.
*******************************************************************************/
public static <T> T asPartner(Supplier<QSession> supplier, Integer partner, com.kingsrook.qbits.edi.support.SystemWrites.Work<T> work) throws Exception
{
return (asService(supplier, () ->
{
QSession prior = QContext.getQSession();
QSession session = prior.clone();
Map<String, List<java.io.Serializable>> keys = new java.util.HashMap<>();
if(prior.getSecurityKeyValues() != null)
{
prior.getSecurityKeyValues().forEach((key, values) -> keys.put(key, new java.util.ArrayList<>(values)));
}
session.setSecurityKeyValues(keys);
if(partner != null)
{
session.withSecurityKeyValue("edi_partner", partner);
}
QContext.setQSession(session);
try
{
return (work.run());
}
finally
{
QContext.setQSession(prior);
}
}));
}
/*******************************************************************************
** The caller owns the per-transaction-set database transaction.
*******************************************************************************/
public QRecord apply(QRecord map, QRecord record) throws Exception
{
String table = map.getValueString("tableName");
var targetTable = QContext.getQInstance().getTable(table);
String backend = com.kingsrook.qbits.edi.EdiRuntime.current().config().getBackendName();
if(targetTable == null || !backend.equals(targetTable.getBackendName())
|| targetTable.getAssociations().stream().anyMatch(a -> !backend.equals(QContext.getQInstance().getTable(a.getAssociatedTableName()).getBackendName())))
{
throw new IllegalArgumentException("Inbound targets and associations must share the EDI transactional backend");
}
String keys = map.getValueString("matchKeyFields");
List<QRecord> matches = List.of();
if(keys != null && !keys.isBlank())
{
QQueryFilter filter = new QQueryFilter().withLimit(2);
for(String key : keys.split(","))
{
key = key.trim();
if(record.getValue(key) == null)
{
throw new IllegalArgumentException("Upsert key is missing");
}
filter.withCriteria(key, QCriteriaOperator.EQUALS, record.getValue(key));
}
QueryInput query = new QueryInput(table).withFilter(filter).withTransaction(QContext.getQBackendTransaction());
query.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
matches = new QueryAction().execute(query).getRecords();
}
if(matches.size() > 1)
{
throw new IllegalArgumentException("Upsert key is ambiguous");
}
QRecord result;
if(matches.isEmpty())
{
InsertInput insert = new InsertInput(table).withRecord(record).withTransaction(QContext.getQBackendTransaction());
insert.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
result = new InsertAction().execute(insert).getRecords().getFirst();
}
else
{
String onMatch = map.getValueString("onMatch");
if("SKIP".equals(onMatch))
{
return (matches.getFirst());
}
if(!"UPDATE".equals(onMatch))
{
throw new IllegalArgumentException("Existing target record is not updateable by this map");
}
String primaryKey = QContext.getQInstance().getTable(table).getPrimaryKeyField();
record.withValue(primaryKey, matches.getFirst().getValue(primaryKey));
UpdateInput update = new UpdateInput(table).withRecord(record).withTransaction(QContext.getQBackendTransaction());
update.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
result = new UpdateAction().execute(update).getRecords().getFirst();
}
if(result.getErrors() != null && !result.getErrors().isEmpty())
{
throw new IllegalArgumentException("Target write failed validation");
}
return (result);
}
}
@@ -1,163 +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.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
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;
/*******************************************************************************
** Inbound mapping preserving loop occurrences, including missing optional values.
*******************************************************************************/
public class InboundMapper
{
private final TransformRegistry transforms;
/*******************************************************************************
** Use the host's transform registry.
*******************************************************************************/
public InboundMapper(TransformRegistry transforms)
{
this.transforms = transforms;
}
/*******************************************************************************
** Mapped records and extracted business identifiers; never log this object.
*******************************************************************************/
public record Result(QRecord record, String businessKey, String secondaryKey)
{
}
/*******************************************************************************
** Load ordered rules for a persisted map.
*******************************************************************************/
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")));
return (map(transaction, guide, map, rules));
}
/*******************************************************************************
** Map in memory; wildcard rules sharing an association use the same loop scope.
*******************************************************************************/
public Result map(X12TransactionSet transaction, Guide guide, QRecord map, List<QRecord> rules) throws Exception
{
Map<String, List<List<X12Segment>>> loops = new GuideValidator().loops(transaction, guide);
RuleEvaluator evaluator = new RuleEvaluator(transaction.segments(), loops);
QRecord result = new QRecord();
for(QRecord rule : rules)
{
try
{
String path = rule.getValueString("ediPath");
String target = rule.getValueString("qqqPath");
String[] fields = target.split("\\.");
if(fields.length > 2 || fields.length == 0)
{
throw new IllegalArgumentException("Invalid target path");
}
if(fields.length == 1)
{
apply(rule, evaluator, result, fields[0], path);
}
else
{
if(path == null || !path.contains("[*]/"))
{
throw new IllegalArgumentException("Association path requires a wildcard loop");
}
String anchor = path.substring(0, path.indexOf("[*]/"));
String relative = path.substring(path.indexOf("[*]/") + 4);
List<List<X12Segment>> occurrences = loops.get(anchor);
if(occurrences == null)
{
occurrences = loops.values().stream().flatMap(List::stream).filter(l -> !l.isEmpty() && l.getFirst().id().equals(anchor)).distinct().toList();
}
List<QRecord> children = result.getAssociatedRecords() == null ? null : result.getAssociatedRecords().get(fields[0]);
if(children == null)
{
children = new ArrayList<>();
for(int i = 0; i < occurrences.size(); i++)
{
children.add(new QRecord());
}
result.withAssociatedRecords(fields[0], children);
}
if(children.size() != occurrences.size())
{
throw new IllegalArgumentException("Association rules use inconsistent loops");
}
for(int i = 0; i < occurrences.size(); i++)
{
apply(rule, new RuleEvaluator(occurrences.get(i), loops), children.get(i), fields[1], relative);
}
}
}
catch(Exception e)
{
throw new IllegalArgumentException("Mapping failed at rule " + rule.getValueInteger("sequence") + " (" + rule.getValueString("ediPath") + " -> " + rule.getValueString("qqqPath") + ")");
}
}
return (new Result(result, key(evaluator, map.getValueString("businessKeyPath")), key(evaluator, map.getValueString("secondaryKeyPath"))));
}
/*******************************************************************************
** Conditions and defaults are evaluated within the current loop occurrence.
*******************************************************************************/
private void apply(QRecord rule, RuleEvaluator evaluator, QRecord target, String field, String path) throws Exception
{
String condition = rule.getValueString("condition");
if(condition != null && !condition.isBlank() && !evaluator.test(condition))
{
return;
}
List<String> values = path == null || path.isBlank() ? List.of() : evaluator.values(path);
if(values.isEmpty() && rule.getValueString("defaultValue") != null)
{
values = List.of(rule.getValueString("defaultValue"));
}
Serializable value = transforms.apply(rule.getValueString("transform"), rule.getValueString("transformArgs"), values);
if(Boolean.TRUE.equals(rule.getValueBoolean("required")) && (value == null || value.toString().isBlank()))
{
throw new IllegalArgumentException("Required mapping input is absent");
}
target.withValue(field, value);
}
/*******************************************************************************
** Missing optional business keys remain null.
*******************************************************************************/
private static String key(RuleEvaluator evaluator, String path)
{
List<String> values = path == null || path.isBlank() ? List.of() : evaluator.values(path);
return (values.isEmpty() ? null : values.getFirst());
}
}
@@ -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.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;
/*******************************************************************************
** No-op apply preview with ordinary Result lines; PHI never enters process output.
*******************************************************************************/
public class MapPreview
{
/*******************************************************************************
** Parse, validate, acknowledge and map a stored synthetic sample without target writes.
*******************************************************************************/
public ArrayList<ProcessSummaryLineInterface> preview(QRecord map, QRecord sample) throws Exception
{
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<>();
try(InputStream stream = runtime.payloads().open(new PayloadRef(sample.getValueString("payloadRef"), null, 0)))
{
var interchanges = runtime.reader().read(stream);
lines.add(new ProcessSummaryLine(Status.INFO, interchanges.size(), "interchanges parsed"));
for(var interchange : interchanges)
{
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,195 +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.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kingsrook.qbits.edi.EdiRuntime;
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;
/*******************************************************************************
** Builds ordered segments from record associations. Loop paths define hierarchy;
** rules within a loop share its association occurrence and sequence number.
*******************************************************************************/
public class OutboundMapper
{
private static final Pattern ELEMENT = Pattern.compile("([A-Z][A-Z0-9]{1,2})([0-9]{2})(?:-([0-9]+))?");
/*******************************************************************************
** Load a persisted outbound map's ordered rules.
*******************************************************************************/
public X12TransactionSet build(QRecord source, QRecord map) throws Exception
{
return (build(source, map, EdiRecords.query("edi_map_rule", EdiRecords.equal("mapId", map.getValue("id"))), "0001"));
}
/*******************************************************************************
** Pure mapping entry point; the envelope owns transaction control allocation.
*******************************************************************************/
public X12TransactionSet build(QRecord source, QRecord map, List<QRecord> rules, String control) throws Exception
{
if(!"OUTBOUND".equals(map.getValueString("direction")))
{
throw new IllegalArgumentException("An outbound map is required");
}
List<X12Segment> segments = new ArrayList<>();
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));
return (new X12TransactionSet(segments.getFirst(), segments.getLast(), List.copyOf(segments), List.of()));
}
/*******************************************************************************
** Walk each loop once per associated record, retaining the nearest HL parent.
*******************************************************************************/
private void emit(QRecord source, List<QRecord> rules, String prefix, String qPrefix, int sequence, String parent,
int[] hierarchy, List<X12Segment> output) throws Exception
{
Map<String, List<QRecord>> nodes = new LinkedHashMap<>();
for(QRecord rule : rules)
{
String path = rule.getValueString("ediPath").substring(prefix.length());
String key = path.contains("/") ? path.substring(0, path.indexOf('/') + 1) : path.replaceAll("[0-9]{2}(-[0-9]+)?$", "");
nodes.computeIfAbsent(key, ignored -> new ArrayList<>()).add(rule);
}
String currentParent = parent;
for(var node : nodes.entrySet())
{
if(node.getKey().endsWith("/"))
{
String association = null;
for(QRecord rule : node.getValue())
{
if(rule.getValueString("ediPath").substring((prefix + node.getKey()).length()).contains("/"))
{
continue;
}
String path = rule.getValueString("qqqPath");
if(path != null && path.startsWith(qPrefix) && path.substring(qPrefix.length()).contains("."))
{
association = path.substring(qPrefix.length()).split("\\.")[0];
break;
}
}
List<QRecord> children = association == null ? List.of(source) : Objects.requireNonNullElse(source.getAssociatedRecords() == null ? null : source.getAssociatedRecords().get(association), List.of());
int index = 0;
for(QRecord child : children)
{
emit(child, node.getValue(), prefix + node.getKey(), association == null ? qPrefix : qPrefix + association + ".", ++index, currentParent, hierarchy, output);
}
continue;
}
List<String> elements = new ArrayList<>();
String segmentId = null;
for(QRecord rule : node.getValue())
{
String path = rule.getValueString("ediPath");
var match = ELEMENT.matcher(path.substring(path.lastIndexOf('/') + 1));
if(!match.matches() || match.group(3) != null)
{
throw new IllegalArgumentException("Unsupported outbound element path");
}
segmentId = match.group(1);
int position = Integer.parseInt(match.group(2));
if(position < 1 || position > 99)
{
throw new IllegalArgumentException("Invalid outbound element position");
}
while(elements.size() < position)
{
elements.add("");
}
String condition = rule.getValueString("condition");
if(condition != null && !condition.isBlank() && !new RuleEvaluator(output, Map.of()).test(condition))
{
continue;
}
String qPath = rule.getValueString("qqqPath");
Object raw = qPath == null ? null : source.getValue(qPath.startsWith(qPrefix) ? qPath.substring(qPrefix.length()) : qPath);
String transform = Objects.requireNonNullElse(rule.getValueString("transform"), "");
String argsJson = rule.getValueString("transformArgs");
Map<String, String> args = argsJson == null ? Map.of() : new ObjectMapper().readValue(argsJson, new TypeReference<Map<String, String>>() {});
String value;
if(transform.equals("HL"))
{
value = switch(position)
{
case 1 -> String.valueOf(++hierarchy[0]);
case 2 -> parent;
case 3 -> args.get("code");
case 4 -> source.getAssociatedRecords() != null && source.getAssociatedRecords().values().stream().anyMatch(v -> !v.isEmpty()) ? "1" : "0";
default -> throw new IllegalArgumentException("HL supports positions 1 through 4");
};
}
else if(transform.equals("SEQUENCE"))
{
value = String.valueOf(sequence + Integer.parseInt(args.getOrDefault("start", "1")) - 1);
}
else if(raw == null && !transform.equals("CONSTANT"))
{
value = rule.getValueString("defaultValue");
}
else if(transform.equals("DATE"))
{
value = java.time.LocalDate.parse(raw.toString()).format(java.time.format.DateTimeFormatter.BASIC_ISO_DATE);
}
else if(transform.equals("TIME"))
{
value = java.time.LocalTime.parse(raw.toString()).format(java.time.format.DateTimeFormatter.ofPattern("HHmmss"));
}
else if(transform.equals("DECIMAL"))
{
value = new java.math.BigDecimal(raw.toString()).movePointRight(Integer.parseInt(args.getOrDefault("scale", "0"))).stripTrailingZeros().toPlainString();
}
else
{
Object transformed = EdiRuntime.current().transforms().apply(transform, argsJson, raw == null ? List.of() : List.of(raw.toString()));
value = transformed == null ? rule.getValueString("defaultValue") : transformed.toString();
}
if((value == null || value.isBlank()) && Boolean.TRUE.equals(rule.getValueBoolean("required")))
{
throw new IllegalArgumentException("Required outbound field is missing");
}
elements.set(position - 1, Objects.requireNonNullElse(value, ""));
}
if(elements.stream().anyMatch(v -> !v.isEmpty()))
{
output.add(X12Segment.of(segmentId, output.size() + 1, elements.toArray(String[]::new)));
if("HL".equals(segmentId))
{
currentParent = elements.getFirst();
}
}
}
}
}
@@ -1,175 +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.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kingsrook.qbits.edi.storage.EdiRecords;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
/*******************************************************************************
** Named, host-extensible transforms. Arguments are a JSON object, never code.
*******************************************************************************/
public class TransformRegistry
{
private final Map<String, Transform> transforms = new ConcurrentHashMap<>();
/*******************************************************************************
** Transform a selected path's values in the service session.
*******************************************************************************/
@FunctionalInterface
public interface Transform
{
/*******************************************************************************
** Return a QQQ-compatible field value.
*******************************************************************************/
Serializable apply(List<String> values, Map<String, String> args) throws Exception;
}
/*******************************************************************************
** Install built-in inbound transforms and explicit outbound placeholders.
*******************************************************************************/
public TransformRegistry()
{
register("CONSTANT", (v, a) -> a.get("value"));
register("TRIM", (v, a) -> first(v).trim());
register("UPPER", (v, a) -> first(v).toUpperCase(Locale.ROOT));
register("DATE", (v, a) -> date(first(v)));
register("TIME", (v, a) -> time(first(v)).toString());
register("DATETIME", (v, a) -> date(first(v)).atTime(time(v.size() > 1 ? v.get(1) : a.getOrDefault("time", "0000")))
.atZone(ZoneId.of(a.getOrDefault("timezone", "UTC"))).toInstant());
register("DECIMAL", (v, a) -> new BigDecimal(first(v)).movePointLeft(Integer.parseInt(a.getOrDefault("scale", "0"))));
register("CONCAT", (v, a) -> String.join(a.getOrDefault("separator", ""), v));
register("SPLIT", (v, a) -> first(v).split(java.util.regex.Pattern.quote(a.getOrDefault("separator", ",")), -1)[Integer.parseInt(a.getOrDefault("index", "0"))]);
register("SUBSTRING", (v, a) -> first(v).substring(Integer.parseInt(a.getOrDefault("start", "0")), Integer.parseInt(a.getOrDefault("end", String.valueOf(first(v).length())))));
register("CODE", (v, a) ->
{
String translated = a.get(first(v));
if(translated == null)
{
throw new IllegalArgumentException("Code is not mapped");
}
return (translated);
});
register("QUALIFIER", (v, a) -> first(v));
register("UNIT", (v, a) -> new BigDecimal(first(v)).multiply(new BigDecimal(a.getOrDefault("factor", "1")))
.divide(new BigDecimal(a.getOrDefault("divisor", "1")), Integer.parseInt(a.getOrDefault("scale", "6")), RoundingMode.HALF_UP));
register("LOOKUP", (v, a) ->
{
QueryInput input = new QueryInput(a.get("table")).withFilter(EdiRecords.equal(a.get("key"), first(v)).withLimit(2));
input.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
var rows = new QueryAction().execute(input).getRecords();
if(rows.size() != 1)
{
throw new IllegalArgumentException("Lookup must match exactly one record");
}
return (rows.getFirst().getValue(a.getOrDefault("result", "id")));
});
for(String name : List.of("SEQUENCE", "HL"))
{
register(name, (v, a) ->
{
throw new UnsupportedOperationException("Outbound transform is not available in phase a");
});
}
}
/*******************************************************************************
** Register or replace a named host transform.
*******************************************************************************/
public TransformRegistry register(String name, Transform transform)
{
transforms.put(name.toUpperCase(Locale.ROOT), transform);
return (this);
}
/*******************************************************************************
** Execute a closed registry entry; unknown names fail explicitly.
*******************************************************************************/
public Serializable apply(String name, String args, List<String> values) throws Exception
{
if(name == null || name.isBlank())
{
return (values.isEmpty() ? null : values.getFirst());
}
Transform transform = transforms.get(name.toUpperCase(Locale.ROOT));
if(transform == null)
{
throw new IllegalArgumentException("Unknown transform");
}
Map<String, String> arguments = args == null || args.isBlank() ? Map.of() : new ObjectMapper().readValue(args, new TypeReference<Map<String, String>>() {});
return (transform.apply(values, arguments));
}
/*******************************************************************************
** Require one input value without echoing it in diagnostics.
*******************************************************************************/
private static String first(List<String> values)
{
if(values.isEmpty())
{
throw new IllegalArgumentException("Transform input is missing");
}
return (values.getFirst());
}
/*******************************************************************************
** Six-digit dates use the fixed 1950-2049 century window.
*******************************************************************************/
private static LocalDate date(String value)
{
DateTimeFormatter format = value.length() == 6 ? new DateTimeFormatterBuilder().appendValueReduced(ChronoField.YEAR, 2, 2, 1950)
.appendPattern("MMdd").toFormatter() : DateTimeFormatter.BASIC_ISO_DATE;
return (LocalDate.parse(value, format));
}
/*******************************************************************************
** X12 HHMM, HHMMSS and fractional seconds.
*******************************************************************************/
private static LocalTime time(String value)
{
String normalized = value.substring(0, 2) + ":" + value.substring(2, 4);
if(value.length() > 4)
{
normalized += ":" + value.substring(4, 6);
if(value.length() > 6)
{
normalized += "." + value.substring(6).replace(".", "");
}
}
return (LocalTime.parse(normalized));
}
}
@@ -1,61 +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.List;
import java.util.Objects;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Default healthcare classification and validation depth when configuring a document.
*******************************************************************************/
public class AgreementDocumentDefaults implements TableCustomizerInterface
{
/*******************************************************************************
** Preserve explicit values and existing edits; default new records by document family.
*******************************************************************************/
@Override
public List<QRecord> preInsertOrUpdate(AbstractActionInput input, List<QRecord> records, boolean preview, Optional<List<QRecord>> oldRecords)
{
for(QRecord record : records)
{
QRecord merged = new QRecord();
oldRecords.orElse(List.of()).stream().filter(old -> Objects.equals(old.getValue("id"), record.getValue("id")))
.findFirst().ifPresent(old -> merged.getValues().putAll(old.getValues()));
merged.getValues().putAll(record.getValues());
boolean healthcare = List.of("837", "835", "270", "271", "276", "277", "834").contains(Objects.toString(merged.getValue("transactionSet"), ""));
if(merged.getValue("containsPhi") == null)
{
record.setValue("containsPhi", healthcare);
}
if(merged.getValue("snipLevel") == null)
{
record.setValue("snipLevel", healthcare ? 4 : 2);
}
}
return (records);
}
}
@@ -1,83 +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.List;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** RC.5 form filters; no dependency on later dependency-behavior metadata.
*******************************************************************************/
public final class EdiContingencies
{
/*******************************************************************************
** Restrict every partner-scoped form reference to its owning context.
*******************************************************************************/
public static void apply(List<QTableMetaData> tables)
{
for(QTableMetaData table : tables)
{
for(var field : table.getFields().values())
{
String source = field.getPossibleValueSourceName();
if(source == null)
{
continue;
}
QQueryFilter filter = null;
if(table.getFields().containsKey("partnerId") && List.of("edi_identity", "edi_transport", "edi_certificate", "edi_guide", "edi_map").contains(source))
{
filter = new QQueryFilter(new QFilterCriteria("partnerId", QCriteriaOperator.EQUALS, "${input.partnerId}"));
if(List.of("localIdentityId", "localCertificateId").contains(field.getName()))
{
filter = new QQueryFilter(new QFilterCriteria("partnerId", QCriteriaOperator.IS_BLANK));
}
else if(List.of("edi_transport", "edi_guide", "edi_map").contains(source))
{
filter = new QQueryFilter().withSubFilter(filter).withCriteria("partnerId", QCriteriaOperator.IS_BLANK)
.withBooleanOperator(QQueryFilter.BooleanOperator.OR);
}
}
if(table.getName().equals("edi_map") && field.getName().equals("guideId"))
{
filter = new QQueryFilter().withSubFilter(filter).withCriteria("transactionSet", QCriteriaOperator.EQUALS, "${input.transactionSet}");
}
if(table.getName().equals("edi_agreement_document") && List.of("guideId", "mapId").contains(field.getName()))
{
filter = new QQueryFilter().withCriteria("transactionSet", QCriteriaOperator.EQUALS, "${input.transactionSet}")
.withCriteria("version", QCriteriaOperator.EQUALS, "${input.version}");
}
if(table.getName().equals("edi_interchange") && field.getName().equals("originalInterchangeId"))
{
filter = new QQueryFilter().withCriteria("partnerId", QCriteriaOperator.EQUALS, "${input.partnerId}")
.withCriteria("agreementId", QCriteriaOperator.EQUALS, "${input.agreementId}").withCriteria("direction", QCriteriaOperator.EQUALS, "OUTBOUND");
}
if(filter != null)
{
field.withPossibleValueSourceFilter(filter);
}
}
}
}
}
@@ -1,90 +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 com.kingsrook.qbits.edi.EdiQBitConfig;
import com.kingsrook.qbits.edi.EdiQBitProducer;
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.qbits.QBitComponentMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Install tables, relations, record widgets and value sources.
*******************************************************************************/
public class EdiMetaDataProducer extends QBitComponentMetaDataProducer<MetaDataProducerMultiOutput, EdiQBitConfig>
{
/*******************************************************************************
** Only run when configured by the owning producer.
*******************************************************************************/
@Override
public boolean isEnabled()
{
return (getQBitConfig() != null);
}
/*******************************************************************************
** Produce tables, the typed app home, record actions and alert types.
*******************************************************************************/
@Override
public MetaDataProducerMultiOutput produce(QInstance instance) throws QException
{
MetaDataProducerMultiOutput output = new MetaDataProducerMultiOutput();
EdiTables.possibleValueSources().forEach(output::add);
EdiTables.joins().forEach(output::add);
EdiTables.childWidgets().forEach(output::add);
output.add(new QSecurityKeyType().withName("edi_partner").withAllAccessKeyName("edi.admin"));
output.add(new QSecurityKeyType().withName("edi_guide_source").withAllAccessKeyName("edi.guide.loader"));
java.util.List<QTableMetaData> tables = EdiTables.tables(getQBitConfig());
for(QTableMetaData table : tables)
{
table.setSourceQBitName(instance.getQBits().values().stream().filter(q -> EdiQBitProducer.ARTIFACT_ID.equals(q.getArtifactId())).findFirst().orElseThrow().getName());
output.add(table);
}
if(getQBitConfig().getEsbDestinationName() != null && !getQBitConfig().getEsbDestinationName().isBlank())
{
instance.withRecordChangeListener(new com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference(com.kingsrook.qbits.edi.support.EdiDocumentEvents.class));
}
output.add(com.kingsrook.qbits.edi.app.EdiHome.app(getQBitConfig(), tables));
com.kingsrook.qbits.edi.app.EdiHome.widgets().forEach(output::add);
for(String name : java.util.List.of("edi.inbox.dead-letter", "edi.ack.overdue", "edi.ack.rejected", "edi.send.failed", "edi.certificate.expiring", "edi.certificate.expired"))
{
output.add(new com.kingsrook.qqq.backend.core.alerts.QAlertTypeMetaData().withName(name).withLabel(name)
.withSourceName("edi").withDefaultSeverity(com.kingsrook.qqq.backend.core.alerts.AlertSeverity.ERROR)
.withDescription("EDI operational exception").withHowToFix("Review the related EDI records")
.withVisibilityPermission("edi.exception.read"));
}
com.kingsrook.qbits.edi.processes.EdiProcesses.processes(getQBitConfig()).forEach(output::add);
output.add(new com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData().withName("edi_preview_download")
.withBackendType(com.kingsrook.qbits.edi.storage.PreviewDownloadBackend.class));
output.add(new QTableMetaData().withName("edi_preview_download").withLabel("Preview Download").withBackendName("edi_preview_download")
.withPrimaryKeyField("id").withField(new com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData("id", com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType.INTEGER).withLabel("ID"))
.withPermissionRules(new com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules().withPermissionBaseName("edi.map.preview")
.withLevel(com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel.HAS_ACCESS_PERMISSION))
.withoutCapabilities(com.kingsrook.qqq.backend.core.model.metadata.tables.Capability.TABLE_INSERT,
com.kingsrook.qqq.backend.core.model.metadata.tables.Capability.TABLE_UPDATE, com.kingsrook.qqq.backend.core.model.metadata.tables.Capability.TABLE_DELETE));
return (output);
}
}
@@ -1,114 +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.List;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Stable operator labels, including a PHI-safe fallback for documents without keys.
*******************************************************************************/
public class EdiRecordLabels implements TableCustomizerInterface, QSupplementalInstanceMetaData
{
/*******************************************************************************
** Instance enrichment runs after core has supplied default table formats.
*******************************************************************************/
@Override
public String getName()
{
return ("edi.recordLabels");
}
/*******************************************************************************
** Keep search/security label fields, while letting the query customizer supply
** the document label. RC.5 otherwise generates a fixed format from those fields.
*******************************************************************************/
@Override
public void enrich(QInstance instance)
{
instance.getTable("edi_transaction").setRecordLabelFormat(null);
}
/*******************************************************************************
** Use native formats except where the label depends on available business keys.
*******************************************************************************/
public static void configure(QTableMetaData table)
{
switch(table.getName())
{
case "edi_identity" -> table.withRecordLabelFormatAndFields("%s %s", "interchangeId", "applicationCode");
case "edi_agreement" -> table.withRecordLabelFormatAndFields("%s %s", "partnerId", "usageIndicator");
case "edi_agreement_document" -> table.withRecordLabelFormatAndFields("%s %s %s", "transactionSet", "direction", "version");
case "edi_control_number" -> table.withRecordLabelFormatAndFields("%s %s", "level", "lastValue");
case "edi_guide", "edi_map" -> table.withRecordLabelFormatAndFields("%s r%s", "name", "revision");
case "edi_guide_rule" -> table.withRecordLabelFormatAndFields("%s: %s", "type", "message");
case "edi_map_rule" -> table.withRecordLabelFormatAndFields("%s: %s", "ediPath", "qqqPath");
case "edi_interchange" -> table.withRecordLabelFormatAndFields("%s ISA %s", "direction", "controlNumber");
case "edi_functional_group" -> table.withRecordLabelFormatAndFields("GS %s", "controlNumber");
case "edi_exception" -> table.withRecordLabelFormatAndFields("%s: %s", "type", "summary");
case "edi_inbox" -> table.withRecordLabelFormatAndFields("%s %s", "transportType", "externalId");
case "edi_outbound_message" -> table.withRecordLabelFormatAndFields("%s %s", "transportId", "status");
case "edi_outbound_request" -> table.withRecordLabelFormatAndFields("%s %s %s", "sourceTableName", "sourceRecordId", "status");
case "edi_transaction_error" -> table.withRecordLabelFormatAndFields("%s: %s", "severity", "message");
case "edi_event" -> table.withRecordLabelFormatAndFields("%s %s", "type", "at");
case "edi_transaction" -> table.withRecordLabelFormat(null)
.withRecordLabelFields("transactionSet", "businessKeyType", "businessKey", "controlNumber", "containsPhi")
.withCustomizer(TableCustomizers.POST_QUERY_RECORD, new QCodeReference(EdiRecordLabels.class));
default ->
{
// Tables with a natural name retain their base record-label field.
}
}
}
/*******************************************************************************
** Queries, record reads and table value sources all use this same fallback.
*******************************************************************************/
@Override
public List<QRecord> postQuery(QueryOrGetInputInterface input, List<QRecord> records)
{
for(QRecord record : records)
{
String key = record.getValueString("businessKey");
String type = record.getValueString("businessKeyType");
String suffix = !Boolean.TRUE.equals(record.getValueBoolean("containsPhi")) && StringUtils.hasContent(key) && StringUtils.hasContent(type)
? type + " " + key : "ST " + record.getValueString("controlNumber");
record.setRecordLabel(record.getValueString("transactionSet") + " " + suffix);
}
return (records);
}
}
@@ -1,205 +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.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
/*******************************************************************************
** Operator vocabulary and scan order shared by lists, records and child widgets.
*******************************************************************************/
public final class EdiTablePresentation
{
private static final Set<String> ACRONYMS = Set.of("ID", "PHI", "PII", "URL", "ISA", "GS", "ST", "AS2", "SFTP", "HTTPS", "HTTP", "MDN", "TLS", "TA1", "EDI", "UPC", "NPI", "QQQ", "JSON", "PEM", "SHA256", "SNIP", "VAN", "AK3", "AK4", "IK3", "IK4");
/*******************************************************************************
** Explicit singular names; unknown tables must declare their vocabulary.
*******************************************************************************/
public static String label(String table)
{
return (switch(table)
{
case "edi_partner" -> "Trading Partner";
case "edi_identity" -> "EDI Identity";
case "edi_agreement" -> "Agreement";
case "edi_agreement_document" -> "Agreement Document";
case "edi_control_number" -> "Control Number";
case "edi_transport" -> "Transport";
case "edi_certificate" -> "Certificate";
case "edi_guide" -> "Implementation Guide";
case "edi_guide_segment" -> "Guide Segment";
case "edi_guide_element" -> "Guide Element";
case "edi_guide_rule" -> "Guide Rule";
case "edi_code_list" -> "Code List";
case "edi_code_list_value" -> "Code";
case "edi_map" -> "Map";
case "edi_map_rule" -> "Map Rule";
case "edi_map_sample" -> "Map Sample";
case "edi_interchange" -> "Interchange";
case "edi_functional_group" -> "Functional Group";
case "edi_transaction" -> "Document";
case "edi_transaction_error" -> "Validation Error";
case "edi_event" -> "Event";
case "edi_exception" -> "Exception";
case "edi_inbox" -> "Inbound Message";
case "edi_outbound_request" -> "Outbound Request";
case "edi_outbound_message" -> "Outbound Message";
case "edi_payload" -> "Payload";
default -> throw new IllegalArgumentException("Missing EDI table label: " + table);
});
}
/*******************************************************************************
** Child sections use the plain plural, without database relationship syntax.
*******************************************************************************/
public static String pluralLabel(String table)
{
return (switch(table)
{
case "edi_identity" -> "Identities";
case "edi_guide" -> "Guides";
case "edi_guide_segment" -> "Segments";
case "edi_guide_element" -> "Elements";
case "edi_guide_rule" -> "Rules";
case "edi_map_sample" -> "Samples";
default -> label(table) + "s";
});
}
/*******************************************************************************
** Keep field insertion order and record sections aligned, with dates last.
*******************************************************************************/
public static void apply(QTableMetaData table)
{
table.setLabel(label(table.getName()));
List<String> summary = leadingFields(table.getName());
Map<String, QFieldMetaData> ordered = new LinkedHashMap<>();
summary.forEach(name -> ordered.put(name, table.getField(name)));
table.getFields().forEach(ordered::putIfAbsent);
List<String> details = new ArrayList<>();
List<String> dates = new ArrayList<>();
for(QFieldMetaData field : ordered.values())
{
field.setLabel(fieldLabel(field));
if(summary.contains(field.getName()) || Boolean.TRUE.equals(field.getIsHidden()))
{
continue;
}
(field.getType() == QFieldType.DATE_TIME || field.getType() == QFieldType.DATE ? dates : details).add(field.getName());
}
Map<String, QFieldMetaData> fields = new LinkedHashMap<>();
for(List<String> section : List.of(summary, details, dates))
{
section.forEach(name -> fields.put(name, ordered.get(name)));
}
ordered.forEach(fields::putIfAbsent);
table.setFields(fields);
table.setSections(new ArrayList<>(List.of(
new QFieldSection("identity", new QIcon("badge"), Tier.T1, new ArrayList<>(summary)).withLabel("Overview"),
new QFieldSection("details", new QIcon("description"), Tier.T2, details).withLabel("Details"),
new QFieldSection("dates", new QIcon("schedule"), Tier.T3, dates).withLabel("Dates"))));
}
/*******************************************************************************
** References name the linked record; external identifiers retain the ID suffix.
*******************************************************************************/
private static String fieldLabel(QFieldMetaData field)
{
String name = switch(field.getName())
{
case "transactionId" -> "document";
case "ackTransactionId" -> "ackDocument";
case "inboxId" -> "inboundMessage";
case "assignedToUserId" -> "assignedTo";
default -> field.getName();
};
if(name.endsWith("Id") && field.getPossibleValueSourceName() != null)
{
name = name.substring(0, name.length() - 2);
}
List<String> words = new ArrayList<>();
for(String word : name.replaceAll("([a-z0-9])([A-Z])", "$1 $2").split(" "))
{
String upper = word.toUpperCase(Locale.ROOT);
words.add(ACRONYMS.contains(upper) ? upper : Character.toUpperCase(word.charAt(0)) + word.substring(1));
}
return (String.join(" ", words));
}
/*******************************************************************************
** The first columns answer what happened, to whom, and what needs attention.
*******************************************************************************/
private static List<String> leadingFields(String table)
{
return (switch(table)
{
case "edi_partner" -> List.of("name", "code", "status", "contactName", "contactEmail", "contactPhone", "containsPhi");
case "edi_identity" -> List.of("interchangeId", "interchangeQualifier", "applicationCode", "partnerId", "standard", "isActive");
case "edi_agreement" -> List.of("partnerId", "localIdentityId", "partnerIdentityId", "standard", "usageIndicator", "isActive", "inboundTransportId", "outboundTransportId");
case "edi_agreement_document" -> List.of("transactionSet", "direction", "version", "agreementId", "isActive", "guideId", "mapId", "ackExpectation");
case "edi_control_number" -> List.of("level", "lastValue", "agreementId", "localIdentityId", "minValue", "maxValue", "wrapAllowed");
case "edi_transport" -> List.of("name", "type", "direction", "partnerId", "isActive");
case "edi_certificate" -> List.of("alias", "status", "usage", "partnerId", "notAfter", "subject", "issuer");
case "edi_guide" -> List.of("name", "revision", "status", "transactionSet", "version", "standard", "partnerId", "source");
case "edi_guide_segment" -> List.of("segmentId", "name", "position", "sequence", "usage", "loopId", "guideId");
case "edi_guide_element" -> List.of("name", "elementId", "position", "componentPosition", "usage", "dataType", "guideSegmentId");
case "edi_guide_rule" -> List.of("type", "message", "severity", "positions", "guideId", "guideSegmentId");
case "edi_code_list" -> List.of("name", "elementId", "source", "externalCodeSet");
case "edi_code_list_value" -> List.of("code", "description", "isActive", "codeListId");
case "edi_map" -> List.of("name", "revision", "status", "direction", "transactionSet", "version", "partnerId", "guideId", "tableName");
case "edi_map_rule" -> List.of("sequence", "ediPath", "qqqPath", "transform", "required", "mapId");
case "edi_map_sample" -> List.of("name", "mapId", "lastPreviewStatus", "lastPreviewAt");
case "edi_interchange" -> List.of("direction", "partnerId", "controlNumber", "status", "interchangeDate", "transportId", "groupCount");
case "edi_functional_group" -> List.of("controlNumber", "functionalIdCode", "version", "transactionCount", "ackStatus", "interchangeId");
case "edi_transaction" -> List.of("transactionSet", "businessKeyType", "businessKey", "partnerId", "direction", "status", "ackStatus", "version", "interchangeId");
case "edi_transaction_error" -> List.of("severity", "message", "segmentId", "segmentPosition", "elementPosition", "transactionId");
case "edi_event" -> List.of("type", "at", "detail", "transactionId", "interchangeId", "inboxId");
case "edi_exception" -> List.of("type", "status", "summary", "partnerId", "transactionId", "interchangeId", "assignedToUserId", "occurrences");
case "edi_inbox" -> List.of("transportType", "externalId", "status", "partnerId", "transportId", "receivedAt", "attempts");
case "edi_outbound_request" -> List.of("status", "agreementDocumentId", "sourceTableName", "sourceRecordId", "attempts", "nextAttemptAt");
case "edi_outbound_message" -> List.of("transportId", "status", "partnerId", "agreementId", "attempts", "sentAt", "deliveredAt");
case "edi_payload" -> List.of("kind", "sizeBytes", "sha256");
default -> throw new IllegalArgumentException("Missing EDI field order: " + table);
});
}
}
@@ -1,452 +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.ArrayList;
import java.util.List;
import com.kingsrook.qbits.edi.EdiQBitConfig;
import com.kingsrook.qbits.edi.model.EdiAgreement;
import com.kingsrook.qbits.edi.model.EdiAgreementDocument;
import com.kingsrook.qbits.edi.model.EdiCertificate;
import com.kingsrook.qbits.edi.model.EdiCodeList;
import com.kingsrook.qbits.edi.model.EdiCodeListValue;
import com.kingsrook.qbits.edi.model.EdiControlNumber;
import com.kingsrook.qbits.edi.model.EdiEvent;
import com.kingsrook.qbits.edi.model.EdiException;
import com.kingsrook.qbits.edi.model.EdiFunctionalGroup;
import com.kingsrook.qbits.edi.model.EdiGuide;
import com.kingsrook.qbits.edi.model.EdiGuideElement;
import com.kingsrook.qbits.edi.model.EdiGuideRule;
import com.kingsrook.qbits.edi.model.EdiGuideSegment;
import com.kingsrook.qbits.edi.model.EdiIdentity;
import com.kingsrook.qbits.edi.model.EdiInbox;
import com.kingsrook.qbits.edi.model.EdiInterchange;
import com.kingsrook.qbits.edi.model.EdiMap;
import com.kingsrook.qbits.edi.model.EdiMapRule;
import com.kingsrook.qbits.edi.model.EdiMapSample;
import com.kingsrook.qbits.edi.model.EdiOutboundMessage;
import com.kingsrook.qbits.edi.model.EdiOutboundRequest;
import com.kingsrook.qbits.edi.model.EdiPartner;
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.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
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.possiblevalues.QPossibleValue;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
/*******************************************************************************
** Tables, relations and value sources for the EDI data model.
*******************************************************************************/
public final class EdiTables
{
/*******************************************************************************
** Build annotated tables and complete record screens.
*******************************************************************************/
public static List<QTableMetaData> tables(EdiQBitConfig config) throws QException
{
List<QTableMetaData> tables = new ArrayList<>();
QTableMetaData partner = base(config, EdiPartner.TABLE_NAME, EdiPartner.class, "name", false);
partner.withUniqueKey(new UniqueKey("name"));
partner.withUniqueKey(new UniqueKey("code"));
tables.add(partner);
QTableMetaData identity = base(config, EdiIdentity.TABLE_NAME, EdiIdentity.class, "id", false);
identity.withUniqueKey(new UniqueKey("standard", "interchangeQualifier", "interchangeId", "applicationCode"));
tables.add(identity);
QTableMetaData agreement = base(config, EdiAgreement.TABLE_NAME, EdiAgreement.class, "id", false);
agreement.withUniqueKey(new UniqueKey("localIdentityId", "partnerIdentityId", "standard", "usageIndicator"));
tables.add(agreement);
QTableMetaData agreementDocument = base(config, EdiAgreementDocument.TABLE_NAME, EdiAgreementDocument.class, "id", false);
agreementDocument.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(AgreementDocumentDefaults.class))
.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(AgreementDocumentDefaults.class));
agreementDocument.withUniqueKey(new UniqueKey("agreementId", "direction", "transactionSet", "version"));
tables.add(agreementDocument);
QTableMetaData controlNumber = base(config, EdiControlNumber.TABLE_NAME, EdiControlNumber.class, "id", false);
controlNumber.getField("scopeKey").withIsHidden(true).withIsEditable(false);
controlNumber.getSections().forEach(section -> section.getFieldNames().remove("scopeKey"));
controlNumber.withUniqueKey(new UniqueKey("scopeKey", "level"));
tables.add(controlNumber);
QTableMetaData transport = base(config, EdiTransport.TABLE_NAME, EdiTransport.class, "name", false);
transport.withUniqueKey(new UniqueKey("name"));
transport.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(TransportValidation.class))
.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(TransportValidation.class));
tables.add(transport);
QTableMetaData certificate = base(config, EdiCertificate.TABLE_NAME, EdiCertificate.class, "alias", false);
tables.add(certificate);
QTableMetaData guide = base(config, EdiGuide.TABLE_NAME, EdiGuide.class, "name", false);
guide.withUniqueKey(new UniqueKey("name", "revision"));
guide.withRecordSecurityLock(new RecordSecurityLock().withFieldName("source").withSecurityKeyType("edi_guide_source").withLockScope(RecordSecurityLock.LockScope.WRITE));
tables.add(guide);
QTableMetaData guideSegment = base(config, EdiGuideSegment.TABLE_NAME, EdiGuideSegment.class, "name", false);
tables.add(guideSegment);
QTableMetaData guideElement = base(config, EdiGuideElement.TABLE_NAME, EdiGuideElement.class, "name", false);
tables.add(guideElement);
QTableMetaData guideRule = base(config, EdiGuideRule.TABLE_NAME, EdiGuideRule.class, "id", false);
tables.add(guideRule);
QTableMetaData codeList = base(config, EdiCodeList.TABLE_NAME, EdiCodeList.class, "name", false);
tables.add(codeList);
QTableMetaData codeListValue = base(config, EdiCodeListValue.TABLE_NAME, EdiCodeListValue.class, "code", false);
codeListValue.withUniqueKey(new UniqueKey("codeListId", "code"));
tables.add(codeListValue);
QTableMetaData interchange = base(config, EdiInterchange.TABLE_NAME, EdiInterchange.class, "controlNumber", true);
tables.add(interchange);
QTableMetaData functionalGroup = base(config, EdiFunctionalGroup.TABLE_NAME, EdiFunctionalGroup.class, "controlNumber", true);
tables.add(functionalGroup);
QTableMetaData transaction = base(config, EdiTransaction.TABLE_NAME, EdiTransaction.class, "controlNumber", true);
tables.add(transaction);
QTableMetaData transactionError = base(config, EdiTransactionError.TABLE_NAME, EdiTransactionError.class, "id", true);
tables.add(transactionError);
QTableMetaData event = base(config, EdiEvent.TABLE_NAME, EdiEvent.class, "id", true);
tables.add(event);
QTableMetaData exception = base(config, EdiException.TABLE_NAME, EdiException.class, "summary", false);
tables.add(exception);
QTableMetaData inbox = base(config, EdiInbox.TABLE_NAME, EdiInbox.class, "id", true);
inbox.withUniqueKey(new UniqueKey("transportType", "externalId"));
tables.add(inbox);
QTableMetaData payload = base(config, EdiPayload.TABLE_NAME, EdiPayload.class, "kind", true);
tables.add(payload);
tables.add(base(config, "edi_map", EdiMap.class, "name", false));
tables.add(base(config, "edi_map_rule", EdiMapRule.class, "id", false));
tables.add(base(config, "edi_map_sample", EdiMapSample.class, "name", false));
tables.add(base(config, "edi_outbound_request", EdiOutboundRequest.class, "id", true).withUniqueKey(new UniqueKey("agreementDocumentId", "eventId")));
tables.add(base(config, "edi_outbound_message", EdiOutboundMessage.class, "id", true));
for(QTableMetaData table : tables)
{
List<String> guidePath = switch(table.getName())
{
case "edi_guide_segment" -> List.of("edi_guide_guide_id_edi_guide_segment");
case "edi_guide_rule" -> List.of("edi_guide_guide_id_edi_guide_rule");
case "edi_guide_element" -> List.of("edi_guide_guide_id_edi_guide_segment", "edi_guide_segment_guide_segment_id_edi_guide_element");
default -> List.of();
};
if(!guidePath.isEmpty())
{
table.withRecordSecurityLock(new RecordSecurityLock().withFieldName("edi_guide.source").withSecurityKeyType("edi_guide_source")
.withLockScope(RecordSecurityLock.LockScope.WRITE).withJoinNameChain(guidePath));
}
if(config.getPartnerLocks() && !table.getFields().containsKey("partnerId"))
{
List<String> path = switch(table.getName())
{
case "edi_agreement_document" -> List.of("edi_agreement_agreement_id_edi_agreement_document");
case "edi_control_number" -> List.of("edi_agreement_agreement_id_edi_control_number");
case "edi_functional_group" -> List.of("edi_interchange_interchange_id_edi_functional_group");
case "edi_transaction_error" -> List.of("edi_transaction_transaction_id_edi_transaction_error");
case "edi_event" -> List.of("edi_interchange_interchange_id_edi_event");
case "edi_map_rule" -> List.of("edi_map_map_id_edi_map_rule");
case "edi_map_sample" -> List.of("edi_map_map_id_edi_map_sample");
case "edi_guide_segment" -> List.of("edi_guide_guide_id_edi_guide_segment");
case "edi_guide_rule" -> List.of("edi_guide_guide_id_edi_guide_rule");
case "edi_guide_element" -> List.of("edi_guide_guide_id_edi_guide_segment", "edi_guide_segment_guide_segment_id_edi_guide_element");
default -> List.of();
};
if(!path.isEmpty())
{
String parent = switch(table.getName())
{
case "edi_agreement_document", "edi_control_number" -> "edi_agreement";
case "edi_functional_group", "edi_event" -> "edi_interchange";
case "edi_transaction_error" -> "edi_transaction";
case "edi_map_rule", "edi_map_sample" -> "edi_map";
default -> "edi_guide";
};
table.withRecordSecurityLock(new RecordSecurityLock().withFieldName(parent + ".partnerId").withSecurityKeyType("edi_partner").withJoinNameChain(path));
}
}
for(QJoinMetaData join : joins())
{
if(join.getLeftTable().equals(table.getName()))
{
table.withExposedJoin(new ExposedJoin().withLabel(joinLabel(join)).withJoinTable(join.getRightTable()).withJoinPath(List.of(join.getName())));
table.withSection(new QFieldSection().withName(join.getName()).withLabel(joinLabel(join)).withTier(Tier.T2).withWidgetName(join.getName()));
}
}
}
for(QTableMetaData table : tables)
{
if(com.kingsrook.qbits.edi.app.EdiHome.ACTIVITY.contains(table.getName()) || com.kingsrook.qbits.edi.app.EdiHome.SETUP.contains(table.getName()))
{
table.withSearchFields(table.getFields().values().stream().filter(f -> List.of("id", "name", "code", "controlNumber", "businessKey", "secondaryKey", "summary", "alias").contains(f.getName())).map(QFieldMetaData::getName).toList());
}
}
tables.forEach(t -> com.kingsrook.qbits.edi.support.EdiIntegrations.configure(t, config.getEsbDestinationName()));
EdiContingencies.apply(tables);
return (tables);
}
/*******************************************************************************
** Common permissions, audit, field names and optional partner isolation.
*******************************************************************************/
private static QTableMetaData base(EdiQBitConfig config, String name, Class<? extends QRecordEntity> entity, String label, boolean readOnly) throws QException
{
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 ? AuditLevel.RECORD : AuditLevel.FIELD));
QRecordEntity.registerTable(entity, table);
if(List.of("edi_payload", "edi_certificate", "edi_inbox").contains(name))
{
table.withSupplementalMetaData(new AiTableMetaData().withExposure(AiExposure.NONE));
}
if(name.equals("edi_exception"))
{
table.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_DELETE);
table.getFields().values().forEach(field -> field.setIsEditable(List.of("status", "assignedToUserId", "resolution").contains(field.getName())));
}
if(readOnly)
{
table.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
}
if(config.getPartnerLocks() && table.getFields().containsKey("partnerId"))
{
table.withRecordSecurityLock(new RecordSecurityLock().withFieldName("partnerId").withSecurityKeyType("edi_partner"));
}
for(QFieldMetaData field : table.getFields().values())
{
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"))
{
field.withSupplementalMetaData(new AiFieldMetaData().withExposure(AiExposure.NONE));
}
}
EdiTablePresentation.apply(table);
EdiRecordLabels.configure(table);
return (table);
}
/*******************************************************************************
** One source for each enumeration and referenced table.
*******************************************************************************/
public static List<QPossibleValueSource> possibleValueSources()
{
List<QPossibleValueSource> sources = new ArrayList<>();
sources.add(enumSource("edi_standard", "X12", "EDIFACT"));
sources.add(enumSource("edi_partner_status", "TESTING", "ACTIVE", "SUSPENDED", "RETIRED"));
sources.add(enumSource("edi_usage_indicator", "T", "P"));
sources.add(enumSource("edi_line_ending", "NONE", "LF", "CRLF"));
sources.add(enumSource("edi_send_ta1", "NEVER", "ON_REQUEST", "ON_ERROR", "ALWAYS"));
sources.add(enumSource("edi_control_number_scope", "AGREEMENT", "LOCAL_IDENTITY"));
sources.add(enumSource("edi_on_duplicate", "REJECT_TA1", "IGNORE", "PROCESS"));
sources.add(enumSource("edi_direction", "INBOUND", "OUTBOUND"));
sources.add(enumSource("edi_transport_direction", "INBOUND", "OUTBOUND", "BOTH"));
sources.add(enumSource("edi_ack_expectation", "NONE", "997", "999"));
sources.add(enumSource("edi_ack_level", "GROUP_ONLY", "ALL_TRANSACTIONS", "ERRORS_ONLY"));
sources.add(enumSource("edi_batch_mode", "IMMEDIATE", "BATCH"));
sources.add(enumSource("edi_outbound_trigger", "ESB_EVENT", "RECORD_CHANGE", "MANUAL"));
sources.add(enumSource("edi_level", "ISA", "GS"));
sources.add(enumSource("edi_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"));
sources.add(enumSource("edi_mdn_mode", "SYNC", "ASYNC", "NONE"));
sources.add(enumSource("edi_sftp_after_pickup", "DELETE", "MOVE"));
sources.add(enumSource("edi_https_auth_type", "NONE", "BASIC", "BEARER", "HMAC", "MTLS"));
sources.add(enumSource("edi_https_inbound_auth", "BASIC", "BEARER", "HMAC"));
sources.add(enumSource("edi_certificate_status", "ACTIVE", "NEXT", "RETIRED"));
sources.add(enumSource("edi_certificate_usage", "SIGNING", "ENCRYPTION", "SIGNING_AND_ENCRYPTION", "TLS"));
sources.add(enumSource("edi_guide_status", "DRAFT", "ACTIVE", "RETIRED"));
sources.add(enumSource("edi_guide_source", "BUILT_IN", "IMPORTED", "CUSTOM"));
sources.add(enumSource("edi_usage", "REQUIRED", "SITUATIONAL", "NOT_USED"));
sources.add(enumSource("edi_data_type", "AN", "ID", "N0", "N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8", "N9", "R", "DT", "TM", "B"));
sources.add(enumSource("edi_rule_type", "PAIRED", "REQUIRED", "EXCLUSION", "CONDITIONAL", "LIST_CONDITIONAL", "SITUATIONAL"));
sources.add(enumSource("edi_severity", "ERROR", "WARNING"));
sources.add(enumSource("edi_code_list_source", "BUILT_IN", "CUSTOM", "EXTERNAL"));
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"));
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"));
sources.add(enumSource("edi_payload_kind", "INBOUND", "OUTBOUND", "AS2", "MDN", "SAMPLE"));
sources.add(QPossibleValueSource.newForTable("edi_partner"));
sources.add(QPossibleValueSource.newForTable("edi_identity"));
sources.add(QPossibleValueSource.newForTable("edi_agreement"));
sources.add(QPossibleValueSource.newForTable("edi_agreement_document"));
sources.add(QPossibleValueSource.newForTable("edi_control_number"));
sources.add(QPossibleValueSource.newForTable("edi_transport"));
sources.add(QPossibleValueSource.newForTable("edi_certificate"));
sources.add(QPossibleValueSource.newForTable("edi_guide"));
sources.add(QPossibleValueSource.newForTable("edi_guide_segment"));
sources.add(QPossibleValueSource.newForTable("edi_guide_element"));
sources.add(QPossibleValueSource.newForTable("edi_guide_rule"));
sources.add(QPossibleValueSource.newForTable("edi_code_list"));
sources.add(QPossibleValueSource.newForTable("edi_code_list_value"));
sources.add(QPossibleValueSource.newForTable("edi_interchange"));
sources.add(QPossibleValueSource.newForTable("edi_functional_group"));
sources.add(QPossibleValueSource.newForTable("edi_transaction"));
sources.add(QPossibleValueSource.newForTable("edi_transaction_error"));
sources.add(QPossibleValueSource.newForTable("edi_event"));
sources.add(QPossibleValueSource.newForTable("edi_exception"));
sources.add(QPossibleValueSource.newForTable("edi_inbox"));
sources.add(QPossibleValueSource.newForTable("edi_payload"));
sources.add(enumSource("edi_on_match", "UPDATE", "SKIP", "EXCEPTION"));
sources.add(QPossibleValueSource.newForTable("edi_map"));
sources.add(QPossibleValueSource.newForTable("edi_map_rule"));
sources.add(QPossibleValueSource.newForTable("edi_map_sample"));
sources.add(QPossibleValueSource.newForTable("edi_outbound_request"));
sources.add(QPossibleValueSource.newForTable("edi_outbound_message"));
return (sources);
}
/*******************************************************************************
** Stable string ids for enum values.
*******************************************************************************/
private static QPossibleValueSource enumSource(String name, String... values)
{
List<QPossibleValue<?>> entries = new ArrayList<>();
for(String value : values)
{
entries.add(new QPossibleValue<>(value, value));
}
return (new QPossibleValueSource().withName(name).withType(QPossibleValueSourceType.ENUM).withEnumValues(entries));
}
/*******************************************************************************
** Foreign-key relationships used by parent record screens.
*******************************************************************************/
public static List<QJoinMetaData> joins()
{
List<QJoinMetaData> joins = new ArrayList<>();
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_identity").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_identity").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_agreement").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_agreement").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_identity_local_identity_id_edi_agreement").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_identity").withRightTable("edi_agreement").withJoinOn(new JoinOn("id", "localIdentityId")));
joins.add(new QJoinMetaData().withName("edi_identity_partner_identity_id_edi_agreement").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_identity").withRightTable("edi_agreement").withJoinOn(new JoinOn("id", "partnerIdentityId")));
joins.add(new QJoinMetaData().withName("edi_transport_inbound_transport_id_edi_agreement").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_transport").withRightTable("edi_agreement").withJoinOn(new JoinOn("id", "inboundTransportId")));
joins.add(new QJoinMetaData().withName("edi_transport_outbound_transport_id_edi_agreement").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_transport").withRightTable("edi_agreement").withJoinOn(new JoinOn("id", "outboundTransportId")));
joins.add(new QJoinMetaData().withName("edi_agreement_agreement_id_edi_agreement_document").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_agreement").withRightTable("edi_agreement_document").withJoinOn(new JoinOn("id", "agreementId")));
joins.add(new QJoinMetaData().withName("edi_guide_guide_id_edi_agreement_document").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_guide").withRightTable("edi_agreement_document").withJoinOn(new JoinOn("id", "guideId")));
joins.add(new QJoinMetaData().withName("edi_agreement_agreement_id_edi_control_number").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_agreement").withRightTable("edi_control_number").withJoinOn(new JoinOn("id", "agreementId")));
joins.add(new QJoinMetaData().withName("edi_identity_local_identity_id_edi_control_number").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_identity").withRightTable("edi_control_number").withJoinOn(new JoinOn("id", "localIdentityId")));
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_transport").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_transport").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_certificate_local_certificate_id_edi_transport").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_certificate").withRightTable("edi_transport").withJoinOn(new JoinOn("id", "localCertificateId")));
joins.add(new QJoinMetaData().withName("edi_certificate_partner_certificate_id_edi_transport").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_certificate").withRightTable("edi_transport").withJoinOn(new JoinOn("id", "partnerCertificateId")));
joins.add(new QJoinMetaData().withName("edi_certificate_partner_tls_certificate_id_edi_transport").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_certificate").withRightTable("edi_transport").withJoinOn(new JoinOn("id", "partnerTlsCertificateId")));
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_certificate").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_certificate").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_guide").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_guide").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_guide_guide_id_edi_guide_segment").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_guide").withRightTable("edi_guide_segment").withJoinOn(new JoinOn("id", "guideId")));
joins.add(new QJoinMetaData().withName("edi_guide_segment_guide_segment_id_edi_guide_element").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_guide_segment").withRightTable("edi_guide_element").withJoinOn(new JoinOn("id", "guideSegmentId")));
joins.add(new QJoinMetaData().withName("edi_code_list_code_list_id_edi_guide_element").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_code_list").withRightTable("edi_guide_element").withJoinOn(new JoinOn("id", "codeListId")));
joins.add(new QJoinMetaData().withName("edi_guide_guide_id_edi_guide_rule").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_guide").withRightTable("edi_guide_rule").withJoinOn(new JoinOn("id", "guideId")));
joins.add(new QJoinMetaData().withName("edi_guide_segment_guide_segment_id_edi_guide_rule").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_guide_segment").withRightTable("edi_guide_rule").withJoinOn(new JoinOn("id", "guideSegmentId")));
joins.add(new QJoinMetaData().withName("edi_code_list_code_list_id_edi_code_list_value").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_code_list").withRightTable("edi_code_list_value").withJoinOn(new JoinOn("id", "codeListId")));
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_interchange").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_interchange").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_agreement_agreement_id_edi_interchange").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_agreement").withRightTable("edi_interchange").withJoinOn(new JoinOn("id", "agreementId")));
joins.add(new QJoinMetaData().withName("edi_transport_transport_id_edi_interchange").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_transport").withRightTable("edi_interchange").withJoinOn(new JoinOn("id", "transportId")));
joins.add(new QJoinMetaData().withName("edi_inbox_inbox_id_edi_interchange").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_inbox").withRightTable("edi_interchange").withJoinOn(new JoinOn("id", "inboxId")));
joins.add(new QJoinMetaData().withName("edi_interchange_interchange_id_edi_functional_group").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_interchange").withRightTable("edi_functional_group").withJoinOn(new JoinOn("id", "interchangeId")));
joins.add(new QJoinMetaData().withName("edi_interchange_interchange_id_edi_transaction").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_interchange").withRightTable("edi_transaction").withJoinOn(new JoinOn("id", "interchangeId")));
joins.add(new QJoinMetaData().withName("edi_functional_group_functional_group_id_edi_transaction").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_functional_group").withRightTable("edi_transaction").withJoinOn(new JoinOn("id", "functionalGroupId")));
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_transaction").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_transaction").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_agreement_document_agreement_document_id_edi_transaction").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_agreement_document").withRightTable("edi_transaction").withJoinOn(new JoinOn("id", "agreementDocumentId")));
joins.add(new QJoinMetaData().withName("edi_guide_guide_id_edi_transaction").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_guide").withRightTable("edi_transaction").withJoinOn(new JoinOn("id", "guideId")));
joins.add(new QJoinMetaData().withName("edi_transaction_transaction_id_edi_transaction_error").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_transaction").withRightTable("edi_transaction_error").withJoinOn(new JoinOn("id", "transactionId")));
joins.add(new QJoinMetaData().withName("edi_interchange_interchange_id_edi_event").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_interchange").withRightTable("edi_event").withJoinOn(new JoinOn("id", "interchangeId")));
joins.add(new QJoinMetaData().withName("edi_transaction_transaction_id_edi_event").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_transaction").withRightTable("edi_event").withJoinOn(new JoinOn("id", "transactionId")));
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_exception").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_exception").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_inbox_inbox_id_edi_exception").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_inbox").withRightTable("edi_exception").withJoinOn(new JoinOn("id", "inboxId")));
joins.add(new QJoinMetaData().withName("edi_interchange_interchange_id_edi_exception").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_interchange").withRightTable("edi_exception").withJoinOn(new JoinOn("id", "interchangeId")));
joins.add(new QJoinMetaData().withName("edi_transaction_transaction_id_edi_exception").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_transaction").withRightTable("edi_exception").withJoinOn(new JoinOn("id", "transactionId")));
joins.add(new QJoinMetaData().withName("edi_transport_transport_id_edi_inbox").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_transport").withRightTable("edi_inbox").withJoinOn(new JoinOn("id", "transportId")));
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_inbox").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_inbox").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_map_map_id_edi_map_rule").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_map").withRightTable("edi_map_rule").withJoinOn(new JoinOn("id", "mapId")));
joins.add(new QJoinMetaData().withName("edi_map_map_id_edi_map_sample").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_map").withRightTable("edi_map_sample").withJoinOn(new JoinOn("id", "mapId")));
joins.add(new QJoinMetaData().withName("edi_partner_partner_id_edi_map").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_partner").withRightTable("edi_map").withJoinOn(new JoinOn("id", "partnerId")));
joins.add(new QJoinMetaData().withName("edi_map_map_id_edi_agreement_document").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_map").withRightTable("edi_agreement_document").withJoinOn(new JoinOn("id", "mapId")));
joins.add(new QJoinMetaData().withName("edi_agreement_document_agreement_document_id_edi_outbound_request").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_agreement_document").withRightTable("edi_outbound_request").withJoinOn(new JoinOn("id", "agreementDocumentId")));
joins.add(new QJoinMetaData().withName("edi_agreement_agreement_id_edi_outbound_message").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_agreement").withRightTable("edi_outbound_message").withJoinOn(new JoinOn("id", "agreementId")));
joins.add(new QJoinMetaData().withName("edi_outbound_message_outbound_message_id_edi_interchange").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_outbound_message").withRightTable("edi_interchange").withJoinOn(new JoinOn("id", "outboundMessageId")));
joins.add(new QJoinMetaData().withName("edi_inbox_inbox_id_edi_event").withType(JoinType.ONE_TO_MANY).withLeftTable("edi_inbox").withRightTable("edi_event").withJoinOn(new JoinOn("id", "inboxId")));
return (joins);
}
/*******************************************************************************
** Typed child lists.
*******************************************************************************/
public static List<QWidgetMetaData> childWidgets()
{
return (joins().stream().map(join -> ChildRecordListRenderer.widgetMetaDataBuilder(join).withName(join.getName())
.withLabel(joinLabel(join)).withCanAddChildRecord(false)
.withOrderBys(childOrder(join.getRightTable())).getWidgetMetaData().withGridColumns(12)).toList());
}
/*******************************************************************************
** Activity is newest first; configuration children retain their logical order.
*******************************************************************************/
private static List<QFilterOrderBy> childOrder(String table)
{
return (switch(table)
{
case "edi_transaction", "edi_interchange", "edi_exception", "edi_inbox", "edi_event", "edi_outbound_request", "edi_outbound_message" ->
List.of(new QFilterOrderBy("createDate", false), new QFilterOrderBy("id", false));
case "edi_guide_segment", "edi_map_rule" -> List.of(new QFilterOrderBy("sequence"), new QFilterOrderBy("id"));
case "edi_guide_element" -> List.of(new QFilterOrderBy("position"), new QFilterOrderBy("componentPosition"), new QFilterOrderBy("id"));
default -> List.of(new QFilterOrderBy("id"));
});
}
/*******************************************************************************
** Distinguish multiple relationships using readable field labels.
*******************************************************************************/
private static String joinLabel(QJoinMetaData join)
{
return (switch(join.getJoinOns().getFirst().getRightField())
{
case "localIdentityId" -> join.getRightTable().equals("edi_agreement") ? "Local Agreements" : "Control Numbers";
case "partnerIdentityId" -> "Partner Agreements";
case "inboundTransportId" -> "Inbound Agreements";
case "outboundTransportId" -> "Outbound Agreements";
case "localCertificateId" -> "Local Certificate Transports";
case "partnerCertificateId" -> "Partner Certificate Transports";
case "partnerTlsCertificateId" -> "Partner TLS Transports";
default -> EdiTablePresentation.pluralLabel(join.getRightTable());
});
}
}
@@ -1,85 +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.List;
import java.util.Objects;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
/*******************************************************************************
** Validate transport routing fields, including partial record edits.
*******************************************************************************/
public class TransportValidation implements TableCustomizerInterface
{
/*******************************************************************************
** Validate merged records without exposing transport settings.
*******************************************************************************/
@Override
public List<QRecord> preInsertOrUpdate(AbstractActionInput input, List<QRecord> records, boolean preview, Optional<List<QRecord>> oldRecords)
{
for(QRecord record : records)
{
QRecord merged = new QRecord();
oldRecords.orElse(List.of()).stream().filter(old -> Objects.equals(old.getValue("id"), record.getValue("id")))
.findFirst().ifPresent(old -> merged.getValues().putAll(old.getValues()));
merged.getValues().putAll(record.getValues());
String name = merged.getValueString("name");
if(name == null || !name.matches("[a-z0-9-]+"))
{
record.addError(new BadInputStatusMessage("name must be a lowercase slug"));
}
String type = merged.getValueString("type");
if(type == null || type.isBlank())
{
record.addError(new BadInputStatusMessage("type is required"));
}
}
for(QRecord record : records)
{
QRecord merged = new QRecord();
oldRecords.orElse(List.of()).stream().filter(old -> Objects.equals(old.getValue("id"), record.getValue("id")))
.findFirst().ifPresent(old -> merged.getValues().putAll(old.getValues()));
merged.getValues().putAll(record.getValues());
try
{
List<String> errors = new java.util.ArrayList<>();
com.kingsrook.qbits.edi.transport.EdiTransportPluginsMetaData.current().get(merged.getValueString("type"))
.validate(merged.toEntity(com.kingsrook.qbits.edi.model.EdiTransport.class), errors);
if(merged.getValueInteger("maxAttempts") != null && merged.getValueInteger("maxAttempts") < 1)
{
errors.add("maxAttempts must be positive");
}
errors.forEach(error -> record.addError(new BadInputStatusMessage(error)));
}
catch(Exception e)
{
record.addError(new BadInputStatusMessage("Invalid transport configuration"));
}
}
return (records);
}
}
@@ -1,842 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_agreement record.
*******************************************************************************/
public class EdiAgreement extends QRecordEntity
{
public static final String TABLE_NAME = "edi_agreement";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "partner_id", possibleValueSourceName = "edi_partner")
private Integer partnerId;
@QField(backendName = "local_identity_id", possibleValueSourceName = "edi_identity")
private Integer localIdentityId;
@QField(backendName = "partner_identity_id", possibleValueSourceName = "edi_identity")
private Integer partnerIdentityId;
@QField(backendName = "standard", maxLength = 255, possibleValueSourceName = "edi_standard")
private String standard;
@QField(backendName = "usage_indicator", maxLength = 255, possibleValueSourceName = "edi_usage_indicator")
private String usageIndicator;
@QField(backendName = "interchange_version", maxLength = 255)
private String interchangeVersion;
@QField(backendName = "element_separator", maxLength = 255, defaultValue = "*")
private String elementSeparator;
@QField(backendName = "segment_terminator", maxLength = 255, defaultValue = "~")
private String segmentTerminator;
@QField(backendName = "component_separator", maxLength = 255, defaultValue = ":")
private String componentSeparator;
@QField(backendName = "repetition_separator", maxLength = 255, defaultValue = "^")
private String repetitionSeparator;
@QField(backendName = "line_ending", maxLength = 255, possibleValueSourceName = "edi_line_ending", defaultValue = "NONE")
private String lineEnding;
@QField(backendName = "request_ta1")
private Boolean requestTa1;
@QField(backendName = "send_ta1", maxLength = 255, possibleValueSourceName = "edi_send_ta1", defaultValue = "ON_ERROR")
private String sendTa1;
@QField(backendName = "inbound_transport_id", possibleValueSourceName = "edi_transport")
private Integer inboundTransportId;
@QField(backendName = "outbound_transport_id", possibleValueSourceName = "edi_transport")
private Integer outboundTransportId;
@QField(backendName = "control_number_scope", maxLength = 255, possibleValueSourceName = "edi_control_number_scope", defaultValue = "AGREEMENT")
private String controlNumberScope;
@QField(backendName = "duplicate_window_days", defaultValue = "365")
private Integer duplicateWindowDays;
@QField(backendName = "on_duplicate", maxLength = 255, possibleValueSourceName = "edi_on_duplicate", defaultValue = "REJECT_TA1")
private String onDuplicate;
@QField(backendName = "max_interchange_bytes", defaultValue = "52428800")
private Long maxInterchangeBytes;
@QField(backendName = "apply_test_documents", defaultValue = "false")
private Boolean applyTestDocuments;
@QField(backendName = "is_active", defaultValue = "true")
private Boolean isActive;
@QField(backendName = "notes", maxLength = 10000)
private String notes;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiAgreement withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get partnerId.
*******************************************************************************/
public Integer getPartnerId()
{
return (partnerId);
}
/*******************************************************************************
** Set partnerId.
*******************************************************************************/
public void setPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
}
/*******************************************************************************
** Set partnerId fluently.
*******************************************************************************/
public EdiAgreement withPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
return (this);
}
/*******************************************************************************
** Get localIdentityId.
*******************************************************************************/
public Integer getLocalIdentityId()
{
return (localIdentityId);
}
/*******************************************************************************
** Set localIdentityId.
*******************************************************************************/
public void setLocalIdentityId(Integer localIdentityId)
{
this.localIdentityId = localIdentityId;
}
/*******************************************************************************
** Set localIdentityId fluently.
*******************************************************************************/
public EdiAgreement withLocalIdentityId(Integer localIdentityId)
{
this.localIdentityId = localIdentityId;
return (this);
}
/*******************************************************************************
** Get partnerIdentityId.
*******************************************************************************/
public Integer getPartnerIdentityId()
{
return (partnerIdentityId);
}
/*******************************************************************************
** Set partnerIdentityId.
*******************************************************************************/
public void setPartnerIdentityId(Integer partnerIdentityId)
{
this.partnerIdentityId = partnerIdentityId;
}
/*******************************************************************************
** Set partnerIdentityId fluently.
*******************************************************************************/
public EdiAgreement withPartnerIdentityId(Integer partnerIdentityId)
{
this.partnerIdentityId = partnerIdentityId;
return (this);
}
/*******************************************************************************
** Get standard.
*******************************************************************************/
public String getStandard()
{
return (standard);
}
/*******************************************************************************
** Set standard.
*******************************************************************************/
public void setStandard(String standard)
{
this.standard = standard;
}
/*******************************************************************************
** Set standard fluently.
*******************************************************************************/
public EdiAgreement withStandard(String standard)
{
this.standard = standard;
return (this);
}
/*******************************************************************************
** Get usageIndicator.
*******************************************************************************/
public String getUsageIndicator()
{
return (usageIndicator);
}
/*******************************************************************************
** Set usageIndicator.
*******************************************************************************/
public void setUsageIndicator(String usageIndicator)
{
this.usageIndicator = usageIndicator;
}
/*******************************************************************************
** Set usageIndicator fluently.
*******************************************************************************/
public EdiAgreement withUsageIndicator(String usageIndicator)
{
this.usageIndicator = usageIndicator;
return (this);
}
/*******************************************************************************
** Get interchangeVersion.
*******************************************************************************/
public String getInterchangeVersion()
{
return (interchangeVersion);
}
/*******************************************************************************
** Set interchangeVersion.
*******************************************************************************/
public void setInterchangeVersion(String interchangeVersion)
{
this.interchangeVersion = interchangeVersion;
}
/*******************************************************************************
** Set interchangeVersion fluently.
*******************************************************************************/
public EdiAgreement withInterchangeVersion(String interchangeVersion)
{
this.interchangeVersion = interchangeVersion;
return (this);
}
/*******************************************************************************
** Get elementSeparator.
*******************************************************************************/
public String getElementSeparator()
{
return (elementSeparator);
}
/*******************************************************************************
** Set elementSeparator.
*******************************************************************************/
public void setElementSeparator(String elementSeparator)
{
this.elementSeparator = elementSeparator;
}
/*******************************************************************************
** Set elementSeparator fluently.
*******************************************************************************/
public EdiAgreement withElementSeparator(String elementSeparator)
{
this.elementSeparator = elementSeparator;
return (this);
}
/*******************************************************************************
** Get segmentTerminator.
*******************************************************************************/
public String getSegmentTerminator()
{
return (segmentTerminator);
}
/*******************************************************************************
** Set segmentTerminator.
*******************************************************************************/
public void setSegmentTerminator(String segmentTerminator)
{
this.segmentTerminator = segmentTerminator;
}
/*******************************************************************************
** Set segmentTerminator fluently.
*******************************************************************************/
public EdiAgreement withSegmentTerminator(String segmentTerminator)
{
this.segmentTerminator = segmentTerminator;
return (this);
}
/*******************************************************************************
** Get componentSeparator.
*******************************************************************************/
public String getComponentSeparator()
{
return (componentSeparator);
}
/*******************************************************************************
** Set componentSeparator.
*******************************************************************************/
public void setComponentSeparator(String componentSeparator)
{
this.componentSeparator = componentSeparator;
}
/*******************************************************************************
** Set componentSeparator fluently.
*******************************************************************************/
public EdiAgreement withComponentSeparator(String componentSeparator)
{
this.componentSeparator = componentSeparator;
return (this);
}
/*******************************************************************************
** Get repetitionSeparator.
*******************************************************************************/
public String getRepetitionSeparator()
{
return (repetitionSeparator);
}
/*******************************************************************************
** Set repetitionSeparator.
*******************************************************************************/
public void setRepetitionSeparator(String repetitionSeparator)
{
this.repetitionSeparator = repetitionSeparator;
}
/*******************************************************************************
** Set repetitionSeparator fluently.
*******************************************************************************/
public EdiAgreement withRepetitionSeparator(String repetitionSeparator)
{
this.repetitionSeparator = repetitionSeparator;
return (this);
}
/*******************************************************************************
** Get lineEnding.
*******************************************************************************/
public String getLineEnding()
{
return (lineEnding);
}
/*******************************************************************************
** Set lineEnding.
*******************************************************************************/
public void setLineEnding(String lineEnding)
{
this.lineEnding = lineEnding;
}
/*******************************************************************************
** Set lineEnding fluently.
*******************************************************************************/
public EdiAgreement withLineEnding(String lineEnding)
{
this.lineEnding = lineEnding;
return (this);
}
/*******************************************************************************
** Get requestTa1.
*******************************************************************************/
public Boolean getRequestTa1()
{
return (requestTa1);
}
/*******************************************************************************
** Set requestTa1.
*******************************************************************************/
public void setRequestTa1(Boolean requestTa1)
{
this.requestTa1 = requestTa1;
}
/*******************************************************************************
** Set requestTa1 fluently.
*******************************************************************************/
public EdiAgreement withRequestTa1(Boolean requestTa1)
{
this.requestTa1 = requestTa1;
return (this);
}
/*******************************************************************************
** Get sendTa1.
*******************************************************************************/
public String getSendTa1()
{
return (sendTa1);
}
/*******************************************************************************
** Set sendTa1.
*******************************************************************************/
public void setSendTa1(String sendTa1)
{
this.sendTa1 = sendTa1;
}
/*******************************************************************************
** Set sendTa1 fluently.
*******************************************************************************/
public EdiAgreement withSendTa1(String sendTa1)
{
this.sendTa1 = sendTa1;
return (this);
}
/*******************************************************************************
** Get inboundTransportId.
*******************************************************************************/
public Integer getInboundTransportId()
{
return (inboundTransportId);
}
/*******************************************************************************
** Set inboundTransportId.
*******************************************************************************/
public void setInboundTransportId(Integer inboundTransportId)
{
this.inboundTransportId = inboundTransportId;
}
/*******************************************************************************
** Set inboundTransportId fluently.
*******************************************************************************/
public EdiAgreement withInboundTransportId(Integer inboundTransportId)
{
this.inboundTransportId = inboundTransportId;
return (this);
}
/*******************************************************************************
** Get outboundTransportId.
*******************************************************************************/
public Integer getOutboundTransportId()
{
return (outboundTransportId);
}
/*******************************************************************************
** Set outboundTransportId.
*******************************************************************************/
public void setOutboundTransportId(Integer outboundTransportId)
{
this.outboundTransportId = outboundTransportId;
}
/*******************************************************************************
** Set outboundTransportId fluently.
*******************************************************************************/
public EdiAgreement withOutboundTransportId(Integer outboundTransportId)
{
this.outboundTransportId = outboundTransportId;
return (this);
}
/*******************************************************************************
** Get controlNumberScope.
*******************************************************************************/
public String getControlNumberScope()
{
return (controlNumberScope);
}
/*******************************************************************************
** Set controlNumberScope.
*******************************************************************************/
public void setControlNumberScope(String controlNumberScope)
{
this.controlNumberScope = controlNumberScope;
}
/*******************************************************************************
** Set controlNumberScope fluently.
*******************************************************************************/
public EdiAgreement withControlNumberScope(String controlNumberScope)
{
this.controlNumberScope = controlNumberScope;
return (this);
}
/*******************************************************************************
** Get duplicateWindowDays.
*******************************************************************************/
public Integer getDuplicateWindowDays()
{
return (duplicateWindowDays);
}
/*******************************************************************************
** Set duplicateWindowDays.
*******************************************************************************/
public void setDuplicateWindowDays(Integer duplicateWindowDays)
{
this.duplicateWindowDays = duplicateWindowDays;
}
/*******************************************************************************
** Set duplicateWindowDays fluently.
*******************************************************************************/
public EdiAgreement withDuplicateWindowDays(Integer duplicateWindowDays)
{
this.duplicateWindowDays = duplicateWindowDays;
return (this);
}
/*******************************************************************************
** Get onDuplicate.
*******************************************************************************/
public String getOnDuplicate()
{
return (onDuplicate);
}
/*******************************************************************************
** Set onDuplicate.
*******************************************************************************/
public void setOnDuplicate(String onDuplicate)
{
this.onDuplicate = onDuplicate;
}
/*******************************************************************************
** Set onDuplicate fluently.
*******************************************************************************/
public EdiAgreement withOnDuplicate(String onDuplicate)
{
this.onDuplicate = onDuplicate;
return (this);
}
/*******************************************************************************
** Get maxInterchangeBytes.
*******************************************************************************/
public Long getMaxInterchangeBytes()
{
return (maxInterchangeBytes);
}
/*******************************************************************************
** Set maxInterchangeBytes.
*******************************************************************************/
public void setMaxInterchangeBytes(Long maxInterchangeBytes)
{
this.maxInterchangeBytes = maxInterchangeBytes;
}
/*******************************************************************************
** Set maxInterchangeBytes fluently.
*******************************************************************************/
public EdiAgreement withMaxInterchangeBytes(Long maxInterchangeBytes)
{
this.maxInterchangeBytes = maxInterchangeBytes;
return (this);
}
/*******************************************************************************
** Get applyTestDocuments.
*******************************************************************************/
public Boolean getApplyTestDocuments()
{
return (applyTestDocuments);
}
/*******************************************************************************
** Set applyTestDocuments.
*******************************************************************************/
public void setApplyTestDocuments(Boolean applyTestDocuments)
{
this.applyTestDocuments = applyTestDocuments;
}
/*******************************************************************************
** Set applyTestDocuments fluently.
*******************************************************************************/
public EdiAgreement withApplyTestDocuments(Boolean applyTestDocuments)
{
this.applyTestDocuments = applyTestDocuments;
return (this);
}
/*******************************************************************************
** Get isActive.
*******************************************************************************/
public Boolean getIsActive()
{
return (isActive);
}
/*******************************************************************************
** Set isActive.
*******************************************************************************/
public void setIsActive(Boolean isActive)
{
this.isActive = isActive;
}
/*******************************************************************************
** Set isActive fluently.
*******************************************************************************/
public EdiAgreement withIsActive(Boolean isActive)
{
this.isActive = isActive;
return (this);
}
/*******************************************************************************
** Get notes.
*******************************************************************************/
public String getNotes()
{
return (notes);
}
/*******************************************************************************
** Set notes.
*******************************************************************************/
public void setNotes(String notes)
{
this.notes = notes;
}
/*******************************************************************************
** Set notes fluently.
*******************************************************************************/
public EdiAgreement withNotes(String notes)
{
this.notes = notes;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiAgreement withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiAgreement withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiAgreement withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,749 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_agreement_document record.
*******************************************************************************/
public class EdiAgreementDocument extends QRecordEntity
{
public static final String TABLE_NAME = "edi_agreement_document";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "agreement_id", possibleValueSourceName = "edi_agreement")
private Integer agreementId;
@QField(backendName = "direction", maxLength = 255, possibleValueSourceName = "edi_direction")
private String direction;
@QField(backendName = "transaction_set", maxLength = 255)
private String transactionSet;
@QField(backendName = "functional_id_code", maxLength = 255)
private String functionalIdCode;
@QField(backendName = "version", maxLength = 255)
private String version;
@QField(backendName = "guide_id", possibleValueSourceName = "edi_guide")
private Integer guideId;
@QField(backendName = "map_id", possibleValueSourceName = "edi_map")
private Integer mapId;
@QField(backendName = "ack_expectation", maxLength = 255, possibleValueSourceName = "edi_ack_expectation")
private String ackExpectation;
@QField(backendName = "ack_due_minutes", defaultValue = "1440")
private Integer ackDueMinutes;
@QField(backendName = "ack_level", maxLength = 255, possibleValueSourceName = "edi_ack_level")
private String ackLevel;
@QField(backendName = "snip_level")
private Integer snipLevel;
@QField(backendName = "contains_phi")
private Boolean containsPhi;
@QField(backendName = "target_table_name", maxLength = 255)
private String targetTableName;
@QField(backendName = "source_table_name", maxLength = 255)
private String sourceTableName;
@QField(backendName = "outbound_trigger", maxLength = 255, possibleValueSourceName = "edi_outbound_trigger")
private String outboundTrigger;
@QField(backendName = "trigger_filter", maxLength = 10000)
private String triggerFilter;
@QField(backendName = "batch_mode", maxLength = 255, possibleValueSourceName = "edi_batch_mode")
private String batchMode;
@QField(backendName = "batch_schedule", maxLength = 255)
private String batchSchedule;
@QField(backendName = "is_active", defaultValue = "true")
private Boolean isActive;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiAgreementDocument withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get agreementId.
*******************************************************************************/
public Integer getAgreementId()
{
return (agreementId);
}
/*******************************************************************************
** Set agreementId.
*******************************************************************************/
public void setAgreementId(Integer agreementId)
{
this.agreementId = agreementId;
}
/*******************************************************************************
** Set agreementId fluently.
*******************************************************************************/
public EdiAgreementDocument withAgreementId(Integer agreementId)
{
this.agreementId = agreementId;
return (this);
}
/*******************************************************************************
** Get direction.
*******************************************************************************/
public String getDirection()
{
return (direction);
}
/*******************************************************************************
** Set direction.
*******************************************************************************/
public void setDirection(String direction)
{
this.direction = direction;
}
/*******************************************************************************
** Set direction fluently.
*******************************************************************************/
public EdiAgreementDocument withDirection(String direction)
{
this.direction = direction;
return (this);
}
/*******************************************************************************
** Get transactionSet.
*******************************************************************************/
public String getTransactionSet()
{
return (transactionSet);
}
/*******************************************************************************
** Set transactionSet.
*******************************************************************************/
public void setTransactionSet(String transactionSet)
{
this.transactionSet = transactionSet;
}
/*******************************************************************************
** Set transactionSet fluently.
*******************************************************************************/
public EdiAgreementDocument withTransactionSet(String transactionSet)
{
this.transactionSet = transactionSet;
return (this);
}
/*******************************************************************************
** Get functionalIdCode.
*******************************************************************************/
public String getFunctionalIdCode()
{
return (functionalIdCode);
}
/*******************************************************************************
** Set functionalIdCode.
*******************************************************************************/
public void setFunctionalIdCode(String functionalIdCode)
{
this.functionalIdCode = functionalIdCode;
}
/*******************************************************************************
** Set functionalIdCode fluently.
*******************************************************************************/
public EdiAgreementDocument withFunctionalIdCode(String functionalIdCode)
{
this.functionalIdCode = functionalIdCode;
return (this);
}
/*******************************************************************************
** Get version.
*******************************************************************************/
public String getVersion()
{
return (version);
}
/*******************************************************************************
** Set version.
*******************************************************************************/
public void setVersion(String version)
{
this.version = version;
}
/*******************************************************************************
** Set version fluently.
*******************************************************************************/
public EdiAgreementDocument withVersion(String version)
{
this.version = version;
return (this);
}
/*******************************************************************************
** Get guideId.
*******************************************************************************/
public Integer getGuideId()
{
return (guideId);
}
/*******************************************************************************
** Set guideId.
*******************************************************************************/
public void setGuideId(Integer guideId)
{
this.guideId = guideId;
}
/*******************************************************************************
** Set guideId fluently.
*******************************************************************************/
public EdiAgreementDocument withGuideId(Integer guideId)
{
this.guideId = guideId;
return (this);
}
/*******************************************************************************
** Get mapId.
*******************************************************************************/
public Integer getMapId()
{
return (mapId);
}
/*******************************************************************************
** Set mapId.
*******************************************************************************/
public void setMapId(Integer mapId)
{
this.mapId = mapId;
}
/*******************************************************************************
** Set mapId fluently.
*******************************************************************************/
public EdiAgreementDocument withMapId(Integer mapId)
{
this.mapId = mapId;
return (this);
}
/*******************************************************************************
** Get ackExpectation.
*******************************************************************************/
public String getAckExpectation()
{
return (ackExpectation);
}
/*******************************************************************************
** Set ackExpectation.
*******************************************************************************/
public void setAckExpectation(String ackExpectation)
{
this.ackExpectation = ackExpectation;
}
/*******************************************************************************
** Set ackExpectation fluently.
*******************************************************************************/
public EdiAgreementDocument withAckExpectation(String ackExpectation)
{
this.ackExpectation = ackExpectation;
return (this);
}
/*******************************************************************************
** Get ackDueMinutes.
*******************************************************************************/
public Integer getAckDueMinutes()
{
return (ackDueMinutes);
}
/*******************************************************************************
** Set ackDueMinutes.
*******************************************************************************/
public void setAckDueMinutes(Integer ackDueMinutes)
{
this.ackDueMinutes = ackDueMinutes;
}
/*******************************************************************************
** Set ackDueMinutes fluently.
*******************************************************************************/
public EdiAgreementDocument withAckDueMinutes(Integer ackDueMinutes)
{
this.ackDueMinutes = ackDueMinutes;
return (this);
}
/*******************************************************************************
** Get ackLevel.
*******************************************************************************/
public String getAckLevel()
{
return (ackLevel);
}
/*******************************************************************************
** Set ackLevel.
*******************************************************************************/
public void setAckLevel(String ackLevel)
{
this.ackLevel = ackLevel;
}
/*******************************************************************************
** Set ackLevel fluently.
*******************************************************************************/
public EdiAgreementDocument withAckLevel(String ackLevel)
{
this.ackLevel = ackLevel;
return (this);
}
/*******************************************************************************
** Get snipLevel.
*******************************************************************************/
public Integer getSnipLevel()
{
return (snipLevel);
}
/*******************************************************************************
** Set snipLevel.
*******************************************************************************/
public void setSnipLevel(Integer snipLevel)
{
this.snipLevel = snipLevel;
}
/*******************************************************************************
** Set snipLevel fluently.
*******************************************************************************/
public EdiAgreementDocument withSnipLevel(Integer snipLevel)
{
this.snipLevel = snipLevel;
return (this);
}
/*******************************************************************************
** Get containsPhi.
*******************************************************************************/
public Boolean getContainsPhi()
{
return (containsPhi);
}
/*******************************************************************************
** Set containsPhi.
*******************************************************************************/
public void setContainsPhi(Boolean containsPhi)
{
this.containsPhi = containsPhi;
}
/*******************************************************************************
** Set containsPhi fluently.
*******************************************************************************/
public EdiAgreementDocument withContainsPhi(Boolean containsPhi)
{
this.containsPhi = containsPhi;
return (this);
}
/*******************************************************************************
** Get targetTableName.
*******************************************************************************/
public String getTargetTableName()
{
return (targetTableName);
}
/*******************************************************************************
** Set targetTableName.
*******************************************************************************/
public void setTargetTableName(String targetTableName)
{
this.targetTableName = targetTableName;
}
/*******************************************************************************
** Set targetTableName fluently.
*******************************************************************************/
public EdiAgreementDocument withTargetTableName(String targetTableName)
{
this.targetTableName = targetTableName;
return (this);
}
/*******************************************************************************
** Get sourceTableName.
*******************************************************************************/
public String getSourceTableName()
{
return (sourceTableName);
}
/*******************************************************************************
** Set sourceTableName.
*******************************************************************************/
public void setSourceTableName(String sourceTableName)
{
this.sourceTableName = sourceTableName;
}
/*******************************************************************************
** Set sourceTableName fluently.
*******************************************************************************/
public EdiAgreementDocument withSourceTableName(String sourceTableName)
{
this.sourceTableName = sourceTableName;
return (this);
}
/*******************************************************************************
** Get outboundTrigger.
*******************************************************************************/
public String getOutboundTrigger()
{
return (outboundTrigger);
}
/*******************************************************************************
** Set outboundTrigger.
*******************************************************************************/
public void setOutboundTrigger(String outboundTrigger)
{
this.outboundTrigger = outboundTrigger;
}
/*******************************************************************************
** Set outboundTrigger fluently.
*******************************************************************************/
public EdiAgreementDocument withOutboundTrigger(String outboundTrigger)
{
this.outboundTrigger = outboundTrigger;
return (this);
}
/*******************************************************************************
** Get triggerFilter.
*******************************************************************************/
public String getTriggerFilter()
{
return (triggerFilter);
}
/*******************************************************************************
** Set triggerFilter.
*******************************************************************************/
public void setTriggerFilter(String triggerFilter)
{
this.triggerFilter = triggerFilter;
}
/*******************************************************************************
** Set triggerFilter fluently.
*******************************************************************************/
public EdiAgreementDocument withTriggerFilter(String triggerFilter)
{
this.triggerFilter = triggerFilter;
return (this);
}
/*******************************************************************************
** Get batchMode.
*******************************************************************************/
public String getBatchMode()
{
return (batchMode);
}
/*******************************************************************************
** Set batchMode.
*******************************************************************************/
public void setBatchMode(String batchMode)
{
this.batchMode = batchMode;
}
/*******************************************************************************
** Set batchMode fluently.
*******************************************************************************/
public EdiAgreementDocument withBatchMode(String batchMode)
{
this.batchMode = batchMode;
return (this);
}
/*******************************************************************************
** Get batchSchedule.
*******************************************************************************/
public String getBatchSchedule()
{
return (batchSchedule);
}
/*******************************************************************************
** Set batchSchedule.
*******************************************************************************/
public void setBatchSchedule(String batchSchedule)
{
this.batchSchedule = batchSchedule;
}
/*******************************************************************************
** Set batchSchedule fluently.
*******************************************************************************/
public EdiAgreementDocument withBatchSchedule(String batchSchedule)
{
this.batchSchedule = batchSchedule;
return (this);
}
/*******************************************************************************
** Get isActive.
*******************************************************************************/
public Boolean getIsActive()
{
return (isActive);
}
/*******************************************************************************
** Set isActive.
*******************************************************************************/
public void setIsActive(Boolean isActive)
{
this.isActive = isActive;
}
/*******************************************************************************
** Set isActive fluently.
*******************************************************************************/
public EdiAgreementDocument withIsActive(Boolean isActive)
{
this.isActive = isActive;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiAgreementDocument withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiAgreementDocument withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiAgreementDocument withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,560 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_certificate record.
*******************************************************************************/
public class EdiCertificate extends QRecordEntity
{
public static final String TABLE_NAME = "edi_certificate";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "alias", maxLength = 255)
private String alias;
@QField(backendName = "partner_id", possibleValueSourceName = "edi_partner")
private Integer partnerId;
@QField(backendName = "usage", maxLength = 255, possibleValueSourceName = "edi_certificate_usage")
private String usage;
@QField(backendName = "certificate_pem", maxLength = 10000)
private String certificatePem;
@QField(backendName = "subject", maxLength = 255)
private String subject;
@QField(backendName = "issuer", maxLength = 255)
private String issuer;
@QField(backendName = "serial_number", maxLength = 255)
private String serialNumber;
@QField(backendName = "fingerprint_sha256", maxLength = 255)
private String fingerprintSha256;
@QField(backendName = "not_before")
private Instant notBefore;
@QField(backendName = "not_after")
private Instant notAfter;
@QField(backendName = "private_key_env_var", maxLength = 255)
private String privateKeyEnvVar;
@QField(backendName = "private_key_password_env_var", maxLength = 255)
private String privateKeyPasswordEnvVar;
@QField(backendName = "status", maxLength = 20, possibleValueSourceName = "edi_certificate_status")
private String status;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiCertificate withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get alias.
*******************************************************************************/
public String getAlias()
{
return (alias);
}
/*******************************************************************************
** Set alias.
*******************************************************************************/
public void setAlias(String alias)
{
this.alias = alias;
}
/*******************************************************************************
** Set alias fluently.
*******************************************************************************/
public EdiCertificate withAlias(String alias)
{
this.alias = alias;
return (this);
}
/*******************************************************************************
** Get partnerId.
*******************************************************************************/
public Integer getPartnerId()
{
return (partnerId);
}
/*******************************************************************************
** Set partnerId.
*******************************************************************************/
public void setPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
}
/*******************************************************************************
** Set partnerId fluently.
*******************************************************************************/
public EdiCertificate withPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
return (this);
}
/*******************************************************************************
** Get usage.
*******************************************************************************/
public String getUsage()
{
return (usage);
}
/*******************************************************************************
** Set usage.
*******************************************************************************/
public void setUsage(String usage)
{
this.usage = usage;
}
/*******************************************************************************
** Set usage fluently.
*******************************************************************************/
public EdiCertificate withUsage(String usage)
{
this.usage = usage;
return (this);
}
/*******************************************************************************
** Get certificatePem.
*******************************************************************************/
public String getCertificatePem()
{
return (certificatePem);
}
/*******************************************************************************
** Set certificatePem.
*******************************************************************************/
public void setCertificatePem(String certificatePem)
{
this.certificatePem = certificatePem;
}
/*******************************************************************************
** Set certificatePem fluently.
*******************************************************************************/
public EdiCertificate withCertificatePem(String certificatePem)
{
this.certificatePem = certificatePem;
return (this);
}
/*******************************************************************************
** Get subject.
*******************************************************************************/
public String getSubject()
{
return (subject);
}
/*******************************************************************************
** Set subject.
*******************************************************************************/
public void setSubject(String subject)
{
this.subject = subject;
}
/*******************************************************************************
** Set subject fluently.
*******************************************************************************/
public EdiCertificate withSubject(String subject)
{
this.subject = subject;
return (this);
}
/*******************************************************************************
** Get issuer.
*******************************************************************************/
public String getIssuer()
{
return (issuer);
}
/*******************************************************************************
** Set issuer.
*******************************************************************************/
public void setIssuer(String issuer)
{
this.issuer = issuer;
}
/*******************************************************************************
** Set issuer fluently.
*******************************************************************************/
public EdiCertificate withIssuer(String issuer)
{
this.issuer = issuer;
return (this);
}
/*******************************************************************************
** Get serialNumber.
*******************************************************************************/
public String getSerialNumber()
{
return (serialNumber);
}
/*******************************************************************************
** Set serialNumber.
*******************************************************************************/
public void setSerialNumber(String serialNumber)
{
this.serialNumber = serialNumber;
}
/*******************************************************************************
** Set serialNumber fluently.
*******************************************************************************/
public EdiCertificate withSerialNumber(String serialNumber)
{
this.serialNumber = serialNumber;
return (this);
}
/*******************************************************************************
** Get fingerprintSha256.
*******************************************************************************/
public String getFingerprintSha256()
{
return (fingerprintSha256);
}
/*******************************************************************************
** Set fingerprintSha256.
*******************************************************************************/
public void setFingerprintSha256(String fingerprintSha256)
{
this.fingerprintSha256 = fingerprintSha256;
}
/*******************************************************************************
** Set fingerprintSha256 fluently.
*******************************************************************************/
public EdiCertificate withFingerprintSha256(String fingerprintSha256)
{
this.fingerprintSha256 = fingerprintSha256;
return (this);
}
/*******************************************************************************
** Get notBefore.
*******************************************************************************/
public Instant getNotBefore()
{
return (notBefore);
}
/*******************************************************************************
** Set notBefore.
*******************************************************************************/
public void setNotBefore(Instant notBefore)
{
this.notBefore = notBefore;
}
/*******************************************************************************
** Set notBefore fluently.
*******************************************************************************/
public EdiCertificate withNotBefore(Instant notBefore)
{
this.notBefore = notBefore;
return (this);
}
/*******************************************************************************
** Get notAfter.
*******************************************************************************/
public Instant getNotAfter()
{
return (notAfter);
}
/*******************************************************************************
** Set notAfter.
*******************************************************************************/
public void setNotAfter(Instant notAfter)
{
this.notAfter = notAfter;
}
/*******************************************************************************
** Set notAfter fluently.
*******************************************************************************/
public EdiCertificate withNotAfter(Instant notAfter)
{
this.notAfter = notAfter;
return (this);
}
/*******************************************************************************
** Get privateKeyEnvVar.
*******************************************************************************/
public String getPrivateKeyEnvVar()
{
return (privateKeyEnvVar);
}
/*******************************************************************************
** Set privateKeyEnvVar.
*******************************************************************************/
public void setPrivateKeyEnvVar(String privateKeyEnvVar)
{
this.privateKeyEnvVar = privateKeyEnvVar;
}
/*******************************************************************************
** Set privateKeyEnvVar fluently.
*******************************************************************************/
public EdiCertificate withPrivateKeyEnvVar(String privateKeyEnvVar)
{
this.privateKeyEnvVar = privateKeyEnvVar;
return (this);
}
/*******************************************************************************
** Get privateKeyPasswordEnvVar.
*******************************************************************************/
public String getPrivateKeyPasswordEnvVar()
{
return (privateKeyPasswordEnvVar);
}
/*******************************************************************************
** Set privateKeyPasswordEnvVar.
*******************************************************************************/
public void setPrivateKeyPasswordEnvVar(String privateKeyPasswordEnvVar)
{
this.privateKeyPasswordEnvVar = privateKeyPasswordEnvVar;
}
/*******************************************************************************
** Set privateKeyPasswordEnvVar fluently.
*******************************************************************************/
public EdiCertificate withPrivateKeyPasswordEnvVar(String privateKeyPasswordEnvVar)
{
this.privateKeyPasswordEnvVar = privateKeyPasswordEnvVar;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiCertificate withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiCertificate withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiCertificate withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
/*******************************************************************************
** Get certificate rollover status.
*******************************************************************************/
public String getStatus()
{
return (status);
}
/*******************************************************************************
** Set certificate rollover status.
*******************************************************************************/
public void setStatus(String status)
{
this.status = status;
}
/*******************************************************************************
** Set certificate rollover status fluently.
*******************************************************************************/
public EdiCertificate withStatus(String status)
{
this.status = status;
return (this);
}
}
@@ -1,284 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_code_list record.
*******************************************************************************/
public class EdiCodeList extends QRecordEntity
{
public static final String TABLE_NAME = "edi_code_list";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "name", maxLength = 255)
private String name;
@QField(backendName = "element_id", maxLength = 255)
private String elementId;
@QField(backendName = "source", maxLength = 255, possibleValueSourceName = "edi_code_list_source")
private String source;
@QField(backendName = "external_code_set", maxLength = 255)
private String externalCodeSet;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiCodeList withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get name.
*******************************************************************************/
public String getName()
{
return (name);
}
/*******************************************************************************
** Set name.
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Set name fluently.
*******************************************************************************/
public EdiCodeList withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Get elementId.
*******************************************************************************/
public String getElementId()
{
return (elementId);
}
/*******************************************************************************
** Set elementId.
*******************************************************************************/
public void setElementId(String elementId)
{
this.elementId = elementId;
}
/*******************************************************************************
** Set elementId fluently.
*******************************************************************************/
public EdiCodeList withElementId(String elementId)
{
this.elementId = elementId;
return (this);
}
/*******************************************************************************
** Get source.
*******************************************************************************/
public String getSource()
{
return (source);
}
/*******************************************************************************
** Set source.
*******************************************************************************/
public void setSource(String source)
{
this.source = source;
}
/*******************************************************************************
** Set source fluently.
*******************************************************************************/
public EdiCodeList withSource(String source)
{
this.source = source;
return (this);
}
/*******************************************************************************
** Get externalCodeSet.
*******************************************************************************/
public String getExternalCodeSet()
{
return (externalCodeSet);
}
/*******************************************************************************
** Set externalCodeSet.
*******************************************************************************/
public void setExternalCodeSet(String externalCodeSet)
{
this.externalCodeSet = externalCodeSet;
}
/*******************************************************************************
** Set externalCodeSet fluently.
*******************************************************************************/
public EdiCodeList withExternalCodeSet(String externalCodeSet)
{
this.externalCodeSet = externalCodeSet;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiCodeList withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiCodeList withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiCodeList withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,284 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_code_list_value record.
*******************************************************************************/
public class EdiCodeListValue extends QRecordEntity
{
public static final String TABLE_NAME = "edi_code_list_value";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "code_list_id", possibleValueSourceName = "edi_code_list")
private Integer codeListId;
@QField(backendName = "code", maxLength = 255)
private String code;
@QField(backendName = "description", maxLength = 10000)
private String description;
@QField(backendName = "is_active", defaultValue = "true")
private Boolean isActive;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiCodeListValue withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get codeListId.
*******************************************************************************/
public Integer getCodeListId()
{
return (codeListId);
}
/*******************************************************************************
** Set codeListId.
*******************************************************************************/
public void setCodeListId(Integer codeListId)
{
this.codeListId = codeListId;
}
/*******************************************************************************
** Set codeListId fluently.
*******************************************************************************/
public EdiCodeListValue withCodeListId(Integer codeListId)
{
this.codeListId = codeListId;
return (this);
}
/*******************************************************************************
** Get code.
*******************************************************************************/
public String getCode()
{
return (code);
}
/*******************************************************************************
** Set code.
*******************************************************************************/
public void setCode(String code)
{
this.code = code;
}
/*******************************************************************************
** Set code fluently.
*******************************************************************************/
public EdiCodeListValue withCode(String code)
{
this.code = code;
return (this);
}
/*******************************************************************************
** Get description.
*******************************************************************************/
public String getDescription()
{
return (description);
}
/*******************************************************************************
** Set description.
*******************************************************************************/
public void setDescription(String description)
{
this.description = description;
}
/*******************************************************************************
** Set description fluently.
*******************************************************************************/
public EdiCodeListValue withDescription(String description)
{
this.description = description;
return (this);
}
/*******************************************************************************
** Get isActive.
*******************************************************************************/
public Boolean getIsActive()
{
return (isActive);
}
/*******************************************************************************
** Set isActive.
*******************************************************************************/
public void setIsActive(Boolean isActive)
{
this.isActive = isActive;
}
/*******************************************************************************
** Set isActive fluently.
*******************************************************************************/
public EdiCodeListValue withIsActive(Boolean isActive)
{
this.isActive = isActive;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiCodeListValue withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiCodeListValue withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiCodeListValue withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,408 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_control_number record.
*******************************************************************************/
public class EdiControlNumber extends QRecordEntity
{
public static final String TABLE_NAME = "edi_control_number";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "agreement_id", possibleValueSourceName = "edi_agreement")
private Integer agreementId;
@QField(backendName = "local_identity_id", possibleValueSourceName = "edi_identity")
private Integer localIdentityId;
@QField(backendName = "scope_key", maxLength = 255)
private String scopeKey;
@QField(backendName = "level", maxLength = 255, possibleValueSourceName = "edi_level")
private String level;
@QField(backendName = "last_value", defaultValue = "0")
private Long lastValue;
@QField(backendName = "min_value", defaultValue = "1")
private Long minValue;
@QField(backendName = "max_value", defaultValue = "999999999")
private Long maxValue;
@QField(backendName = "wrap_allowed", defaultValue = "true")
private Boolean wrapAllowed;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiControlNumber withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get agreementId.
*******************************************************************************/
public Integer getAgreementId()
{
return (agreementId);
}
/*******************************************************************************
** Set agreementId.
*******************************************************************************/
public void setAgreementId(Integer agreementId)
{
this.agreementId = agreementId;
}
/*******************************************************************************
** Set agreementId fluently.
*******************************************************************************/
public EdiControlNumber withAgreementId(Integer agreementId)
{
this.agreementId = agreementId;
return (this);
}
/*******************************************************************************
** Get localIdentityId.
*******************************************************************************/
public Integer getLocalIdentityId()
{
return (localIdentityId);
}
/*******************************************************************************
** Set localIdentityId.
*******************************************************************************/
public void setLocalIdentityId(Integer localIdentityId)
{
this.localIdentityId = localIdentityId;
}
/*******************************************************************************
** Set localIdentityId fluently.
*******************************************************************************/
public EdiControlNumber withLocalIdentityId(Integer localIdentityId)
{
this.localIdentityId = localIdentityId;
return (this);
}
/*******************************************************************************
** Get scopeKey.
*******************************************************************************/
public String getScopeKey()
{
return (scopeKey);
}
/*******************************************************************************
** Set scopeKey.
*******************************************************************************/
public void setScopeKey(String scopeKey)
{
this.scopeKey = scopeKey;
}
/*******************************************************************************
** Set scopeKey fluently.
*******************************************************************************/
public EdiControlNumber withScopeKey(String scopeKey)
{
this.scopeKey = scopeKey;
return (this);
}
/*******************************************************************************
** Get level.
*******************************************************************************/
public String getLevel()
{
return (level);
}
/*******************************************************************************
** Set level.
*******************************************************************************/
public void setLevel(String level)
{
this.level = level;
}
/*******************************************************************************
** Set level fluently.
*******************************************************************************/
public EdiControlNumber withLevel(String level)
{
this.level = level;
return (this);
}
/*******************************************************************************
** Get lastValue.
*******************************************************************************/
public Long getLastValue()
{
return (lastValue);
}
/*******************************************************************************
** Set lastValue.
*******************************************************************************/
public void setLastValue(Long lastValue)
{
this.lastValue = lastValue;
}
/*******************************************************************************
** Set lastValue fluently.
*******************************************************************************/
public EdiControlNumber withLastValue(Long lastValue)
{
this.lastValue = lastValue;
return (this);
}
/*******************************************************************************
** Get minValue.
*******************************************************************************/
public Long getMinValue()
{
return (minValue);
}
/*******************************************************************************
** Set minValue.
*******************************************************************************/
public void setMinValue(Long minValue)
{
this.minValue = minValue;
}
/*******************************************************************************
** Set minValue fluently.
*******************************************************************************/
public EdiControlNumber withMinValue(Long minValue)
{
this.minValue = minValue;
return (this);
}
/*******************************************************************************
** Get maxValue.
*******************************************************************************/
public Long getMaxValue()
{
return (maxValue);
}
/*******************************************************************************
** Set maxValue.
*******************************************************************************/
public void setMaxValue(Long maxValue)
{
this.maxValue = maxValue;
}
/*******************************************************************************
** Set maxValue fluently.
*******************************************************************************/
public EdiControlNumber withMaxValue(Long maxValue)
{
this.maxValue = maxValue;
return (this);
}
/*******************************************************************************
** Get wrapAllowed.
*******************************************************************************/
public Boolean getWrapAllowed()
{
return (wrapAllowed);
}
/*******************************************************************************
** Set wrapAllowed.
*******************************************************************************/
public void setWrapAllowed(Boolean wrapAllowed)
{
this.wrapAllowed = wrapAllowed;
}
/*******************************************************************************
** Set wrapAllowed fluently.
*******************************************************************************/
public EdiControlNumber withWrapAllowed(Boolean wrapAllowed)
{
this.wrapAllowed = wrapAllowed;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiControlNumber withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiControlNumber withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiControlNumber withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,343 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_event record.
*******************************************************************************/
public class EdiEvent extends QRecordEntity
{
public static final String TABLE_NAME = "edi_event";
@QField(backendName = "inbox_id", possibleValueSourceName = "edi_inbox")
private Integer inboxId;
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "interchange_id", possibleValueSourceName = "edi_interchange")
private Integer interchangeId;
@QField(backendName = "transaction_id", possibleValueSourceName = "edi_transaction")
private Integer transactionId;
@QField(backendName = "at")
private Instant at;
@QField(backendName = "type", maxLength = 255, possibleValueSourceName = "edi_event_type")
private String type;
@QField(backendName = "detail", maxLength = 10000)
private String detail;
@QField(backendName = "user_name", maxLength = 255)
private String userName;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiEvent withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get interchangeId.
*******************************************************************************/
public Integer getInterchangeId()
{
return (interchangeId);
}
/*******************************************************************************
** Set interchangeId.
*******************************************************************************/
public void setInterchangeId(Integer interchangeId)
{
this.interchangeId = interchangeId;
}
/*******************************************************************************
** Set interchangeId fluently.
*******************************************************************************/
public EdiEvent withInterchangeId(Integer interchangeId)
{
this.interchangeId = interchangeId;
return (this);
}
/*******************************************************************************
** Get transactionId.
*******************************************************************************/
public Integer getTransactionId()
{
return (transactionId);
}
/*******************************************************************************
** Set transactionId.
*******************************************************************************/
public void setTransactionId(Integer transactionId)
{
this.transactionId = transactionId;
}
/*******************************************************************************
** Set transactionId fluently.
*******************************************************************************/
public EdiEvent withTransactionId(Integer transactionId)
{
this.transactionId = transactionId;
return (this);
}
/*******************************************************************************
** Get at.
*******************************************************************************/
public Instant getAt()
{
return (at);
}
/*******************************************************************************
** Set at.
*******************************************************************************/
public void setAt(Instant at)
{
this.at = at;
}
/*******************************************************************************
** Set at fluently.
*******************************************************************************/
public EdiEvent withAt(Instant at)
{
this.at = at;
return (this);
}
/*******************************************************************************
** Get type.
*******************************************************************************/
public String getType()
{
return (type);
}
/*******************************************************************************
** Set type.
*******************************************************************************/
public void setType(String type)
{
this.type = type;
}
/*******************************************************************************
** Set type fluently.
*******************************************************************************/
public EdiEvent withType(String type)
{
this.type = type;
return (this);
}
/*******************************************************************************
** Get detail.
*******************************************************************************/
public String getDetail()
{
return (detail);
}
/*******************************************************************************
** Set detail.
*******************************************************************************/
public void setDetail(String detail)
{
this.detail = detail;
}
/*******************************************************************************
** Set detail fluently.
*******************************************************************************/
public EdiEvent withDetail(String detail)
{
this.detail = detail;
return (this);
}
/*******************************************************************************
** Get userName.
*******************************************************************************/
public String getUserName()
{
return (userName);
}
/*******************************************************************************
** Set userName.
*******************************************************************************/
public void setUserName(String userName)
{
this.userName = userName;
}
/*******************************************************************************
** Set userName fluently.
*******************************************************************************/
public EdiEvent withUserName(String userName)
{
this.userName = userName;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiEvent withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiEvent withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get inboxId.
*******************************************************************************/
public Integer getInboxId()
{
return (inboxId);
}
/*******************************************************************************
** Set inboxId.
*******************************************************************************/
public void setInboxId(Integer value)
{
inboxId = value;
}
/*******************************************************************************
** Configure inboxId.
*******************************************************************************/
public EdiEvent withInboxId(Integer value)
{
inboxId = value;
return (this);
}
}
@@ -1,594 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_exception record.
*******************************************************************************/
public class EdiException extends QRecordEntity
{
public static final String TABLE_NAME = "edi_exception";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "type", maxLength = 255, possibleValueSourceName = "edi_exception_type")
private String type;
@QField(backendName = "status", maxLength = 255, possibleValueSourceName = "edi_exception_status")
private String status;
@QField(backendName = "partner_id", possibleValueSourceName = "edi_partner")
private Integer partnerId;
@QField(backendName = "inbox_id", possibleValueSourceName = "edi_inbox")
private Integer inboxId;
@QField(backendName = "interchange_id", possibleValueSourceName = "edi_interchange")
private Integer interchangeId;
@QField(backendName = "transaction_id", possibleValueSourceName = "edi_transaction")
private Integer transactionId;
@QField(backendName = "outbound_message_id", possibleValueSourceName = "edi_outbound_message")
private Integer outboundMessageId;
@QField(backendName = "summary", maxLength = 255)
private String summary;
@QField(backendName = "detail", maxLength = 10000)
private String detail;
@QField(backendName = "assigned_to_user_id", maxLength = 255)
private String assignedToUserId;
@QField(backendName = "resolved_by", maxLength = 255)
private String resolvedBy;
@QField(backendName = "resolved_at")
private Instant resolvedAt;
@QField(backendName = "resolution", maxLength = 10000)
private String resolution;
@QField(backendName = "occurrences")
private Integer occurrences;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiException withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get type.
*******************************************************************************/
public String getType()
{
return (type);
}
/*******************************************************************************
** Set type.
*******************************************************************************/
public void setType(String type)
{
this.type = type;
}
/*******************************************************************************
** Set type fluently.
*******************************************************************************/
public EdiException withType(String type)
{
this.type = type;
return (this);
}
/*******************************************************************************
** Get status.
*******************************************************************************/
public String getStatus()
{
return (status);
}
/*******************************************************************************
** Set status.
*******************************************************************************/
public void setStatus(String status)
{
this.status = status;
}
/*******************************************************************************
** Set status fluently.
*******************************************************************************/
public EdiException withStatus(String status)
{
this.status = status;
return (this);
}
/*******************************************************************************
** Get partnerId.
*******************************************************************************/
public Integer getPartnerId()
{
return (partnerId);
}
/*******************************************************************************
** Set partnerId.
*******************************************************************************/
public void setPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
}
/*******************************************************************************
** Set partnerId fluently.
*******************************************************************************/
public EdiException withPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
return (this);
}
/*******************************************************************************
** Get inboxId.
*******************************************************************************/
public Integer getInboxId()
{
return (inboxId);
}
/*******************************************************************************
** Set inboxId.
*******************************************************************************/
public void setInboxId(Integer inboxId)
{
this.inboxId = inboxId;
}
/*******************************************************************************
** Set inboxId fluently.
*******************************************************************************/
public EdiException withInboxId(Integer inboxId)
{
this.inboxId = inboxId;
return (this);
}
/*******************************************************************************
** Get interchangeId.
*******************************************************************************/
public Integer getInterchangeId()
{
return (interchangeId);
}
/*******************************************************************************
** Set interchangeId.
*******************************************************************************/
public void setInterchangeId(Integer interchangeId)
{
this.interchangeId = interchangeId;
}
/*******************************************************************************
** Set interchangeId fluently.
*******************************************************************************/
public EdiException withInterchangeId(Integer interchangeId)
{
this.interchangeId = interchangeId;
return (this);
}
/*******************************************************************************
** Get transactionId.
*******************************************************************************/
public Integer getTransactionId()
{
return (transactionId);
}
/*******************************************************************************
** Set transactionId.
*******************************************************************************/
public void setTransactionId(Integer transactionId)
{
this.transactionId = transactionId;
}
/*******************************************************************************
** Set transactionId fluently.
*******************************************************************************/
public EdiException withTransactionId(Integer transactionId)
{
this.transactionId = transactionId;
return (this);
}
/*******************************************************************************
** Get outboundMessageId.
*******************************************************************************/
public Integer getOutboundMessageId()
{
return (outboundMessageId);
}
/*******************************************************************************
** Set outboundMessageId.
*******************************************************************************/
public void setOutboundMessageId(Integer outboundMessageId)
{
this.outboundMessageId = outboundMessageId;
}
/*******************************************************************************
** Set outboundMessageId fluently.
*******************************************************************************/
public EdiException withOutboundMessageId(Integer outboundMessageId)
{
this.outboundMessageId = outboundMessageId;
return (this);
}
/*******************************************************************************
** Get summary.
*******************************************************************************/
public String getSummary()
{
return (summary);
}
/*******************************************************************************
** Set summary.
*******************************************************************************/
public void setSummary(String summary)
{
this.summary = summary;
}
/*******************************************************************************
** Set summary fluently.
*******************************************************************************/
public EdiException withSummary(String summary)
{
this.summary = summary;
return (this);
}
/*******************************************************************************
** Get detail.
*******************************************************************************/
public String getDetail()
{
return (detail);
}
/*******************************************************************************
** Set detail.
*******************************************************************************/
public void setDetail(String detail)
{
this.detail = detail;
}
/*******************************************************************************
** Set detail fluently.
*******************************************************************************/
public EdiException withDetail(String detail)
{
this.detail = detail;
return (this);
}
/*******************************************************************************
** Get assignedToUserId.
*******************************************************************************/
public String getAssignedToUserId()
{
return (assignedToUserId);
}
/*******************************************************************************
** Set assignedToUserId.
*******************************************************************************/
public void setAssignedToUserId(String assignedToUserId)
{
this.assignedToUserId = assignedToUserId;
}
/*******************************************************************************
** Set assignedToUserId fluently.
*******************************************************************************/
public EdiException withAssignedToUserId(String assignedToUserId)
{
this.assignedToUserId = assignedToUserId;
return (this);
}
/*******************************************************************************
** Get resolvedBy.
*******************************************************************************/
public String getResolvedBy()
{
return (resolvedBy);
}
/*******************************************************************************
** Set resolvedBy.
*******************************************************************************/
public void setResolvedBy(String resolvedBy)
{
this.resolvedBy = resolvedBy;
}
/*******************************************************************************
** Set resolvedBy fluently.
*******************************************************************************/
public EdiException withResolvedBy(String resolvedBy)
{
this.resolvedBy = resolvedBy;
return (this);
}
/*******************************************************************************
** Get resolvedAt.
*******************************************************************************/
public Instant getResolvedAt()
{
return (resolvedAt);
}
/*******************************************************************************
** Set resolvedAt.
*******************************************************************************/
public void setResolvedAt(Instant resolvedAt)
{
this.resolvedAt = resolvedAt;
}
/*******************************************************************************
** Set resolvedAt fluently.
*******************************************************************************/
public EdiException withResolvedAt(Instant resolvedAt)
{
this.resolvedAt = resolvedAt;
return (this);
}
/*******************************************************************************
** Get resolution.
*******************************************************************************/
public String getResolution()
{
return (resolution);
}
/*******************************************************************************
** Set resolution.
*******************************************************************************/
public void setResolution(String resolution)
{
this.resolution = resolution;
}
/*******************************************************************************
** Set resolution fluently.
*******************************************************************************/
public EdiException withResolution(String resolution)
{
this.resolution = resolution;
return (this);
}
/*******************************************************************************
** Get occurrences.
*******************************************************************************/
public Integer getOccurrences()
{
return (occurrences);
}
/*******************************************************************************
** Set occurrences.
*******************************************************************************/
public void setOccurrences(Integer occurrences)
{
this.occurrences = occurrences;
}
/*******************************************************************************
** Set occurrences fluently.
*******************************************************************************/
public EdiException withOccurrences(Integer occurrences)
{
this.occurrences = occurrences;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiException withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiException withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiException withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,466 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_functional_group record.
*******************************************************************************/
public class EdiFunctionalGroup extends QRecordEntity
{
public static final String TABLE_NAME = "edi_functional_group";
@QField(backendName = "inbound_position")
private Integer inboundPosition;
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "interchange_id", possibleValueSourceName = "edi_interchange")
private Integer interchangeId;
@QField(backendName = "sender_id", maxLength = 255)
private String senderId;
@QField(backendName = "functional_id_code", maxLength = 255)
private String functionalIdCode;
@QField(backendName = "control_number", maxLength = 255)
private String controlNumber;
@QField(backendName = "version", maxLength = 255)
private String version;
@QField(backendName = "transaction_count")
private Integer transactionCount;
@QField(backendName = "ack_status", maxLength = 255, possibleValueSourceName = "edi_ack_status")
private String ackStatus;
@QField(backendName = "ack_due_at")
private Instant ackDueAt;
@QField(backendName = "ack_received_at")
private Instant ackReceivedAt;
@QField(backendName = "ack_transaction_id", possibleValueSourceName = "edi_transaction")
private Integer ackTransactionId;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiFunctionalGroup withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get interchangeId.
*******************************************************************************/
public Integer getInterchangeId()
{
return (interchangeId);
}
/*******************************************************************************
** Set interchangeId.
*******************************************************************************/
public void setInterchangeId(Integer interchangeId)
{
this.interchangeId = interchangeId;
}
/*******************************************************************************
** Set interchangeId fluently.
*******************************************************************************/
public EdiFunctionalGroup withInterchangeId(Integer interchangeId)
{
this.interchangeId = interchangeId;
return (this);
}
/*******************************************************************************
** Get senderId.
*******************************************************************************/
public String getSenderId()
{
return (senderId);
}
/*******************************************************************************
** Set senderId.
*******************************************************************************/
public void setSenderId(String senderId)
{
this.senderId = senderId;
}
/*******************************************************************************
** Set senderId fluently.
*******************************************************************************/
public EdiFunctionalGroup withSenderId(String senderId)
{
this.senderId = senderId;
return (this);
}
/*******************************************************************************
** Get functionalIdCode.
*******************************************************************************/
public String getFunctionalIdCode()
{
return (functionalIdCode);
}
/*******************************************************************************
** Set functionalIdCode.
*******************************************************************************/
public void setFunctionalIdCode(String functionalIdCode)
{
this.functionalIdCode = functionalIdCode;
}
/*******************************************************************************
** Set functionalIdCode fluently.
*******************************************************************************/
public EdiFunctionalGroup withFunctionalIdCode(String functionalIdCode)
{
this.functionalIdCode = functionalIdCode;
return (this);
}
/*******************************************************************************
** Get controlNumber.
*******************************************************************************/
public String getControlNumber()
{
return (controlNumber);
}
/*******************************************************************************
** Set controlNumber.
*******************************************************************************/
public void setControlNumber(String controlNumber)
{
this.controlNumber = controlNumber;
}
/*******************************************************************************
** Set controlNumber fluently.
*******************************************************************************/
public EdiFunctionalGroup withControlNumber(String controlNumber)
{
this.controlNumber = controlNumber;
return (this);
}
/*******************************************************************************
** Get version.
*******************************************************************************/
public String getVersion()
{
return (version);
}
/*******************************************************************************
** Set version.
*******************************************************************************/
public void setVersion(String version)
{
this.version = version;
}
/*******************************************************************************
** Set version fluently.
*******************************************************************************/
public EdiFunctionalGroup withVersion(String version)
{
this.version = version;
return (this);
}
/*******************************************************************************
** Get transactionCount.
*******************************************************************************/
public Integer getTransactionCount()
{
return (transactionCount);
}
/*******************************************************************************
** Set transactionCount.
*******************************************************************************/
public void setTransactionCount(Integer transactionCount)
{
this.transactionCount = transactionCount;
}
/*******************************************************************************
** Set transactionCount fluently.
*******************************************************************************/
public EdiFunctionalGroup withTransactionCount(Integer transactionCount)
{
this.transactionCount = transactionCount;
return (this);
}
/*******************************************************************************
** Get ackStatus.
*******************************************************************************/
public String getAckStatus()
{
return (ackStatus);
}
/*******************************************************************************
** Set ackStatus.
*******************************************************************************/
public void setAckStatus(String ackStatus)
{
this.ackStatus = ackStatus;
}
/*******************************************************************************
** Set ackStatus fluently.
*******************************************************************************/
public EdiFunctionalGroup withAckStatus(String ackStatus)
{
this.ackStatus = ackStatus;
return (this);
}
/*******************************************************************************
** Get ackDueAt.
*******************************************************************************/
public Instant getAckDueAt()
{
return (ackDueAt);
}
/*******************************************************************************
** Set ackDueAt.
*******************************************************************************/
public void setAckDueAt(Instant ackDueAt)
{
this.ackDueAt = ackDueAt;
}
/*******************************************************************************
** Set ackDueAt fluently.
*******************************************************************************/
public EdiFunctionalGroup withAckDueAt(Instant ackDueAt)
{
this.ackDueAt = ackDueAt;
return (this);
}
/*******************************************************************************
** Get ackReceivedAt.
*******************************************************************************/
public Instant getAckReceivedAt()
{
return (ackReceivedAt);
}
/*******************************************************************************
** Set ackReceivedAt.
*******************************************************************************/
public void setAckReceivedAt(Instant ackReceivedAt)
{
this.ackReceivedAt = ackReceivedAt;
}
/*******************************************************************************
** Set ackReceivedAt fluently.
*******************************************************************************/
public EdiFunctionalGroup withAckReceivedAt(Instant ackReceivedAt)
{
this.ackReceivedAt = ackReceivedAt;
return (this);
}
/*******************************************************************************
** Get ackTransactionId.
*******************************************************************************/
public Integer getAckTransactionId()
{
return (ackTransactionId);
}
/*******************************************************************************
** Set ackTransactionId.
*******************************************************************************/
public void setAckTransactionId(Integer ackTransactionId)
{
this.ackTransactionId = ackTransactionId;
}
/*******************************************************************************
** Set ackTransactionId fluently.
*******************************************************************************/
public EdiFunctionalGroup withAckTransactionId(Integer ackTransactionId)
{
this.ackTransactionId = ackTransactionId;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiFunctionalGroup withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiFunctionalGroup withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** One-based occurrence within the parent payload, interchange or group.
*******************************************************************************/
public Integer getInboundPosition()
{
return (inboundPosition);
}
/*******************************************************************************
** Set the stable source position used for retry checkpoints.
*******************************************************************************/
public void setInboundPosition(Integer inboundPosition)
{
this.inboundPosition = inboundPosition;
}
/*******************************************************************************
** Fluently set the source position.
*******************************************************************************/
public EdiFunctionalGroup withInboundPosition(Integer inboundPosition)
{
this.inboundPosition = inboundPosition;
return (this);
}
}
@@ -1,532 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_guide record.
*******************************************************************************/
public class EdiGuide extends QRecordEntity
{
public static final String TABLE_NAME = "edi_guide";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "name", maxLength = 255)
private String name;
@QField(backendName = "standard", maxLength = 255, possibleValueSourceName = "edi_standard")
private String standard;
@QField(backendName = "version", maxLength = 255)
private String version;
@QField(backendName = "transaction_set", maxLength = 255)
private String transactionSet;
@QField(backendName = "functional_id_code", maxLength = 255)
private String functionalIdCode;
@QField(backendName = "partner_id", possibleValueSourceName = "edi_partner")
private Integer partnerId;
@QField(backendName = "parent_guide_id", possibleValueSourceName = "edi_guide")
private Integer parentGuideId;
@QField(backendName = "revision")
private Integer revision;
@QField(backendName = "status", maxLength = 255, possibleValueSourceName = "edi_guide_status")
private String status;
@QField(backendName = "source", maxLength = 255, possibleValueSourceName = "edi_guide_source")
private String source;
@QField(backendName = "description", maxLength = 10000)
private String description;
@QField(backendName = "compiled_checksum", maxLength = 255)
private String compiledChecksum;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiGuide withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get name.
*******************************************************************************/
public String getName()
{
return (name);
}
/*******************************************************************************
** Set name.
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Set name fluently.
*******************************************************************************/
public EdiGuide withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Get standard.
*******************************************************************************/
public String getStandard()
{
return (standard);
}
/*******************************************************************************
** Set standard.
*******************************************************************************/
public void setStandard(String standard)
{
this.standard = standard;
}
/*******************************************************************************
** Set standard fluently.
*******************************************************************************/
public EdiGuide withStandard(String standard)
{
this.standard = standard;
return (this);
}
/*******************************************************************************
** Get version.
*******************************************************************************/
public String getVersion()
{
return (version);
}
/*******************************************************************************
** Set version.
*******************************************************************************/
public void setVersion(String version)
{
this.version = version;
}
/*******************************************************************************
** Set version fluently.
*******************************************************************************/
public EdiGuide withVersion(String version)
{
this.version = version;
return (this);
}
/*******************************************************************************
** Get transactionSet.
*******************************************************************************/
public String getTransactionSet()
{
return (transactionSet);
}
/*******************************************************************************
** Set transactionSet.
*******************************************************************************/
public void setTransactionSet(String transactionSet)
{
this.transactionSet = transactionSet;
}
/*******************************************************************************
** Set transactionSet fluently.
*******************************************************************************/
public EdiGuide withTransactionSet(String transactionSet)
{
this.transactionSet = transactionSet;
return (this);
}
/*******************************************************************************
** Get functionalIdCode.
*******************************************************************************/
public String getFunctionalIdCode()
{
return (functionalIdCode);
}
/*******************************************************************************
** Set functionalIdCode.
*******************************************************************************/
public void setFunctionalIdCode(String functionalIdCode)
{
this.functionalIdCode = functionalIdCode;
}
/*******************************************************************************
** Set functionalIdCode fluently.
*******************************************************************************/
public EdiGuide withFunctionalIdCode(String functionalIdCode)
{
this.functionalIdCode = functionalIdCode;
return (this);
}
/*******************************************************************************
** Get partnerId.
*******************************************************************************/
public Integer getPartnerId()
{
return (partnerId);
}
/*******************************************************************************
** Set partnerId.
*******************************************************************************/
public void setPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
}
/*******************************************************************************
** Set partnerId fluently.
*******************************************************************************/
public EdiGuide withPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
return (this);
}
/*******************************************************************************
** Get parentGuideId.
*******************************************************************************/
public Integer getParentGuideId()
{
return (parentGuideId);
}
/*******************************************************************************
** Set parentGuideId.
*******************************************************************************/
public void setParentGuideId(Integer parentGuideId)
{
this.parentGuideId = parentGuideId;
}
/*******************************************************************************
** Set parentGuideId fluently.
*******************************************************************************/
public EdiGuide withParentGuideId(Integer parentGuideId)
{
this.parentGuideId = parentGuideId;
return (this);
}
/*******************************************************************************
** Get revision.
*******************************************************************************/
public Integer getRevision()
{
return (revision);
}
/*******************************************************************************
** Set revision.
*******************************************************************************/
public void setRevision(Integer revision)
{
this.revision = revision;
}
/*******************************************************************************
** Set revision fluently.
*******************************************************************************/
public EdiGuide withRevision(Integer revision)
{
this.revision = revision;
return (this);
}
/*******************************************************************************
** Get status.
*******************************************************************************/
public String getStatus()
{
return (status);
}
/*******************************************************************************
** Set status.
*******************************************************************************/
public void setStatus(String status)
{
this.status = status;
}
/*******************************************************************************
** Set status fluently.
*******************************************************************************/
public EdiGuide withStatus(String status)
{
this.status = status;
return (this);
}
/*******************************************************************************
** Get source.
*******************************************************************************/
public String getSource()
{
return (source);
}
/*******************************************************************************
** Set source.
*******************************************************************************/
public void setSource(String source)
{
this.source = source;
}
/*******************************************************************************
** Set source fluently.
*******************************************************************************/
public EdiGuide withSource(String source)
{
this.source = source;
return (this);
}
/*******************************************************************************
** Get description.
*******************************************************************************/
public String getDescription()
{
return (description);
}
/*******************************************************************************
** Set description.
*******************************************************************************/
public void setDescription(String description)
{
this.description = description;
}
/*******************************************************************************
** Set description fluently.
*******************************************************************************/
public EdiGuide withDescription(String description)
{
this.description = description;
return (this);
}
/*******************************************************************************
** Get compiledChecksum.
*******************************************************************************/
public String getCompiledChecksum()
{
return (compiledChecksum);
}
/*******************************************************************************
** Set compiledChecksum.
*******************************************************************************/
public void setCompiledChecksum(String compiledChecksum)
{
this.compiledChecksum = compiledChecksum;
}
/*******************************************************************************
** Set compiledChecksum fluently.
*******************************************************************************/
public EdiGuide withCompiledChecksum(String compiledChecksum)
{
this.compiledChecksum = compiledChecksum;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiGuide withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiGuide withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiGuide withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,563 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_guide_element record.
*******************************************************************************/
public class EdiGuideElement extends QRecordEntity
{
public static final String TABLE_NAME = "edi_guide_element";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "guide_segment_id", possibleValueSourceName = "edi_guide_segment")
private Integer guideSegmentId;
@QField(backendName = "position")
private Integer position;
@QField(backendName = "component_position")
private Integer componentPosition;
@QField(backendName = "element_id", maxLength = 255)
private String elementId;
@QField(backendName = "name", maxLength = 255)
private String name;
@QField(backendName = "data_type", maxLength = 255, possibleValueSourceName = "edi_data_type")
private String dataType;
@QField(backendName = "min_length")
private Integer minLength;
@QField(backendName = "max_length")
private Integer maxLength;
@QField(backendName = "usage", maxLength = 255, possibleValueSourceName = "edi_usage")
private String usage;
@QField(backendName = "code_list_id", possibleValueSourceName = "edi_code_list")
private Integer codeListId;
@QField(backendName = "fixed_value", maxLength = 255)
private String fixedValue;
@QField(backendName = "repeat_max")
private Integer repeatMax;
@QField(backendName = "situational_rule_id", possibleValueSourceName = "edi_guide_rule")
private Integer situationalRuleId;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiGuideElement withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get guideSegmentId.
*******************************************************************************/
public Integer getGuideSegmentId()
{
return (guideSegmentId);
}
/*******************************************************************************
** Set guideSegmentId.
*******************************************************************************/
public void setGuideSegmentId(Integer guideSegmentId)
{
this.guideSegmentId = guideSegmentId;
}
/*******************************************************************************
** Set guideSegmentId fluently.
*******************************************************************************/
public EdiGuideElement withGuideSegmentId(Integer guideSegmentId)
{
this.guideSegmentId = guideSegmentId;
return (this);
}
/*******************************************************************************
** Get position.
*******************************************************************************/
public Integer getPosition()
{
return (position);
}
/*******************************************************************************
** Set position.
*******************************************************************************/
public void setPosition(Integer position)
{
this.position = position;
}
/*******************************************************************************
** Set position fluently.
*******************************************************************************/
public EdiGuideElement withPosition(Integer position)
{
this.position = position;
return (this);
}
/*******************************************************************************
** Get componentPosition.
*******************************************************************************/
public Integer getComponentPosition()
{
return (componentPosition);
}
/*******************************************************************************
** Set componentPosition.
*******************************************************************************/
public void setComponentPosition(Integer componentPosition)
{
this.componentPosition = componentPosition;
}
/*******************************************************************************
** Set componentPosition fluently.
*******************************************************************************/
public EdiGuideElement withComponentPosition(Integer componentPosition)
{
this.componentPosition = componentPosition;
return (this);
}
/*******************************************************************************
** Get elementId.
*******************************************************************************/
public String getElementId()
{
return (elementId);
}
/*******************************************************************************
** Set elementId.
*******************************************************************************/
public void setElementId(String elementId)
{
this.elementId = elementId;
}
/*******************************************************************************
** Set elementId fluently.
*******************************************************************************/
public EdiGuideElement withElementId(String elementId)
{
this.elementId = elementId;
return (this);
}
/*******************************************************************************
** Get name.
*******************************************************************************/
public String getName()
{
return (name);
}
/*******************************************************************************
** Set name.
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Set name fluently.
*******************************************************************************/
public EdiGuideElement withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Get dataType.
*******************************************************************************/
public String getDataType()
{
return (dataType);
}
/*******************************************************************************
** Set dataType.
*******************************************************************************/
public void setDataType(String dataType)
{
this.dataType = dataType;
}
/*******************************************************************************
** Set dataType fluently.
*******************************************************************************/
public EdiGuideElement withDataType(String dataType)
{
this.dataType = dataType;
return (this);
}
/*******************************************************************************
** Get minLength.
*******************************************************************************/
public Integer getMinLength()
{
return (minLength);
}
/*******************************************************************************
** Set minLength.
*******************************************************************************/
public void setMinLength(Integer minLength)
{
this.minLength = minLength;
}
/*******************************************************************************
** Set minLength fluently.
*******************************************************************************/
public EdiGuideElement withMinLength(Integer minLength)
{
this.minLength = minLength;
return (this);
}
/*******************************************************************************
** Get maxLength.
*******************************************************************************/
public Integer getMaxLength()
{
return (maxLength);
}
/*******************************************************************************
** Set maxLength.
*******************************************************************************/
public void setMaxLength(Integer maxLength)
{
this.maxLength = maxLength;
}
/*******************************************************************************
** Set maxLength fluently.
*******************************************************************************/
public EdiGuideElement withMaxLength(Integer maxLength)
{
this.maxLength = maxLength;
return (this);
}
/*******************************************************************************
** Get usage.
*******************************************************************************/
public String getUsage()
{
return (usage);
}
/*******************************************************************************
** Set usage.
*******************************************************************************/
public void setUsage(String usage)
{
this.usage = usage;
}
/*******************************************************************************
** Set usage fluently.
*******************************************************************************/
public EdiGuideElement withUsage(String usage)
{
this.usage = usage;
return (this);
}
/*******************************************************************************
** Get codeListId.
*******************************************************************************/
public Integer getCodeListId()
{
return (codeListId);
}
/*******************************************************************************
** Set codeListId.
*******************************************************************************/
public void setCodeListId(Integer codeListId)
{
this.codeListId = codeListId;
}
/*******************************************************************************
** Set codeListId fluently.
*******************************************************************************/
public EdiGuideElement withCodeListId(Integer codeListId)
{
this.codeListId = codeListId;
return (this);
}
/*******************************************************************************
** Get fixedValue.
*******************************************************************************/
public String getFixedValue()
{
return (fixedValue);
}
/*******************************************************************************
** Set fixedValue.
*******************************************************************************/
public void setFixedValue(String fixedValue)
{
this.fixedValue = fixedValue;
}
/*******************************************************************************
** Set fixedValue fluently.
*******************************************************************************/
public EdiGuideElement withFixedValue(String fixedValue)
{
this.fixedValue = fixedValue;
return (this);
}
/*******************************************************************************
** Get repeatMax.
*******************************************************************************/
public Integer getRepeatMax()
{
return (repeatMax);
}
/*******************************************************************************
** Set repeatMax.
*******************************************************************************/
public void setRepeatMax(Integer repeatMax)
{
this.repeatMax = repeatMax;
}
/*******************************************************************************
** Set repeatMax fluently.
*******************************************************************************/
public EdiGuideElement withRepeatMax(Integer repeatMax)
{
this.repeatMax = repeatMax;
return (this);
}
/*******************************************************************************
** Get situationalRuleId.
*******************************************************************************/
public Integer getSituationalRuleId()
{
return (situationalRuleId);
}
/*******************************************************************************
** Set situationalRuleId.
*******************************************************************************/
public void setSituationalRuleId(Integer situationalRuleId)
{
this.situationalRuleId = situationalRuleId;
}
/*******************************************************************************
** Set situationalRuleId fluently.
*******************************************************************************/
public EdiGuideElement withSituationalRuleId(Integer situationalRuleId)
{
this.situationalRuleId = situationalRuleId;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiGuideElement withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiGuideElement withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiGuideElement withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,532 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_guide_rule record.
*******************************************************************************/
public class EdiGuideRule extends QRecordEntity
{
public static final String TABLE_NAME = "edi_guide_rule";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "guide_id", possibleValueSourceName = "edi_guide")
private Integer guideId;
@QField(backendName = "guide_segment_id", possibleValueSourceName = "edi_guide_segment")
private Integer guideSegmentId;
@QField(backendName = "type", maxLength = 255, possibleValueSourceName = "edi_rule_type")
private String type;
@QField(backendName = "positions", maxLength = 255)
private String positions;
@QField(backendName = "expression", maxLength = 10000)
private String expression;
@QField(backendName = "snip_level", defaultValue = "2")
private Integer snipLevel;
@QField(backendName = "severity", maxLength = 255, possibleValueSourceName = "edi_severity")
private String severity;
@QField(backendName = "message", maxLength = 10000)
private String message;
@QField(backendName = "ak3_code", maxLength = 255)
private String ak3Code;
@QField(backendName = "ak4_code", maxLength = 255)
private String ak4Code;
@QField(backendName = "ik3_code", maxLength = 255)
private String ik3Code;
@QField(backendName = "ik4_code", maxLength = 255)
private String ik4Code;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiGuideRule withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get guideId.
*******************************************************************************/
public Integer getGuideId()
{
return (guideId);
}
/*******************************************************************************
** Set guideId.
*******************************************************************************/
public void setGuideId(Integer guideId)
{
this.guideId = guideId;
}
/*******************************************************************************
** Set guideId fluently.
*******************************************************************************/
public EdiGuideRule withGuideId(Integer guideId)
{
this.guideId = guideId;
return (this);
}
/*******************************************************************************
** Get guideSegmentId.
*******************************************************************************/
public Integer getGuideSegmentId()
{
return (guideSegmentId);
}
/*******************************************************************************
** Set guideSegmentId.
*******************************************************************************/
public void setGuideSegmentId(Integer guideSegmentId)
{
this.guideSegmentId = guideSegmentId;
}
/*******************************************************************************
** Set guideSegmentId fluently.
*******************************************************************************/
public EdiGuideRule withGuideSegmentId(Integer guideSegmentId)
{
this.guideSegmentId = guideSegmentId;
return (this);
}
/*******************************************************************************
** Get type.
*******************************************************************************/
public String getType()
{
return (type);
}
/*******************************************************************************
** Set type.
*******************************************************************************/
public void setType(String type)
{
this.type = type;
}
/*******************************************************************************
** Set type fluently.
*******************************************************************************/
public EdiGuideRule withType(String type)
{
this.type = type;
return (this);
}
/*******************************************************************************
** Get positions.
*******************************************************************************/
public String getPositions()
{
return (positions);
}
/*******************************************************************************
** Set positions.
*******************************************************************************/
public void setPositions(String positions)
{
this.positions = positions;
}
/*******************************************************************************
** Set positions fluently.
*******************************************************************************/
public EdiGuideRule withPositions(String positions)
{
this.positions = positions;
return (this);
}
/*******************************************************************************
** Get expression.
*******************************************************************************/
public String getExpression()
{
return (expression);
}
/*******************************************************************************
** Set expression.
*******************************************************************************/
public void setExpression(String expression)
{
this.expression = expression;
}
/*******************************************************************************
** Set expression fluently.
*******************************************************************************/
public EdiGuideRule withExpression(String expression)
{
this.expression = expression;
return (this);
}
/*******************************************************************************
** Get snipLevel.
*******************************************************************************/
public Integer getSnipLevel()
{
return (snipLevel);
}
/*******************************************************************************
** Set snipLevel.
*******************************************************************************/
public void setSnipLevel(Integer snipLevel)
{
this.snipLevel = snipLevel;
}
/*******************************************************************************
** Set snipLevel fluently.
*******************************************************************************/
public EdiGuideRule withSnipLevel(Integer snipLevel)
{
this.snipLevel = snipLevel;
return (this);
}
/*******************************************************************************
** Get severity.
*******************************************************************************/
public String getSeverity()
{
return (severity);
}
/*******************************************************************************
** Set severity.
*******************************************************************************/
public void setSeverity(String severity)
{
this.severity = severity;
}
/*******************************************************************************
** Set severity fluently.
*******************************************************************************/
public EdiGuideRule withSeverity(String severity)
{
this.severity = severity;
return (this);
}
/*******************************************************************************
** Get message.
*******************************************************************************/
public String getMessage()
{
return (message);
}
/*******************************************************************************
** Set message.
*******************************************************************************/
public void setMessage(String message)
{
this.message = message;
}
/*******************************************************************************
** Set message fluently.
*******************************************************************************/
public EdiGuideRule withMessage(String message)
{
this.message = message;
return (this);
}
/*******************************************************************************
** Get ak3Code.
*******************************************************************************/
public String getAk3Code()
{
return (ak3Code);
}
/*******************************************************************************
** Set ak3Code.
*******************************************************************************/
public void setAk3Code(String ak3Code)
{
this.ak3Code = ak3Code;
}
/*******************************************************************************
** Set ak3Code fluently.
*******************************************************************************/
public EdiGuideRule withAk3Code(String ak3Code)
{
this.ak3Code = ak3Code;
return (this);
}
/*******************************************************************************
** Get ak4Code.
*******************************************************************************/
public String getAk4Code()
{
return (ak4Code);
}
/*******************************************************************************
** Set ak4Code.
*******************************************************************************/
public void setAk4Code(String ak4Code)
{
this.ak4Code = ak4Code;
}
/*******************************************************************************
** Set ak4Code fluently.
*******************************************************************************/
public EdiGuideRule withAk4Code(String ak4Code)
{
this.ak4Code = ak4Code;
return (this);
}
/*******************************************************************************
** Get ik3Code.
*******************************************************************************/
public String getIk3Code()
{
return (ik3Code);
}
/*******************************************************************************
** Set ik3Code.
*******************************************************************************/
public void setIk3Code(String ik3Code)
{
this.ik3Code = ik3Code;
}
/*******************************************************************************
** Set ik3Code fluently.
*******************************************************************************/
public EdiGuideRule withIk3Code(String ik3Code)
{
this.ik3Code = ik3Code;
return (this);
}
/*******************************************************************************
** Get ik4Code.
*******************************************************************************/
public String getIk4Code()
{
return (ik4Code);
}
/*******************************************************************************
** Set ik4Code.
*******************************************************************************/
public void setIk4Code(String ik4Code)
{
this.ik4Code = ik4Code;
}
/*******************************************************************************
** Set ik4Code fluently.
*******************************************************************************/
public EdiGuideRule withIk4Code(String ik4Code)
{
this.ik4Code = ik4Code;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiGuideRule withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiGuideRule withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiGuideRule withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,563 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_guide_segment record.
*******************************************************************************/
public class EdiGuideSegment extends QRecordEntity
{
public static final String TABLE_NAME = "edi_guide_segment";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "guide_id", possibleValueSourceName = "edi_guide")
private Integer guideId;
@QField(backendName = "sequence")
private Integer sequence;
@QField(backendName = "loop_id", maxLength = 255)
private String loopId;
@QField(backendName = "parent_loop_id", maxLength = 255)
private String parentLoopId;
@QField(backendName = "loop_max_repeat")
private Integer loopMaxRepeat;
@QField(backendName = "segment_id", maxLength = 255)
private String segmentId;
@QField(backendName = "position", maxLength = 255)
private String position;
@QField(backendName = "usage", maxLength = 255, possibleValueSourceName = "edi_usage")
private String usage;
@QField(backendName = "min_use")
private Integer minUse;
@QField(backendName = "max_use")
private Integer maxUse;
@QField(backendName = "name", maxLength = 255)
private String name;
@QField(backendName = "situational_rule_id", possibleValueSourceName = "edi_guide_rule")
private Integer situationalRuleId;
@QField(backendName = "notes", maxLength = 10000)
private String notes;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiGuideSegment withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get guideId.
*******************************************************************************/
public Integer getGuideId()
{
return (guideId);
}
/*******************************************************************************
** Set guideId.
*******************************************************************************/
public void setGuideId(Integer guideId)
{
this.guideId = guideId;
}
/*******************************************************************************
** Set guideId fluently.
*******************************************************************************/
public EdiGuideSegment withGuideId(Integer guideId)
{
this.guideId = guideId;
return (this);
}
/*******************************************************************************
** Get sequence.
*******************************************************************************/
public Integer getSequence()
{
return (sequence);
}
/*******************************************************************************
** Set sequence.
*******************************************************************************/
public void setSequence(Integer sequence)
{
this.sequence = sequence;
}
/*******************************************************************************
** Set sequence fluently.
*******************************************************************************/
public EdiGuideSegment withSequence(Integer sequence)
{
this.sequence = sequence;
return (this);
}
/*******************************************************************************
** Get loopId.
*******************************************************************************/
public String getLoopId()
{
return (loopId);
}
/*******************************************************************************
** Set loopId.
*******************************************************************************/
public void setLoopId(String loopId)
{
this.loopId = loopId;
}
/*******************************************************************************
** Set loopId fluently.
*******************************************************************************/
public EdiGuideSegment withLoopId(String loopId)
{
this.loopId = loopId;
return (this);
}
/*******************************************************************************
** Get parentLoopId.
*******************************************************************************/
public String getParentLoopId()
{
return (parentLoopId);
}
/*******************************************************************************
** Set parentLoopId.
*******************************************************************************/
public void setParentLoopId(String parentLoopId)
{
this.parentLoopId = parentLoopId;
}
/*******************************************************************************
** Set parentLoopId fluently.
*******************************************************************************/
public EdiGuideSegment withParentLoopId(String parentLoopId)
{
this.parentLoopId = parentLoopId;
return (this);
}
/*******************************************************************************
** Get loopMaxRepeat.
*******************************************************************************/
public Integer getLoopMaxRepeat()
{
return (loopMaxRepeat);
}
/*******************************************************************************
** Set loopMaxRepeat.
*******************************************************************************/
public void setLoopMaxRepeat(Integer loopMaxRepeat)
{
this.loopMaxRepeat = loopMaxRepeat;
}
/*******************************************************************************
** Set loopMaxRepeat fluently.
*******************************************************************************/
public EdiGuideSegment withLoopMaxRepeat(Integer loopMaxRepeat)
{
this.loopMaxRepeat = loopMaxRepeat;
return (this);
}
/*******************************************************************************
** Get segmentId.
*******************************************************************************/
public String getSegmentId()
{
return (segmentId);
}
/*******************************************************************************
** Set segmentId.
*******************************************************************************/
public void setSegmentId(String segmentId)
{
this.segmentId = segmentId;
}
/*******************************************************************************
** Set segmentId fluently.
*******************************************************************************/
public EdiGuideSegment withSegmentId(String segmentId)
{
this.segmentId = segmentId;
return (this);
}
/*******************************************************************************
** Get position.
*******************************************************************************/
public String getPosition()
{
return (position);
}
/*******************************************************************************
** Set position.
*******************************************************************************/
public void setPosition(String position)
{
this.position = position;
}
/*******************************************************************************
** Set position fluently.
*******************************************************************************/
public EdiGuideSegment withPosition(String position)
{
this.position = position;
return (this);
}
/*******************************************************************************
** Get usage.
*******************************************************************************/
public String getUsage()
{
return (usage);
}
/*******************************************************************************
** Set usage.
*******************************************************************************/
public void setUsage(String usage)
{
this.usage = usage;
}
/*******************************************************************************
** Set usage fluently.
*******************************************************************************/
public EdiGuideSegment withUsage(String usage)
{
this.usage = usage;
return (this);
}
/*******************************************************************************
** Get minUse.
*******************************************************************************/
public Integer getMinUse()
{
return (minUse);
}
/*******************************************************************************
** Set minUse.
*******************************************************************************/
public void setMinUse(Integer minUse)
{
this.minUse = minUse;
}
/*******************************************************************************
** Set minUse fluently.
*******************************************************************************/
public EdiGuideSegment withMinUse(Integer minUse)
{
this.minUse = minUse;
return (this);
}
/*******************************************************************************
** Get maxUse.
*******************************************************************************/
public Integer getMaxUse()
{
return (maxUse);
}
/*******************************************************************************
** Set maxUse.
*******************************************************************************/
public void setMaxUse(Integer maxUse)
{
this.maxUse = maxUse;
}
/*******************************************************************************
** Set maxUse fluently.
*******************************************************************************/
public EdiGuideSegment withMaxUse(Integer maxUse)
{
this.maxUse = maxUse;
return (this);
}
/*******************************************************************************
** Get name.
*******************************************************************************/
public String getName()
{
return (name);
}
/*******************************************************************************
** Set name.
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Set name fluently.
*******************************************************************************/
public EdiGuideSegment withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Get situationalRuleId.
*******************************************************************************/
public Integer getSituationalRuleId()
{
return (situationalRuleId);
}
/*******************************************************************************
** Set situationalRuleId.
*******************************************************************************/
public void setSituationalRuleId(Integer situationalRuleId)
{
this.situationalRuleId = situationalRuleId;
}
/*******************************************************************************
** Set situationalRuleId fluently.
*******************************************************************************/
public EdiGuideSegment withSituationalRuleId(Integer situationalRuleId)
{
this.situationalRuleId = situationalRuleId;
return (this);
}
/*******************************************************************************
** Get notes.
*******************************************************************************/
public String getNotes()
{
return (notes);
}
/*******************************************************************************
** Set notes.
*******************************************************************************/
public void setNotes(String notes)
{
this.notes = notes;
}
/*******************************************************************************
** Set notes fluently.
*******************************************************************************/
public EdiGuideSegment withNotes(String notes)
{
this.notes = notes;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiGuideSegment withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiGuideSegment withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiGuideSegment withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,377 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_identity record.
*******************************************************************************/
public class EdiIdentity extends QRecordEntity
{
public static final String TABLE_NAME = "edi_identity";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "partner_id", possibleValueSourceName = "edi_partner")
private Integer partnerId;
@QField(backendName = "standard", maxLength = 255, possibleValueSourceName = "edi_standard")
private String standard;
@QField(backendName = "interchange_qualifier", maxLength = 255)
private String interchangeQualifier;
@QField(backendName = "interchange_id", maxLength = 255)
private String interchangeId;
@QField(backendName = "application_code", maxLength = 255)
private String applicationCode;
@QField(backendName = "description", maxLength = 10000)
private String description;
@QField(backendName = "is_active", defaultValue = "true")
private Boolean isActive;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiIdentity withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get partnerId.
*******************************************************************************/
public Integer getPartnerId()
{
return (partnerId);
}
/*******************************************************************************
** Set partnerId.
*******************************************************************************/
public void setPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
}
/*******************************************************************************
** Set partnerId fluently.
*******************************************************************************/
public EdiIdentity withPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
return (this);
}
/*******************************************************************************
** Get standard.
*******************************************************************************/
public String getStandard()
{
return (standard);
}
/*******************************************************************************
** Set standard.
*******************************************************************************/
public void setStandard(String standard)
{
this.standard = standard;
}
/*******************************************************************************
** Set standard fluently.
*******************************************************************************/
public EdiIdentity withStandard(String standard)
{
this.standard = standard;
return (this);
}
/*******************************************************************************
** Get interchangeQualifier.
*******************************************************************************/
public String getInterchangeQualifier()
{
return (interchangeQualifier);
}
/*******************************************************************************
** Set interchangeQualifier.
*******************************************************************************/
public void setInterchangeQualifier(String interchangeQualifier)
{
this.interchangeQualifier = interchangeQualifier;
}
/*******************************************************************************
** Set interchangeQualifier fluently.
*******************************************************************************/
public EdiIdentity withInterchangeQualifier(String interchangeQualifier)
{
this.interchangeQualifier = interchangeQualifier;
return (this);
}
/*******************************************************************************
** Get interchangeId.
*******************************************************************************/
public String getInterchangeId()
{
return (interchangeId);
}
/*******************************************************************************
** Set interchangeId.
*******************************************************************************/
public void setInterchangeId(String interchangeId)
{
this.interchangeId = interchangeId;
}
/*******************************************************************************
** Set interchangeId fluently.
*******************************************************************************/
public EdiIdentity withInterchangeId(String interchangeId)
{
this.interchangeId = interchangeId;
return (this);
}
/*******************************************************************************
** Get applicationCode.
*******************************************************************************/
public String getApplicationCode()
{
return (applicationCode);
}
/*******************************************************************************
** Set applicationCode.
*******************************************************************************/
public void setApplicationCode(String applicationCode)
{
this.applicationCode = applicationCode;
}
/*******************************************************************************
** Set applicationCode fluently.
*******************************************************************************/
public EdiIdentity withApplicationCode(String applicationCode)
{
this.applicationCode = applicationCode;
return (this);
}
/*******************************************************************************
** Get description.
*******************************************************************************/
public String getDescription()
{
return (description);
}
/*******************************************************************************
** Set description.
*******************************************************************************/
public void setDescription(String description)
{
this.description = description;
}
/*******************************************************************************
** Set description fluently.
*******************************************************************************/
public EdiIdentity withDescription(String description)
{
this.description = description;
return (this);
}
/*******************************************************************************
** Get isActive.
*******************************************************************************/
public Boolean getIsActive()
{
return (isActive);
}
/*******************************************************************************
** Set isActive.
*******************************************************************************/
public void setIsActive(Boolean isActive)
{
this.isActive = isActive;
}
/*******************************************************************************
** Set isActive fluently.
*******************************************************************************/
public EdiIdentity withIsActive(Boolean isActive)
{
this.isActive = isActive;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiIdentity withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiIdentity withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiIdentity withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
}
@@ -1,737 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_inbox record.
*******************************************************************************/
public class EdiInbox extends QRecordEntity
{
public static final String TABLE_NAME = "edi_inbox";
@QField(backendName = "gateway_user_id", maxLength = 255)
private String gatewayUserId;
@QField(backendName = "username", maxLength = 255)
private String username;
@QField(backendName = "reprocess")
private Boolean reprocess;
@QField(backendName = "use_current_revisions")
private Boolean useCurrentRevisions;
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "transport_id", possibleValueSourceName = "edi_transport")
private Integer transportId;
@QField(backendName = "transport_type", maxLength = 255, possibleValueSourceName = "edi_transport_type")
private String transportType;
@QField(backendName = "external_id", maxLength = 255)
private String externalId;
@QField(backendName = "payload_ref", maxLength = 255)
private String payloadRef;
@QField(backendName = "payload_sha256", maxLength = 255)
private String payloadSha256;
@QField(backendName = "size_bytes")
private Long sizeBytes;
@QField(backendName = "file_name", maxLength = 255)
private String fileName;
@QField(backendName = "remote_address", maxLength = 255)
private String remoteAddress;
@QField(backendName = "received_at")
private Instant receivedAt;
@QField(backendName = "status", maxLength = 255, possibleValueSourceName = "edi_inbox_status")
private String status;
@QField(backendName = "attempts", defaultValue = "0")
private Integer attempts;
@QField(backendName = "next_attempt_at")
private Instant nextAttemptAt;
@QField(backendName = "claimed_at")
private Instant claimedAt;
@QField(backendName = "processed_at")
private Instant processedAt;
@QField(backendName = "last_error", maxLength = 255)
private String lastError;
@QField(backendName = "partner_id", possibleValueSourceName = "edi_partner")
private Integer partnerId;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiInbox withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get transportId.
*******************************************************************************/
public Integer getTransportId()
{
return (transportId);
}
/*******************************************************************************
** Set transportId.
*******************************************************************************/
public void setTransportId(Integer transportId)
{
this.transportId = transportId;
}
/*******************************************************************************
** Set transportId fluently.
*******************************************************************************/
public EdiInbox withTransportId(Integer transportId)
{
this.transportId = transportId;
return (this);
}
/*******************************************************************************
** Get transportType.
*******************************************************************************/
public String getTransportType()
{
return (transportType);
}
/*******************************************************************************
** Set transportType.
*******************************************************************************/
public void setTransportType(String transportType)
{
this.transportType = transportType;
}
/*******************************************************************************
** Set transportType fluently.
*******************************************************************************/
public EdiInbox withTransportType(String transportType)
{
this.transportType = transportType;
return (this);
}
/*******************************************************************************
** Get externalId.
*******************************************************************************/
public String getExternalId()
{
return (externalId);
}
/*******************************************************************************
** Set externalId.
*******************************************************************************/
public void setExternalId(String externalId)
{
this.externalId = externalId;
}
/*******************************************************************************
** Set externalId fluently.
*******************************************************************************/
public EdiInbox withExternalId(String externalId)
{
this.externalId = externalId;
return (this);
}
/*******************************************************************************
** Get payloadRef.
*******************************************************************************/
public String getPayloadRef()
{
return (payloadRef);
}
/*******************************************************************************
** Set payloadRef.
*******************************************************************************/
public void setPayloadRef(String payloadRef)
{
this.payloadRef = payloadRef;
}
/*******************************************************************************
** Set payloadRef fluently.
*******************************************************************************/
public EdiInbox withPayloadRef(String payloadRef)
{
this.payloadRef = payloadRef;
return (this);
}
/*******************************************************************************
** Get payloadSha256.
*******************************************************************************/
public String getPayloadSha256()
{
return (payloadSha256);
}
/*******************************************************************************
** Set payloadSha256.
*******************************************************************************/
public void setPayloadSha256(String payloadSha256)
{
this.payloadSha256 = payloadSha256;
}
/*******************************************************************************
** Set payloadSha256 fluently.
*******************************************************************************/
public EdiInbox withPayloadSha256(String payloadSha256)
{
this.payloadSha256 = payloadSha256;
return (this);
}
/*******************************************************************************
** Get sizeBytes.
*******************************************************************************/
public Long getSizeBytes()
{
return (sizeBytes);
}
/*******************************************************************************
** Set sizeBytes.
*******************************************************************************/
public void setSizeBytes(Long sizeBytes)
{
this.sizeBytes = sizeBytes;
}
/*******************************************************************************
** Set sizeBytes fluently.
*******************************************************************************/
public EdiInbox withSizeBytes(Long sizeBytes)
{
this.sizeBytes = sizeBytes;
return (this);
}
/*******************************************************************************
** Get fileName.
*******************************************************************************/
public String getFileName()
{
return (fileName);
}
/*******************************************************************************
** Set fileName.
*******************************************************************************/
public void setFileName(String fileName)
{
this.fileName = fileName;
}
/*******************************************************************************
** Set fileName fluently.
*******************************************************************************/
public EdiInbox withFileName(String fileName)
{
this.fileName = fileName;
return (this);
}
/*******************************************************************************
** Get remoteAddress.
*******************************************************************************/
public String getRemoteAddress()
{
return (remoteAddress);
}
/*******************************************************************************
** Set remoteAddress.
*******************************************************************************/
public void setRemoteAddress(String remoteAddress)
{
this.remoteAddress = remoteAddress;
}
/*******************************************************************************
** Set remoteAddress fluently.
*******************************************************************************/
public EdiInbox withRemoteAddress(String remoteAddress)
{
this.remoteAddress = remoteAddress;
return (this);
}
/*******************************************************************************
** Get receivedAt.
*******************************************************************************/
public Instant getReceivedAt()
{
return (receivedAt);
}
/*******************************************************************************
** Set receivedAt.
*******************************************************************************/
public void setReceivedAt(Instant receivedAt)
{
this.receivedAt = receivedAt;
}
/*******************************************************************************
** Set receivedAt fluently.
*******************************************************************************/
public EdiInbox withReceivedAt(Instant receivedAt)
{
this.receivedAt = receivedAt;
return (this);
}
/*******************************************************************************
** Get status.
*******************************************************************************/
public String getStatus()
{
return (status);
}
/*******************************************************************************
** Set status.
*******************************************************************************/
public void setStatus(String status)
{
this.status = status;
}
/*******************************************************************************
** Set status fluently.
*******************************************************************************/
public EdiInbox withStatus(String status)
{
this.status = status;
return (this);
}
/*******************************************************************************
** Get attempts.
*******************************************************************************/
public Integer getAttempts()
{
return (attempts);
}
/*******************************************************************************
** Set attempts.
*******************************************************************************/
public void setAttempts(Integer attempts)
{
this.attempts = attempts;
}
/*******************************************************************************
** Set attempts fluently.
*******************************************************************************/
public EdiInbox withAttempts(Integer attempts)
{
this.attempts = attempts;
return (this);
}
/*******************************************************************************
** Get nextAttemptAt.
*******************************************************************************/
public Instant getNextAttemptAt()
{
return (nextAttemptAt);
}
/*******************************************************************************
** Set nextAttemptAt.
*******************************************************************************/
public void setNextAttemptAt(Instant nextAttemptAt)
{
this.nextAttemptAt = nextAttemptAt;
}
/*******************************************************************************
** Set nextAttemptAt fluently.
*******************************************************************************/
public EdiInbox withNextAttemptAt(Instant nextAttemptAt)
{
this.nextAttemptAt = nextAttemptAt;
return (this);
}
/*******************************************************************************
** Get claimedAt.
*******************************************************************************/
public Instant getClaimedAt()
{
return (claimedAt);
}
/*******************************************************************************
** Set claimedAt.
*******************************************************************************/
public void setClaimedAt(Instant claimedAt)
{
this.claimedAt = claimedAt;
}
/*******************************************************************************
** Set claimedAt fluently.
*******************************************************************************/
public EdiInbox withClaimedAt(Instant claimedAt)
{
this.claimedAt = claimedAt;
return (this);
}
/*******************************************************************************
** Get processedAt.
*******************************************************************************/
public Instant getProcessedAt()
{
return (processedAt);
}
/*******************************************************************************
** Set processedAt.
*******************************************************************************/
public void setProcessedAt(Instant processedAt)
{
this.processedAt = processedAt;
}
/*******************************************************************************
** Set processedAt fluently.
*******************************************************************************/
public EdiInbox withProcessedAt(Instant processedAt)
{
this.processedAt = processedAt;
return (this);
}
/*******************************************************************************
** Get lastError.
*******************************************************************************/
public String getLastError()
{
return (lastError);
}
/*******************************************************************************
** Set lastError.
*******************************************************************************/
public void setLastError(String lastError)
{
this.lastError = lastError;
}
/*******************************************************************************
** Set lastError fluently.
*******************************************************************************/
public EdiInbox withLastError(String lastError)
{
this.lastError = lastError;
return (this);
}
/*******************************************************************************
** Get partnerId.
*******************************************************************************/
public Integer getPartnerId()
{
return (partnerId);
}
/*******************************************************************************
** Set partnerId.
*******************************************************************************/
public void setPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
}
/*******************************************************************************
** Set partnerId fluently.
*******************************************************************************/
public EdiInbox withPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiInbox withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiInbox withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get gatewayUserId.
*******************************************************************************/
public String getGatewayUserId()
{
return (gatewayUserId);
}
/*******************************************************************************
** Set gatewayUserId.
*******************************************************************************/
public void setGatewayUserId(String value)
{
gatewayUserId = value;
}
/*******************************************************************************
** Configure gatewayUserId.
*******************************************************************************/
public EdiInbox withGatewayUserId(String value)
{
gatewayUserId = value;
return (this);
}
/*******************************************************************************
** Get username.
*******************************************************************************/
public String getUsername()
{
return (username);
}
/*******************************************************************************
** Set username.
*******************************************************************************/
public void setUsername(String value)
{
username = value;
}
/*******************************************************************************
** Configure username.
*******************************************************************************/
public EdiInbox withUsername(String value)
{
username = value;
return (this);
}
/*******************************************************************************
** Get reprocess.
*******************************************************************************/
public Boolean getReprocess()
{
return (reprocess);
}
/*******************************************************************************
** Set reprocess.
*******************************************************************************/
public void setReprocess(Boolean value)
{
reprocess = value;
}
/*******************************************************************************
** Configure reprocess.
*******************************************************************************/
public EdiInbox withReprocess(Boolean value)
{
reprocess = value;
return (this);
}
/*******************************************************************************
** Get useCurrentRevisions.
*******************************************************************************/
public Boolean getUseCurrentRevisions()
{
return (useCurrentRevisions);
}
/*******************************************************************************
** Set useCurrentRevisions.
*******************************************************************************/
public void setUseCurrentRevisions(Boolean value)
{
useCurrentRevisions = value;
}
/*******************************************************************************
** Configure useCurrentRevisions.
*******************************************************************************/
public EdiInbox withUseCurrentRevisions(Boolean value)
{
useCurrentRevisions = value;
return (this);
}
}
@@ -1,866 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_interchange record.
*******************************************************************************/
public class EdiInterchange extends QRecordEntity
{
@QField(backendName = "original_interchange_id", possibleValueSourceName = "edi_interchange")
private Integer originalInterchangeId;
/*******************************************************************************
** Original interchange for re-enveloped resends.
*******************************************************************************/
public Integer getOriginalInterchangeId()
{
return (originalInterchangeId);
}
/*******************************************************************************
** Set the original interchange reference.
*******************************************************************************/
public void setOriginalInterchangeId(Integer value)
{
originalInterchangeId = value;
}
/*******************************************************************************
** Fluent original interchange reference.
*******************************************************************************/
public EdiInterchange withOriginalInterchangeId(Integer value)
{
originalInterchangeId = value;
return (this);
}
public static final String TABLE_NAME = "edi_interchange";
@QField(backendName = "inbound_position")
private Integer inboundPosition;
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "direction", maxLength = 255, possibleValueSourceName = "edi_direction")
private String direction;
@QField(backendName = "partner_id", possibleValueSourceName = "edi_partner")
private Integer partnerId;
@QField(backendName = "agreement_id", possibleValueSourceName = "edi_agreement")
private Integer agreementId;
@QField(backendName = "transport_id", possibleValueSourceName = "edi_transport")
private Integer transportId;
@QField(backendName = "inbox_id", possibleValueSourceName = "edi_inbox")
private Integer inboxId;
@QField(backendName = "outbound_message_id", possibleValueSourceName = "edi_outbound_message")
private Integer outboundMessageId;
@QField(backendName = "standard", maxLength = 255, possibleValueSourceName = "edi_standard")
private String standard;
@QField(backendName = "sender_qualifier", maxLength = 255)
private String senderQualifier;
@QField(backendName = "sender_id", maxLength = 255)
private String senderId;
@QField(backendName = "receiver_qualifier", maxLength = 255)
private String receiverQualifier;
@QField(backendName = "receiver_id", maxLength = 255)
private String receiverId;
@QField(backendName = "control_number", maxLength = 255)
private String controlNumber;
@QField(backendName = "interchange_version", maxLength = 255)
private String interchangeVersion;
@QField(backendName = "usage_indicator", maxLength = 255, possibleValueSourceName = "edi_usage_indicator")
private String usageIndicator;
@QField(backendName = "interchange_date")
private Instant interchangeDate;
@QField(backendName = "group_count")
private Integer groupCount;
@QField(backendName = "payload_ref", maxLength = 255)
private String payloadRef;
@QField(backendName = "payload_sha256", maxLength = 255)
private String payloadSha256;
@QField(backendName = "size_bytes")
private Long sizeBytes;
@QField(backendName = "status", maxLength = 255, possibleValueSourceName = "edi_interchange_status")
private String status;
@QField(backendName = "ta1_status", maxLength = 255)
private String ta1Status;
@QField(backendName = "contains_phi", defaultValue = "false")
private Boolean containsPhi;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiInterchange withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get direction.
*******************************************************************************/
public String getDirection()
{
return (direction);
}
/*******************************************************************************
** Set direction.
*******************************************************************************/
public void setDirection(String direction)
{
this.direction = direction;
}
/*******************************************************************************
** Set direction fluently.
*******************************************************************************/
public EdiInterchange withDirection(String direction)
{
this.direction = direction;
return (this);
}
/*******************************************************************************
** Get partnerId.
*******************************************************************************/
public Integer getPartnerId()
{
return (partnerId);
}
/*******************************************************************************
** Set partnerId.
*******************************************************************************/
public void setPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
}
/*******************************************************************************
** Set partnerId fluently.
*******************************************************************************/
public EdiInterchange withPartnerId(Integer partnerId)
{
this.partnerId = partnerId;
return (this);
}
/*******************************************************************************
** Get agreementId.
*******************************************************************************/
public Integer getAgreementId()
{
return (agreementId);
}
/*******************************************************************************
** Set agreementId.
*******************************************************************************/
public void setAgreementId(Integer agreementId)
{
this.agreementId = agreementId;
}
/*******************************************************************************
** Set agreementId fluently.
*******************************************************************************/
public EdiInterchange withAgreementId(Integer agreementId)
{
this.agreementId = agreementId;
return (this);
}
/*******************************************************************************
** Get transportId.
*******************************************************************************/
public Integer getTransportId()
{
return (transportId);
}
/*******************************************************************************
** Set transportId.
*******************************************************************************/
public void setTransportId(Integer transportId)
{
this.transportId = transportId;
}
/*******************************************************************************
** Set transportId fluently.
*******************************************************************************/
public EdiInterchange withTransportId(Integer transportId)
{
this.transportId = transportId;
return (this);
}
/*******************************************************************************
** Get inboxId.
*******************************************************************************/
public Integer getInboxId()
{
return (inboxId);
}
/*******************************************************************************
** Set inboxId.
*******************************************************************************/
public void setInboxId(Integer inboxId)
{
this.inboxId = inboxId;
}
/*******************************************************************************
** Set inboxId fluently.
*******************************************************************************/
public EdiInterchange withInboxId(Integer inboxId)
{
this.inboxId = inboxId;
return (this);
}
/*******************************************************************************
** Get outboundMessageId.
*******************************************************************************/
public Integer getOutboundMessageId()
{
return (outboundMessageId);
}
/*******************************************************************************
** Set outboundMessageId.
*******************************************************************************/
public void setOutboundMessageId(Integer outboundMessageId)
{
this.outboundMessageId = outboundMessageId;
}
/*******************************************************************************
** Set outboundMessageId fluently.
*******************************************************************************/
public EdiInterchange withOutboundMessageId(Integer outboundMessageId)
{
this.outboundMessageId = outboundMessageId;
return (this);
}
/*******************************************************************************
** Get standard.
*******************************************************************************/
public String getStandard()
{
return (standard);
}
/*******************************************************************************
** Set standard.
*******************************************************************************/
public void setStandard(String standard)
{
this.standard = standard;
}
/*******************************************************************************
** Set standard fluently.
*******************************************************************************/
public EdiInterchange withStandard(String standard)
{
this.standard = standard;
return (this);
}
/*******************************************************************************
** Get senderQualifier.
*******************************************************************************/
public String getSenderQualifier()
{
return (senderQualifier);
}
/*******************************************************************************
** Set senderQualifier.
*******************************************************************************/
public void setSenderQualifier(String senderQualifier)
{
this.senderQualifier = senderQualifier;
}
/*******************************************************************************
** Set senderQualifier fluently.
*******************************************************************************/
public EdiInterchange withSenderQualifier(String senderQualifier)
{
this.senderQualifier = senderQualifier;
return (this);
}
/*******************************************************************************
** Get senderId.
*******************************************************************************/
public String getSenderId()
{
return (senderId);
}
/*******************************************************************************
** Set senderId.
*******************************************************************************/
public void setSenderId(String senderId)
{
this.senderId = senderId;
}
/*******************************************************************************
** Set senderId fluently.
*******************************************************************************/
public EdiInterchange withSenderId(String senderId)
{
this.senderId = senderId;
return (this);
}
/*******************************************************************************
** Get receiverQualifier.
*******************************************************************************/
public String getReceiverQualifier()
{
return (receiverQualifier);
}
/*******************************************************************************
** Set receiverQualifier.
*******************************************************************************/
public void setReceiverQualifier(String receiverQualifier)
{
this.receiverQualifier = receiverQualifier;
}
/*******************************************************************************
** Set receiverQualifier fluently.
*******************************************************************************/
public EdiInterchange withReceiverQualifier(String receiverQualifier)
{
this.receiverQualifier = receiverQualifier;
return (this);
}
/*******************************************************************************
** Get receiverId.
*******************************************************************************/
public String getReceiverId()
{
return (receiverId);
}
/*******************************************************************************
** Set receiverId.
*******************************************************************************/
public void setReceiverId(String receiverId)
{
this.receiverId = receiverId;
}
/*******************************************************************************
** Set receiverId fluently.
*******************************************************************************/
public EdiInterchange withReceiverId(String receiverId)
{
this.receiverId = receiverId;
return (this);
}
/*******************************************************************************
** Get controlNumber.
*******************************************************************************/
public String getControlNumber()
{
return (controlNumber);
}
/*******************************************************************************
** Set controlNumber.
*******************************************************************************/
public void setControlNumber(String controlNumber)
{
this.controlNumber = controlNumber;
}
/*******************************************************************************
** Set controlNumber fluently.
*******************************************************************************/
public EdiInterchange withControlNumber(String controlNumber)
{
this.controlNumber = controlNumber;
return (this);
}
/*******************************************************************************
** Get interchangeVersion.
*******************************************************************************/
public String getInterchangeVersion()
{
return (interchangeVersion);
}
/*******************************************************************************
** Set interchangeVersion.
*******************************************************************************/
public void setInterchangeVersion(String interchangeVersion)
{
this.interchangeVersion = interchangeVersion;
}
/*******************************************************************************
** Set interchangeVersion fluently.
*******************************************************************************/
public EdiInterchange withInterchangeVersion(String interchangeVersion)
{
this.interchangeVersion = interchangeVersion;
return (this);
}
/*******************************************************************************
** Get usageIndicator.
*******************************************************************************/
public String getUsageIndicator()
{
return (usageIndicator);
}
/*******************************************************************************
** Set usageIndicator.
*******************************************************************************/
public void setUsageIndicator(String usageIndicator)
{
this.usageIndicator = usageIndicator;
}
/*******************************************************************************
** Set usageIndicator fluently.
*******************************************************************************/
public EdiInterchange withUsageIndicator(String usageIndicator)
{
this.usageIndicator = usageIndicator;
return (this);
}
/*******************************************************************************
** Get interchangeDate.
*******************************************************************************/
public Instant getInterchangeDate()
{
return (interchangeDate);
}
/*******************************************************************************
** Set interchangeDate.
*******************************************************************************/
public void setInterchangeDate(Instant interchangeDate)
{
this.interchangeDate = interchangeDate;
}
/*******************************************************************************
** Set interchangeDate fluently.
*******************************************************************************/
public EdiInterchange withInterchangeDate(Instant interchangeDate)
{
this.interchangeDate = interchangeDate;
return (this);
}
/*******************************************************************************
** Get groupCount.
*******************************************************************************/
public Integer getGroupCount()
{
return (groupCount);
}
/*******************************************************************************
** Set groupCount.
*******************************************************************************/
public void setGroupCount(Integer groupCount)
{
this.groupCount = groupCount;
}
/*******************************************************************************
** Set groupCount fluently.
*******************************************************************************/
public EdiInterchange withGroupCount(Integer groupCount)
{
this.groupCount = groupCount;
return (this);
}
/*******************************************************************************
** Get payloadRef.
*******************************************************************************/
public String getPayloadRef()
{
return (payloadRef);
}
/*******************************************************************************
** Set payloadRef.
*******************************************************************************/
public void setPayloadRef(String payloadRef)
{
this.payloadRef = payloadRef;
}
/*******************************************************************************
** Set payloadRef fluently.
*******************************************************************************/
public EdiInterchange withPayloadRef(String payloadRef)
{
this.payloadRef = payloadRef;
return (this);
}
/*******************************************************************************
** Get payloadSha256.
*******************************************************************************/
public String getPayloadSha256()
{
return (payloadSha256);
}
/*******************************************************************************
** Set payloadSha256.
*******************************************************************************/
public void setPayloadSha256(String payloadSha256)
{
this.payloadSha256 = payloadSha256;
}
/*******************************************************************************
** Set payloadSha256 fluently.
*******************************************************************************/
public EdiInterchange withPayloadSha256(String payloadSha256)
{
this.payloadSha256 = payloadSha256;
return (this);
}
/*******************************************************************************
** Get sizeBytes.
*******************************************************************************/
public Long getSizeBytes()
{
return (sizeBytes);
}
/*******************************************************************************
** Set sizeBytes.
*******************************************************************************/
public void setSizeBytes(Long sizeBytes)
{
this.sizeBytes = sizeBytes;
}
/*******************************************************************************
** Set sizeBytes fluently.
*******************************************************************************/
public EdiInterchange withSizeBytes(Long sizeBytes)
{
this.sizeBytes = sizeBytes;
return (this);
}
/*******************************************************************************
** Get status.
*******************************************************************************/
public String getStatus()
{
return (status);
}
/*******************************************************************************
** Set status.
*******************************************************************************/
public void setStatus(String status)
{
this.status = status;
}
/*******************************************************************************
** Set status fluently.
*******************************************************************************/
public EdiInterchange withStatus(String status)
{
this.status = status;
return (this);
}
/*******************************************************************************
** Get ta1Status.
*******************************************************************************/
public String getTa1Status()
{
return (ta1Status);
}
/*******************************************************************************
** Set ta1Status.
*******************************************************************************/
public void setTa1Status(String ta1Status)
{
this.ta1Status = ta1Status;
}
/*******************************************************************************
** Set ta1Status fluently.
*******************************************************************************/
public EdiInterchange withTa1Status(String ta1Status)
{
this.ta1Status = ta1Status;
return (this);
}
/*******************************************************************************
** Get containsPhi.
*******************************************************************************/
public Boolean getContainsPhi()
{
return (containsPhi);
}
/*******************************************************************************
** Set containsPhi.
*******************************************************************************/
public void setContainsPhi(Boolean containsPhi)
{
this.containsPhi = containsPhi;
}
/*******************************************************************************
** Set containsPhi fluently.
*******************************************************************************/
public EdiInterchange withContainsPhi(Boolean containsPhi)
{
this.containsPhi = containsPhi;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiInterchange withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiInterchange withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** One-based occurrence within the parent payload, interchange or group.
*******************************************************************************/
public Integer getInboundPosition()
{
return (inboundPosition);
}
/*******************************************************************************
** Set the stable source position used for retry checkpoints.
*******************************************************************************/
public void setInboundPosition(Integer inboundPosition)
{
this.inboundPosition = inboundPosition;
}
/*******************************************************************************
** Fluently set the source position.
*******************************************************************************/
public EdiInterchange withInboundPosition(Integer inboundPosition)
{
this.inboundPosition = inboundPosition;
return (this);
}
}
@@ -1,596 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_map record.
*******************************************************************************/
public class EdiMap extends QRecordEntity
{
public static final String TABLE_NAME = "edi_map";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "name", maxLength = 255)
private String name;
@QField(backendName = "direction", maxLength = 255, possibleValueSourceName = "edi_direction")
private String direction;
@QField(backendName = "standard", maxLength = 255, possibleValueSourceName = "edi_standard")
private String standard;
@QField(backendName = "version", maxLength = 255)
private String version;
@QField(backendName = "transaction_set", maxLength = 255)
private String transactionSet;
@QField(backendName = "guide_id", possibleValueSourceName = "edi_guide")
private Integer guideId;
@QField(backendName = "partner_id", possibleValueSourceName = "edi_partner")
private Integer partnerId;
@QField(backendName = "table_name", maxLength = 255)
private String tableName;
@QField(backendName = "match_key_fields", maxLength = 255)
private String matchKeyFields;
@QField(backendName = "on_match", maxLength = 255, possibleValueSourceName = "edi_on_match")
private String onMatch;
@QField(backendName = "business_key_path", maxLength = 255)
private String businessKeyPath;
@QField(backendName = "business_key_type", maxLength = 255)
private String businessKeyType;
@QField(backendName = "secondary_key_path", maxLength = 255)
private String secondaryKeyPath;
@QField(backendName = "revision")
private Integer revision;
@QField(backendName = "status", maxLength = 255, possibleValueSourceName = "edi_guide_status")
private String status;
@QField(backendName = "parent_map_id", possibleValueSourceName = "edi_map")
private Integer parentMapId;
@QField(backendName = "description", maxLength = 10000)
private String description;
@QField(backendName = "create_date")
private Instant createDate;
@QField(backendName = "modify_date")
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer value)
{
id = value;
}
/*******************************************************************************
** Configure id.
*******************************************************************************/
public EdiMap withId(Integer value)
{
id = value;
return (this);
}
/*******************************************************************************
** Get name.
*******************************************************************************/
public String getName()
{
return (name);
}
/*******************************************************************************
** Set name.
*******************************************************************************/
public void setName(String value)
{
name = value;
}
/*******************************************************************************
** Configure name.
*******************************************************************************/
public EdiMap withName(String value)
{
name = value;
return (this);
}
/*******************************************************************************
** Get direction.
*******************************************************************************/
public String getDirection()
{
return (direction);
}
/*******************************************************************************
** Set direction.
*******************************************************************************/
public void setDirection(String value)
{
direction = value;
}
/*******************************************************************************
** Configure direction.
*******************************************************************************/
public EdiMap withDirection(String value)
{
direction = value;
return (this);
}
/*******************************************************************************
** Get standard.
*******************************************************************************/
public String getStandard()
{
return (standard);
}
/*******************************************************************************
** Set standard.
*******************************************************************************/
public void setStandard(String value)
{
standard = value;
}
/*******************************************************************************
** Configure standard.
*******************************************************************************/
public EdiMap withStandard(String value)
{
standard = value;
return (this);
}
/*******************************************************************************
** Get version.
*******************************************************************************/
public String getVersion()
{
return (version);
}
/*******************************************************************************
** Set version.
*******************************************************************************/
public void setVersion(String value)
{
version = value;
}
/*******************************************************************************
** Configure version.
*******************************************************************************/
public EdiMap withVersion(String value)
{
version = value;
return (this);
}
/*******************************************************************************
** Get transactionSet.
*******************************************************************************/
public String getTransactionSet()
{
return (transactionSet);
}
/*******************************************************************************
** Set transactionSet.
*******************************************************************************/
public void setTransactionSet(String value)
{
transactionSet = value;
}
/*******************************************************************************
** Configure transactionSet.
*******************************************************************************/
public EdiMap withTransactionSet(String value)
{
transactionSet = value;
return (this);
}
/*******************************************************************************
** Get guideId.
*******************************************************************************/
public Integer getGuideId()
{
return (guideId);
}
/*******************************************************************************
** Set guideId.
*******************************************************************************/
public void setGuideId(Integer value)
{
guideId = value;
}
/*******************************************************************************
** Configure guideId.
*******************************************************************************/
public EdiMap withGuideId(Integer value)
{
guideId = value;
return (this);
}
/*******************************************************************************
** Get partnerId.
*******************************************************************************/
public Integer getPartnerId()
{
return (partnerId);
}
/*******************************************************************************
** Set partnerId.
*******************************************************************************/
public void setPartnerId(Integer value)
{
partnerId = value;
}
/*******************************************************************************
** Configure partnerId.
*******************************************************************************/
public EdiMap withPartnerId(Integer value)
{
partnerId = value;
return (this);
}
/*******************************************************************************
** Get tableName.
*******************************************************************************/
public String getTableName()
{
return (tableName);
}
/*******************************************************************************
** Set tableName.
*******************************************************************************/
public void setTableName(String value)
{
tableName = value;
}
/*******************************************************************************
** Configure tableName.
*******************************************************************************/
public EdiMap withTableName(String value)
{
tableName = value;
return (this);
}
/*******************************************************************************
** Get matchKeyFields.
*******************************************************************************/
public String getMatchKeyFields()
{
return (matchKeyFields);
}
/*******************************************************************************
** Set matchKeyFields.
*******************************************************************************/
public void setMatchKeyFields(String value)
{
matchKeyFields = value;
}
/*******************************************************************************
** Configure matchKeyFields.
*******************************************************************************/
public EdiMap withMatchKeyFields(String value)
{
matchKeyFields = value;
return (this);
}
/*******************************************************************************
** Get onMatch.
*******************************************************************************/
public String getOnMatch()
{
return (onMatch);
}
/*******************************************************************************
** Set onMatch.
*******************************************************************************/
public void setOnMatch(String value)
{
onMatch = value;
}
/*******************************************************************************
** Configure onMatch.
*******************************************************************************/
public EdiMap withOnMatch(String value)
{
onMatch = value;
return (this);
}
/*******************************************************************************
** Get businessKeyPath.
*******************************************************************************/
public String getBusinessKeyPath()
{
return (businessKeyPath);
}
/*******************************************************************************
** Set businessKeyPath.
*******************************************************************************/
public void setBusinessKeyPath(String value)
{
businessKeyPath = value;
}
/*******************************************************************************
** Configure businessKeyPath.
*******************************************************************************/
public EdiMap withBusinessKeyPath(String value)
{
businessKeyPath = value;
return (this);
}
/*******************************************************************************
** Get businessKeyType.
*******************************************************************************/
public String getBusinessKeyType()
{
return (businessKeyType);
}
/*******************************************************************************
** Set businessKeyType.
*******************************************************************************/
public void setBusinessKeyType(String value)
{
businessKeyType = value;
}
/*******************************************************************************
** Configure businessKeyType.
*******************************************************************************/
public EdiMap withBusinessKeyType(String value)
{
businessKeyType = value;
return (this);
}
/*******************************************************************************
** Get secondaryKeyPath.
*******************************************************************************/
public String getSecondaryKeyPath()
{
return (secondaryKeyPath);
}
/*******************************************************************************
** Set secondaryKeyPath.
*******************************************************************************/
public void setSecondaryKeyPath(String value)
{
secondaryKeyPath = value;
}
/*******************************************************************************
** Configure secondaryKeyPath.
*******************************************************************************/
public EdiMap withSecondaryKeyPath(String value)
{
secondaryKeyPath = value;
return (this);
}
/*******************************************************************************
** Get revision.
*******************************************************************************/
public Integer getRevision()
{
return (revision);
}
/*******************************************************************************
** Set revision.
*******************************************************************************/
public void setRevision(Integer value)
{
revision = value;
}
/*******************************************************************************
** Configure revision.
*******************************************************************************/
public EdiMap withRevision(Integer value)
{
revision = value;
return (this);
}
/*******************************************************************************
** Get status.
*******************************************************************************/
public String getStatus()
{
return (status);
}
/*******************************************************************************
** Set status.
*******************************************************************************/
public void setStatus(String value)
{
status = value;
}
/*******************************************************************************
** Configure status.
*******************************************************************************/
public EdiMap withStatus(String value)
{
status = value;
return (this);
}
/*******************************************************************************
** Get parentMapId.
*******************************************************************************/
public Integer getParentMapId()
{
return (parentMapId);
}
/*******************************************************************************
** Set parentMapId.
*******************************************************************************/
public void setParentMapId(Integer value)
{
parentMapId = value;
}
/*******************************************************************************
** Configure parentMapId.
*******************************************************************************/
public EdiMap withParentMapId(Integer value)
{
parentMapId = value;
return (this);
}
/*******************************************************************************
** Get description.
*******************************************************************************/
public String getDescription()
{
return (description);
}
/*******************************************************************************
** Set description.
*******************************************************************************/
public void setDescription(String value)
{
description = value;
}
/*******************************************************************************
** Configure description.
*******************************************************************************/
public EdiMap withDescription(String value)
{
description = value;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant value)
{
createDate = value;
}
/*******************************************************************************
** Configure createDate.
*******************************************************************************/
public EdiMap withCreateDate(Instant value)
{
createDate = value;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant value)
{
modifyDate = value;
}
/*******************************************************************************
** Configure modifyDate.
*******************************************************************************/
public EdiMap withModifyDate(Instant value)
{
modifyDate = value;
return (this);
}
}
@@ -1,400 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_map_rule record.
*******************************************************************************/
public class EdiMapRule extends QRecordEntity
{
public static final String TABLE_NAME = "edi_map_rule";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "map_id", possibleValueSourceName = "edi_map")
private Integer mapId;
@QField(backendName = "sequence")
private Integer sequence;
@QField(backendName = "edi_path", maxLength = 255)
private String ediPath;
@QField(backendName = "qqq_path", maxLength = 255)
private String qqqPath;
@QField(backendName = "transform", maxLength = 255)
private String transform;
@QField(backendName = "transform_args", maxLength = 10000)
private String transformArgs;
@QField(backendName = "required")
private Boolean required;
@QField(backendName = "condition", maxLength = 10000)
private String condition;
@QField(backendName = "default_value", maxLength = 255)
private String defaultValue;
@QField(backendName = "notes", maxLength = 10000)
private String notes;
@QField(backendName = "create_date")
private Instant createDate;
@QField(backendName = "modify_date")
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer value)
{
id = value;
}
/*******************************************************************************
** Configure id.
*******************************************************************************/
public EdiMapRule withId(Integer value)
{
id = value;
return (this);
}
/*******************************************************************************
** Get mapId.
*******************************************************************************/
public Integer getMapId()
{
return (mapId);
}
/*******************************************************************************
** Set mapId.
*******************************************************************************/
public void setMapId(Integer value)
{
mapId = value;
}
/*******************************************************************************
** Configure mapId.
*******************************************************************************/
public EdiMapRule withMapId(Integer value)
{
mapId = value;
return (this);
}
/*******************************************************************************
** Get sequence.
*******************************************************************************/
public Integer getSequence()
{
return (sequence);
}
/*******************************************************************************
** Set sequence.
*******************************************************************************/
public void setSequence(Integer value)
{
sequence = value;
}
/*******************************************************************************
** Configure sequence.
*******************************************************************************/
public EdiMapRule withSequence(Integer value)
{
sequence = value;
return (this);
}
/*******************************************************************************
** Get ediPath.
*******************************************************************************/
public String getEdiPath()
{
return (ediPath);
}
/*******************************************************************************
** Set ediPath.
*******************************************************************************/
public void setEdiPath(String value)
{
ediPath = value;
}
/*******************************************************************************
** Configure ediPath.
*******************************************************************************/
public EdiMapRule withEdiPath(String value)
{
ediPath = value;
return (this);
}
/*******************************************************************************
** Get qqqPath.
*******************************************************************************/
public String getQqqPath()
{
return (qqqPath);
}
/*******************************************************************************
** Set qqqPath.
*******************************************************************************/
public void setQqqPath(String value)
{
qqqPath = value;
}
/*******************************************************************************
** Configure qqqPath.
*******************************************************************************/
public EdiMapRule withQqqPath(String value)
{
qqqPath = value;
return (this);
}
/*******************************************************************************
** Get transform.
*******************************************************************************/
public String getTransform()
{
return (transform);
}
/*******************************************************************************
** Set transform.
*******************************************************************************/
public void setTransform(String value)
{
transform = value;
}
/*******************************************************************************
** Configure transform.
*******************************************************************************/
public EdiMapRule withTransform(String value)
{
transform = value;
return (this);
}
/*******************************************************************************
** Get transformArgs.
*******************************************************************************/
public String getTransformArgs()
{
return (transformArgs);
}
/*******************************************************************************
** Set transformArgs.
*******************************************************************************/
public void setTransformArgs(String value)
{
transformArgs = value;
}
/*******************************************************************************
** Configure transformArgs.
*******************************************************************************/
public EdiMapRule withTransformArgs(String value)
{
transformArgs = value;
return (this);
}
/*******************************************************************************
** Get required.
*******************************************************************************/
public Boolean getRequired()
{
return (required);
}
/*******************************************************************************
** Set required.
*******************************************************************************/
public void setRequired(Boolean value)
{
required = value;
}
/*******************************************************************************
** Configure required.
*******************************************************************************/
public EdiMapRule withRequired(Boolean value)
{
required = value;
return (this);
}
/*******************************************************************************
** Get condition.
*******************************************************************************/
public String getCondition()
{
return (condition);
}
/*******************************************************************************
** Set condition.
*******************************************************************************/
public void setCondition(String value)
{
condition = value;
}
/*******************************************************************************
** Configure condition.
*******************************************************************************/
public EdiMapRule withCondition(String value)
{
condition = value;
return (this);
}
/*******************************************************************************
** Get defaultValue.
*******************************************************************************/
public String getDefaultValue()
{
return (defaultValue);
}
/*******************************************************************************
** Set defaultValue.
*******************************************************************************/
public void setDefaultValue(String value)
{
defaultValue = value;
}
/*******************************************************************************
** Configure defaultValue.
*******************************************************************************/
public EdiMapRule withDefaultValue(String value)
{
defaultValue = value;
return (this);
}
/*******************************************************************************
** Get notes.
*******************************************************************************/
public String getNotes()
{
return (notes);
}
/*******************************************************************************
** Set notes.
*******************************************************************************/
public void setNotes(String value)
{
notes = value;
}
/*******************************************************************************
** Configure notes.
*******************************************************************************/
public EdiMapRule withNotes(String value)
{
notes = value;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant value)
{
createDate = value;
}
/*******************************************************************************
** Configure createDate.
*******************************************************************************/
public EdiMapRule withCreateDate(Instant value)
{
createDate = value;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant value)
{
modifyDate = value;
}
/*******************************************************************************
** Configure modifyDate.
*******************************************************************************/
public EdiMapRule withModifyDate(Instant value)
{
modifyDate = value;
return (this);
}
}
@@ -1,288 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_map_sample record.
*******************************************************************************/
public class EdiMapSample extends QRecordEntity
{
public static final String TABLE_NAME = "edi_map_sample";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "map_id", possibleValueSourceName = "edi_map")
private Integer mapId;
@QField(backendName = "name", maxLength = 255)
private String name;
@QField(backendName = "payload_ref", maxLength = 255)
private String payloadRef;
@QField(backendName = "expected_result_json", maxLength = 10000)
private String expectedResultJson;
@QField(backendName = "last_preview_at")
private Instant lastPreviewAt;
@QField(backendName = "last_preview_status", maxLength = 255)
private String lastPreviewStatus;
@QField(backendName = "create_date")
private Instant createDate;
@QField(backendName = "modify_date")
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer value)
{
id = value;
}
/*******************************************************************************
** Configure id.
*******************************************************************************/
public EdiMapSample withId(Integer value)
{
id = value;
return (this);
}
/*******************************************************************************
** Get mapId.
*******************************************************************************/
public Integer getMapId()
{
return (mapId);
}
/*******************************************************************************
** Set mapId.
*******************************************************************************/
public void setMapId(Integer value)
{
mapId = value;
}
/*******************************************************************************
** Configure mapId.
*******************************************************************************/
public EdiMapSample withMapId(Integer value)
{
mapId = value;
return (this);
}
/*******************************************************************************
** Get name.
*******************************************************************************/
public String getName()
{
return (name);
}
/*******************************************************************************
** Set name.
*******************************************************************************/
public void setName(String value)
{
name = value;
}
/*******************************************************************************
** Configure name.
*******************************************************************************/
public EdiMapSample withName(String value)
{
name = value;
return (this);
}
/*******************************************************************************
** Get payloadRef.
*******************************************************************************/
public String getPayloadRef()
{
return (payloadRef);
}
/*******************************************************************************
** Set payloadRef.
*******************************************************************************/
public void setPayloadRef(String value)
{
payloadRef = value;
}
/*******************************************************************************
** Configure payloadRef.
*******************************************************************************/
public EdiMapSample withPayloadRef(String value)
{
payloadRef = value;
return (this);
}
/*******************************************************************************
** Get expectedResultJson.
*******************************************************************************/
public String getExpectedResultJson()
{
return (expectedResultJson);
}
/*******************************************************************************
** Set expectedResultJson.
*******************************************************************************/
public void setExpectedResultJson(String value)
{
expectedResultJson = value;
}
/*******************************************************************************
** Configure expectedResultJson.
*******************************************************************************/
public EdiMapSample withExpectedResultJson(String value)
{
expectedResultJson = value;
return (this);
}
/*******************************************************************************
** Get lastPreviewAt.
*******************************************************************************/
public Instant getLastPreviewAt()
{
return (lastPreviewAt);
}
/*******************************************************************************
** Set lastPreviewAt.
*******************************************************************************/
public void setLastPreviewAt(Instant value)
{
lastPreviewAt = value;
}
/*******************************************************************************
** Configure lastPreviewAt.
*******************************************************************************/
public EdiMapSample withLastPreviewAt(Instant value)
{
lastPreviewAt = value;
return (this);
}
/*******************************************************************************
** Get lastPreviewStatus.
*******************************************************************************/
public String getLastPreviewStatus()
{
return (lastPreviewStatus);
}
/*******************************************************************************
** Set lastPreviewStatus.
*******************************************************************************/
public void setLastPreviewStatus(String value)
{
lastPreviewStatus = value;
}
/*******************************************************************************
** Configure lastPreviewStatus.
*******************************************************************************/
public EdiMapSample withLastPreviewStatus(String value)
{
lastPreviewStatus = value;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant value)
{
createDate = value;
}
/*******************************************************************************
** Configure createDate.
*******************************************************************************/
public EdiMapSample withCreateDate(Instant value)
{
createDate = value;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant value)
{
modifyDate = value;
}
/*******************************************************************************
** Configure modifyDate.
*******************************************************************************/
public EdiMapSample withModifyDate(Instant value)
{
modifyDate = value;
return (this);
}
}
@@ -1,624 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_outbound_message record.
*******************************************************************************/
public class EdiOutboundMessage extends QRecordEntity
{
public static final String TABLE_NAME = "edi_outbound_message";
@QField(backendName = "interchange_id", possibleValueSourceName = "edi_interchange")
private Integer interchangeId;
@QField(backendName = "transport_message_id", maxLength = 255)
private String transportMessageId;
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "agreement_id", possibleValueSourceName = "edi_agreement")
private Integer agreementId;
@QField(backendName = "partner_id", possibleValueSourceName = "edi_partner")
private Integer partnerId;
@QField(backendName = "transport_id", possibleValueSourceName = "edi_transport")
private Integer transportId;
@QField(backendName = "outbound_request_id", possibleValueSourceName = "edi_outbound_request")
private Integer outboundRequestId;
@QField(backendName = "payload_ref", maxLength = 255)
private String payloadRef;
@QField(backendName = "payload_sha256", maxLength = 255)
private String payloadSha256;
@QField(backendName = "size_bytes")
private Long sizeBytes;
@QField(backendName = "status", maxLength = 255)
private String status;
@QField(backendName = "attempts")
private Integer attempts;
@QField(backendName = "next_attempt_at")
private Instant nextAttemptAt;
@QField(backendName = "claimed_at")
private Instant claimedAt;
@QField(backendName = "sent_at")
private Instant sentAt;
@QField(backendName = "delivered_at")
private Instant deliveredAt;
@QField(backendName = "last_error", maxLength = 255)
private String lastError;
@QField(backendName = "ack_for_group_id", possibleValueSourceName = "edi_functional_group")
private Integer ackForGroupId;
@QField(backendName = "ack_for_interchange_id", possibleValueSourceName = "edi_interchange")
private Integer ackForInterchangeId;
@QField(backendName = "create_date")
private Instant createDate;
@QField(backendName = "modify_date")
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer value)
{
id = value;
}
/*******************************************************************************
** Configure id.
*******************************************************************************/
public EdiOutboundMessage withId(Integer value)
{
id = value;
return (this);
}
/*******************************************************************************
** Get agreementId.
*******************************************************************************/
public Integer getAgreementId()
{
return (agreementId);
}
/*******************************************************************************
** Set agreementId.
*******************************************************************************/
public void setAgreementId(Integer value)
{
agreementId = value;
}
/*******************************************************************************
** Configure agreementId.
*******************************************************************************/
public EdiOutboundMessage withAgreementId(Integer value)
{
agreementId = value;
return (this);
}
/*******************************************************************************
** Get partnerId.
*******************************************************************************/
public Integer getPartnerId()
{
return (partnerId);
}
/*******************************************************************************
** Set partnerId.
*******************************************************************************/
public void setPartnerId(Integer value)
{
partnerId = value;
}
/*******************************************************************************
** Configure partnerId.
*******************************************************************************/
public EdiOutboundMessage withPartnerId(Integer value)
{
partnerId = value;
return (this);
}
/*******************************************************************************
** Get transportId.
*******************************************************************************/
public Integer getTransportId()
{
return (transportId);
}
/*******************************************************************************
** Set transportId.
*******************************************************************************/
public void setTransportId(Integer value)
{
transportId = value;
}
/*******************************************************************************
** Configure transportId.
*******************************************************************************/
public EdiOutboundMessage withTransportId(Integer value)
{
transportId = value;
return (this);
}
/*******************************************************************************
** Get outboundRequestId.
*******************************************************************************/
public Integer getOutboundRequestId()
{
return (outboundRequestId);
}
/*******************************************************************************
** Set outboundRequestId.
*******************************************************************************/
public void setOutboundRequestId(Integer value)
{
outboundRequestId = value;
}
/*******************************************************************************
** Configure outboundRequestId.
*******************************************************************************/
public EdiOutboundMessage withOutboundRequestId(Integer value)
{
outboundRequestId = value;
return (this);
}
/*******************************************************************************
** Get payloadRef.
*******************************************************************************/
public String getPayloadRef()
{
return (payloadRef);
}
/*******************************************************************************
** Set payloadRef.
*******************************************************************************/
public void setPayloadRef(String value)
{
payloadRef = value;
}
/*******************************************************************************
** Configure payloadRef.
*******************************************************************************/
public EdiOutboundMessage withPayloadRef(String value)
{
payloadRef = value;
return (this);
}
/*******************************************************************************
** Get payloadSha256.
*******************************************************************************/
public String getPayloadSha256()
{
return (payloadSha256);
}
/*******************************************************************************
** Set payloadSha256.
*******************************************************************************/
public void setPayloadSha256(String value)
{
payloadSha256 = value;
}
/*******************************************************************************
** Configure payloadSha256.
*******************************************************************************/
public EdiOutboundMessage withPayloadSha256(String value)
{
payloadSha256 = value;
return (this);
}
/*******************************************************************************
** Get sizeBytes.
*******************************************************************************/
public Long getSizeBytes()
{
return (sizeBytes);
}
/*******************************************************************************
** Set sizeBytes.
*******************************************************************************/
public void setSizeBytes(Long value)
{
sizeBytes = value;
}
/*******************************************************************************
** Configure sizeBytes.
*******************************************************************************/
public EdiOutboundMessage withSizeBytes(Long value)
{
sizeBytes = value;
return (this);
}
/*******************************************************************************
** Get status.
*******************************************************************************/
public String getStatus()
{
return (status);
}
/*******************************************************************************
** Set status.
*******************************************************************************/
public void setStatus(String value)
{
status = value;
}
/*******************************************************************************
** Configure status.
*******************************************************************************/
public EdiOutboundMessage withStatus(String value)
{
status = value;
return (this);
}
/*******************************************************************************
** Get attempts.
*******************************************************************************/
public Integer getAttempts()
{
return (attempts);
}
/*******************************************************************************
** Set attempts.
*******************************************************************************/
public void setAttempts(Integer value)
{
attempts = value;
}
/*******************************************************************************
** Configure attempts.
*******************************************************************************/
public EdiOutboundMessage withAttempts(Integer value)
{
attempts = value;
return (this);
}
/*******************************************************************************
** Get nextAttemptAt.
*******************************************************************************/
public Instant getNextAttemptAt()
{
return (nextAttemptAt);
}
/*******************************************************************************
** Set nextAttemptAt.
*******************************************************************************/
public void setNextAttemptAt(Instant value)
{
nextAttemptAt = value;
}
/*******************************************************************************
** Configure nextAttemptAt.
*******************************************************************************/
public EdiOutboundMessage withNextAttemptAt(Instant value)
{
nextAttemptAt = value;
return (this);
}
/*******************************************************************************
** Get claimedAt.
*******************************************************************************/
public Instant getClaimedAt()
{
return (claimedAt);
}
/*******************************************************************************
** Set claimedAt.
*******************************************************************************/
public void setClaimedAt(Instant value)
{
claimedAt = value;
}
/*******************************************************************************
** Configure claimedAt.
*******************************************************************************/
public EdiOutboundMessage withClaimedAt(Instant value)
{
claimedAt = value;
return (this);
}
/*******************************************************************************
** Get sentAt.
*******************************************************************************/
public Instant getSentAt()
{
return (sentAt);
}
/*******************************************************************************
** Set sentAt.
*******************************************************************************/
public void setSentAt(Instant value)
{
sentAt = value;
}
/*******************************************************************************
** Configure sentAt.
*******************************************************************************/
public EdiOutboundMessage withSentAt(Instant value)
{
sentAt = value;
return (this);
}
/*******************************************************************************
** Get deliveredAt.
*******************************************************************************/
public Instant getDeliveredAt()
{
return (deliveredAt);
}
/*******************************************************************************
** Set deliveredAt.
*******************************************************************************/
public void setDeliveredAt(Instant value)
{
deliveredAt = value;
}
/*******************************************************************************
** Configure deliveredAt.
*******************************************************************************/
public EdiOutboundMessage withDeliveredAt(Instant value)
{
deliveredAt = value;
return (this);
}
/*******************************************************************************
** Get lastError.
*******************************************************************************/
public String getLastError()
{
return (lastError);
}
/*******************************************************************************
** Set lastError.
*******************************************************************************/
public void setLastError(String value)
{
lastError = value;
}
/*******************************************************************************
** Configure lastError.
*******************************************************************************/
public EdiOutboundMessage withLastError(String value)
{
lastError = value;
return (this);
}
/*******************************************************************************
** Get ackForGroupId.
*******************************************************************************/
public Integer getAckForGroupId()
{
return (ackForGroupId);
}
/*******************************************************************************
** Set ackForGroupId.
*******************************************************************************/
public void setAckForGroupId(Integer value)
{
ackForGroupId = value;
}
/*******************************************************************************
** Configure ackForGroupId.
*******************************************************************************/
public EdiOutboundMessage withAckForGroupId(Integer value)
{
ackForGroupId = value;
return (this);
}
/*******************************************************************************
** Get ackForInterchangeId.
*******************************************************************************/
public Integer getAckForInterchangeId()
{
return (ackForInterchangeId);
}
/*******************************************************************************
** Set ackForInterchangeId.
*******************************************************************************/
public void setAckForInterchangeId(Integer value)
{
ackForInterchangeId = value;
}
/*******************************************************************************
** Configure ackForInterchangeId.
*******************************************************************************/
public EdiOutboundMessage withAckForInterchangeId(Integer value)
{
ackForInterchangeId = value;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant value)
{
createDate = value;
}
/*******************************************************************************
** Configure createDate.
*******************************************************************************/
public EdiOutboundMessage withCreateDate(Instant value)
{
createDate = value;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant value)
{
modifyDate = value;
}
/*******************************************************************************
** Configure modifyDate.
*******************************************************************************/
public EdiOutboundMessage withModifyDate(Instant value)
{
modifyDate = value;
return (this);
}
/*******************************************************************************
** Get interchangeId.
*******************************************************************************/
public Integer getInterchangeId()
{
return (interchangeId);
}
/*******************************************************************************
** Set interchangeId.
*******************************************************************************/
public void setInterchangeId(Integer value)
{
interchangeId = value;
}
/*******************************************************************************
** Configure interchangeId.
*******************************************************************************/
public EdiOutboundMessage withInterchangeId(Integer value)
{
interchangeId = value;
return (this);
}
/*******************************************************************************
** Get transportMessageId.
*******************************************************************************/
public String getTransportMessageId()
{
return (transportMessageId);
}
/*******************************************************************************
** Set transportMessageId.
*******************************************************************************/
public void setTransportMessageId(String value)
{
transportMessageId = value;
}
/*******************************************************************************
** Configure transportMessageId.
*******************************************************************************/
public EdiOutboundMessage withTransportMessageId(String value)
{
transportMessageId = value;
return (this);
}
}
@@ -1,372 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_outbound_request record.
*******************************************************************************/
public class EdiOutboundRequest extends QRecordEntity
{
public static final String TABLE_NAME = "edi_outbound_request";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "agreement_document_id", possibleValueSourceName = "edi_agreement_document")
private Integer agreementDocumentId;
@QField(backendName = "source_table_name", maxLength = 255)
private String sourceTableName;
@QField(backendName = "source_record_id", maxLength = 255)
private String sourceRecordId;
@QField(backendName = "event_id", maxLength = 255)
private String eventId;
@QField(backendName = "status", maxLength = 255)
private String status;
@QField(backendName = "attempts")
private Integer attempts;
@QField(backendName = "next_attempt_at")
private Instant nextAttemptAt;
@QField(backendName = "claimed_at")
private Instant claimedAt;
@QField(backendName = "last_error", maxLength = 255)
private String lastError;
@QField(backendName = "create_date")
private Instant createDate;
@QField(backendName = "modify_date")
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer value)
{
id = value;
}
/*******************************************************************************
** Configure id.
*******************************************************************************/
public EdiOutboundRequest withId(Integer value)
{
id = value;
return (this);
}
/*******************************************************************************
** Get agreementDocumentId.
*******************************************************************************/
public Integer getAgreementDocumentId()
{
return (agreementDocumentId);
}
/*******************************************************************************
** Set agreementDocumentId.
*******************************************************************************/
public void setAgreementDocumentId(Integer value)
{
agreementDocumentId = value;
}
/*******************************************************************************
** Configure agreementDocumentId.
*******************************************************************************/
public EdiOutboundRequest withAgreementDocumentId(Integer value)
{
agreementDocumentId = value;
return (this);
}
/*******************************************************************************
** Get sourceTableName.
*******************************************************************************/
public String getSourceTableName()
{
return (sourceTableName);
}
/*******************************************************************************
** Set sourceTableName.
*******************************************************************************/
public void setSourceTableName(String value)
{
sourceTableName = value;
}
/*******************************************************************************
** Configure sourceTableName.
*******************************************************************************/
public EdiOutboundRequest withSourceTableName(String value)
{
sourceTableName = value;
return (this);
}
/*******************************************************************************
** Get sourceRecordId.
*******************************************************************************/
public String getSourceRecordId()
{
return (sourceRecordId);
}
/*******************************************************************************
** Set sourceRecordId.
*******************************************************************************/
public void setSourceRecordId(String value)
{
sourceRecordId = value;
}
/*******************************************************************************
** Configure sourceRecordId.
*******************************************************************************/
public EdiOutboundRequest withSourceRecordId(String value)
{
sourceRecordId = value;
return (this);
}
/*******************************************************************************
** Get eventId.
*******************************************************************************/
public String getEventId()
{
return (eventId);
}
/*******************************************************************************
** Set eventId.
*******************************************************************************/
public void setEventId(String value)
{
eventId = value;
}
/*******************************************************************************
** Configure eventId.
*******************************************************************************/
public EdiOutboundRequest withEventId(String value)
{
eventId = value;
return (this);
}
/*******************************************************************************
** Get status.
*******************************************************************************/
public String getStatus()
{
return (status);
}
/*******************************************************************************
** Set status.
*******************************************************************************/
public void setStatus(String value)
{
status = value;
}
/*******************************************************************************
** Configure status.
*******************************************************************************/
public EdiOutboundRequest withStatus(String value)
{
status = value;
return (this);
}
/*******************************************************************************
** Get attempts.
*******************************************************************************/
public Integer getAttempts()
{
return (attempts);
}
/*******************************************************************************
** Set attempts.
*******************************************************************************/
public void setAttempts(Integer value)
{
attempts = value;
}
/*******************************************************************************
** Configure attempts.
*******************************************************************************/
public EdiOutboundRequest withAttempts(Integer value)
{
attempts = value;
return (this);
}
/*******************************************************************************
** Get nextAttemptAt.
*******************************************************************************/
public Instant getNextAttemptAt()
{
return (nextAttemptAt);
}
/*******************************************************************************
** Set nextAttemptAt.
*******************************************************************************/
public void setNextAttemptAt(Instant value)
{
nextAttemptAt = value;
}
/*******************************************************************************
** Configure nextAttemptAt.
*******************************************************************************/
public EdiOutboundRequest withNextAttemptAt(Instant value)
{
nextAttemptAt = value;
return (this);
}
/*******************************************************************************
** Get claimedAt.
*******************************************************************************/
public Instant getClaimedAt()
{
return (claimedAt);
}
/*******************************************************************************
** Set claimedAt.
*******************************************************************************/
public void setClaimedAt(Instant value)
{
claimedAt = value;
}
/*******************************************************************************
** Configure claimedAt.
*******************************************************************************/
public EdiOutboundRequest withClaimedAt(Instant value)
{
claimedAt = value;
return (this);
}
/*******************************************************************************
** Get lastError.
*******************************************************************************/
public String getLastError()
{
return (lastError);
}
/*******************************************************************************
** Set lastError.
*******************************************************************************/
public void setLastError(String value)
{
lastError = value;
}
/*******************************************************************************
** Configure lastError.
*******************************************************************************/
public EdiOutboundRequest withLastError(String value)
{
lastError = value;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant value)
{
createDate = value;
}
/*******************************************************************************
** Configure createDate.
*******************************************************************************/
public EdiOutboundRequest withCreateDate(Instant value)
{
createDate = value;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant value)
{
modifyDate = value;
}
/*******************************************************************************
** Configure modifyDate.
*******************************************************************************/
public EdiOutboundRequest withModifyDate(Instant value)
{
modifyDate = value;
return (this);
}
}
@@ -1,467 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_partner record.
*******************************************************************************/
public class EdiPartner extends QRecordEntity
{
public static final String TABLE_NAME = "edi_partner";
@QField(backendName = "gateway_username", maxLength = 255)
private String gatewayUsername;
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "name", maxLength = 255)
private String name;
@QField(backendName = "code", maxLength = 255)
private String code;
@QField(backendName = "status", maxLength = 255, possibleValueSourceName = "edi_partner_status")
private String status;
@QField(backendName = "contact_name", maxLength = 255)
private String contactName;
@QField(backendName = "contact_email", maxLength = 255)
private String contactEmail;
@QField(backendName = "contact_phone", maxLength = 255)
private String contactPhone;
@QField(backendName = "notes", maxLength = 10000)
private String notes;
@QField(backendName = "default_timezone", maxLength = 255)
private String defaultTimezone;
@QField(backendName = "contains_phi", defaultValue = "false")
private Boolean containsPhi;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
@QField(backendName = "modified_by", maxLength = 255)
private String modifiedBy;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiPartner withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get name.
*******************************************************************************/
public String getName()
{
return (name);
}
/*******************************************************************************
** Set name.
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Set name fluently.
*******************************************************************************/
public EdiPartner withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Get code.
*******************************************************************************/
public String getCode()
{
return (code);
}
/*******************************************************************************
** Set code.
*******************************************************************************/
public void setCode(String code)
{
this.code = code;
}
/*******************************************************************************
** Set code fluently.
*******************************************************************************/
public EdiPartner withCode(String code)
{
this.code = code;
return (this);
}
/*******************************************************************************
** Get status.
*******************************************************************************/
public String getStatus()
{
return (status);
}
/*******************************************************************************
** Set status.
*******************************************************************************/
public void setStatus(String status)
{
this.status = status;
}
/*******************************************************************************
** Set status fluently.
*******************************************************************************/
public EdiPartner withStatus(String status)
{
this.status = status;
return (this);
}
/*******************************************************************************
** Get contactName.
*******************************************************************************/
public String getContactName()
{
return (contactName);
}
/*******************************************************************************
** Set contactName.
*******************************************************************************/
public void setContactName(String contactName)
{
this.contactName = contactName;
}
/*******************************************************************************
** Set contactName fluently.
*******************************************************************************/
public EdiPartner withContactName(String contactName)
{
this.contactName = contactName;
return (this);
}
/*******************************************************************************
** Get contactEmail.
*******************************************************************************/
public String getContactEmail()
{
return (contactEmail);
}
/*******************************************************************************
** Set contactEmail.
*******************************************************************************/
public void setContactEmail(String contactEmail)
{
this.contactEmail = contactEmail;
}
/*******************************************************************************
** Set contactEmail fluently.
*******************************************************************************/
public EdiPartner withContactEmail(String contactEmail)
{
this.contactEmail = contactEmail;
return (this);
}
/*******************************************************************************
** Get contactPhone.
*******************************************************************************/
public String getContactPhone()
{
return (contactPhone);
}
/*******************************************************************************
** Set contactPhone.
*******************************************************************************/
public void setContactPhone(String contactPhone)
{
this.contactPhone = contactPhone;
}
/*******************************************************************************
** Set contactPhone fluently.
*******************************************************************************/
public EdiPartner withContactPhone(String contactPhone)
{
this.contactPhone = contactPhone;
return (this);
}
/*******************************************************************************
** Get notes.
*******************************************************************************/
public String getNotes()
{
return (notes);
}
/*******************************************************************************
** Set notes.
*******************************************************************************/
public void setNotes(String notes)
{
this.notes = notes;
}
/*******************************************************************************
** Set notes fluently.
*******************************************************************************/
public EdiPartner withNotes(String notes)
{
this.notes = notes;
return (this);
}
/*******************************************************************************
** Get defaultTimezone.
*******************************************************************************/
public String getDefaultTimezone()
{
return (defaultTimezone);
}
/*******************************************************************************
** Set defaultTimezone.
*******************************************************************************/
public void setDefaultTimezone(String defaultTimezone)
{
this.defaultTimezone = defaultTimezone;
}
/*******************************************************************************
** Set defaultTimezone fluently.
*******************************************************************************/
public EdiPartner withDefaultTimezone(String defaultTimezone)
{
this.defaultTimezone = defaultTimezone;
return (this);
}
/*******************************************************************************
** Get containsPhi.
*******************************************************************************/
public Boolean getContainsPhi()
{
return (containsPhi);
}
/*******************************************************************************
** Set containsPhi.
*******************************************************************************/
public void setContainsPhi(Boolean containsPhi)
{
this.containsPhi = containsPhi;
}
/*******************************************************************************
** Set containsPhi fluently.
*******************************************************************************/
public EdiPartner withContainsPhi(Boolean containsPhi)
{
this.containsPhi = containsPhi;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiPartner withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiPartner withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
/*******************************************************************************
** Get modifiedBy.
*******************************************************************************/
public String getModifiedBy()
{
return (modifiedBy);
}
/*******************************************************************************
** Set modifiedBy.
*******************************************************************************/
public void setModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
}
/*******************************************************************************
** Set modifiedBy fluently.
*******************************************************************************/
public EdiPartner withModifiedBy(String modifiedBy)
{
this.modifiedBy = modifiedBy;
return (this);
}
/*******************************************************************************
** Get gatewayUsername.
*******************************************************************************/
public String getGatewayUsername()
{
return (gatewayUsername);
}
/*******************************************************************************
** Set gatewayUsername.
*******************************************************************************/
public void setGatewayUsername(String value)
{
gatewayUsername = value;
}
/*******************************************************************************
** Configure gatewayUsername.
*******************************************************************************/
public EdiPartner withGatewayUsername(String value)
{
gatewayUsername = value;
return (this);
}
}
@@ -1,284 +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.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Persistent edi_payload record.
*******************************************************************************/
public class EdiPayload extends QRecordEntity
{
public static final String TABLE_NAME = "edi_payload";
@QField(backendName = "id", isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(backendName = "kind", maxLength = 255, possibleValueSourceName = "edi_payload_kind")
private String kind;
@QField(backendName = "ciphertext")
private byte[] ciphertext;
@QField(backendName = "key_id", maxLength = 255)
private String keyId;
@QField(backendName = "sha256", maxLength = 255)
private String sha256;
@QField(backendName = "size_bytes")
private Long sizeBytes;
@QField(backendName = "create_date", isEditable = false)
private Instant createDate;
@QField(backendName = "modify_date", isEditable = false)
private Instant modifyDate;
/*******************************************************************************
** Get id.
*******************************************************************************/
public Integer getId()
{
return (id);
}
/*******************************************************************************
** Set id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Set id fluently.
*******************************************************************************/
public EdiPayload withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Get kind.
*******************************************************************************/
public String getKind()
{
return (kind);
}
/*******************************************************************************
** Set kind.
*******************************************************************************/
public void setKind(String kind)
{
this.kind = kind;
}
/*******************************************************************************
** Set kind fluently.
*******************************************************************************/
public EdiPayload withKind(String kind)
{
this.kind = kind;
return (this);
}
/*******************************************************************************
** Get ciphertext.
*******************************************************************************/
public byte[] getCiphertext()
{
return (ciphertext);
}
/*******************************************************************************
** Set ciphertext.
*******************************************************************************/
public void setCiphertext(byte[] ciphertext)
{
this.ciphertext = ciphertext;
}
/*******************************************************************************
** Set ciphertext fluently.
*******************************************************************************/
public EdiPayload withCiphertext(byte[] ciphertext)
{
this.ciphertext = ciphertext;
return (this);
}
/*******************************************************************************
** Get keyId.
*******************************************************************************/
public String getKeyId()
{
return (keyId);
}
/*******************************************************************************
** Set keyId.
*******************************************************************************/
public void setKeyId(String keyId)
{
this.keyId = keyId;
}
/*******************************************************************************
** Set keyId fluently.
*******************************************************************************/
public EdiPayload withKeyId(String keyId)
{
this.keyId = keyId;
return (this);
}
/*******************************************************************************
** Get sha256.
*******************************************************************************/
public String getSha256()
{
return (sha256);
}
/*******************************************************************************
** Set sha256.
*******************************************************************************/
public void setSha256(String sha256)
{
this.sha256 = sha256;
}
/*******************************************************************************
** Set sha256 fluently.
*******************************************************************************/
public EdiPayload withSha256(String sha256)
{
this.sha256 = sha256;
return (this);
}
/*******************************************************************************
** Get sizeBytes.
*******************************************************************************/
public Long getSizeBytes()
{
return (sizeBytes);
}
/*******************************************************************************
** Set sizeBytes.
*******************************************************************************/
public void setSizeBytes(Long sizeBytes)
{
this.sizeBytes = sizeBytes;
}
/*******************************************************************************
** Set sizeBytes fluently.
*******************************************************************************/
public EdiPayload withSizeBytes(Long sizeBytes)
{
this.sizeBytes = sizeBytes;
return (this);
}
/*******************************************************************************
** Get createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (createDate);
}
/*******************************************************************************
** Set createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Set createDate fluently.
*******************************************************************************/
public EdiPayload withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Get modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (modifyDate);
}
/*******************************************************************************
** Set modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Set modifyDate fluently.
*******************************************************************************/
public EdiPayload withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
}
Loaded 100 of 199 files, more files were not shown because too many files have changed in this diff. Show more