Move System, Dead letters, Certificates expiring and Setup status off the EDI home into EdiHome.ADMIN_WIDGET_NAMES, gated to edi.admin.hasAccess (hidden when denied) and full width, for a host to place on its Admin home. Every remaining home widget requires edi.hasAccess (hidden when denied); none is NOT_PROTECTED. Add "Inbound documents are delayed": an alert at the top of the home, shown only when the oldest unprocessed inbound document is older than inboundDelayedAfterMinutes (default 60), with its age and a link to the filtered inbound documents. Setup counts that fail show "Unavailable" and are logged, never -1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
417 lines
15 KiB
Java
417 lines
15 KiB
Java
/*
|
|
* 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 com.kingsrook.qbits.edi.guide.ExternalCodeSetProvider externalCodeSetProvider;
|
|
|
|
/*******************************************************************************
|
|
** Optional host provider for licensed external code sets.
|
|
*******************************************************************************/
|
|
@JsonIgnore
|
|
public com.kingsrook.qbits.edi.guide.ExternalCodeSetProvider getExternalCodeSetProvider()
|
|
{
|
|
return (externalCodeSetProvider);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
** Register a provider; null retains format-only validation.
|
|
*******************************************************************************/
|
|
public EdiQBitConfig withExternalCodeSetProvider(com.kingsrook.qbits.edi.guide.ExternalCodeSetProvider value)
|
|
{
|
|
externalCodeSetProvider = value;
|
|
return (this);
|
|
}
|
|
|
|
private String payloadBucketAlias = "edi";
|
|
private java.util.function.Supplier<com.kingsrook.qqq.backend.core.model.session.QSession> serviceSessionSupplier = null;
|
|
private java.time.Clock clock = java.time.Clock.systemUTC();
|
|
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;
|
|
private String publicBaseUrl;
|
|
private Integer inboundDelayedAfterMinutes = 60;
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
** 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(inboundDelayedAfterMinutes == null || inboundDelayedAfterMinutes < 1)
|
|
{
|
|
errors.add("inboundDelayedAfterMinutes must be at least 1");
|
|
}
|
|
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);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
** Public base URL of this deployment (for example https://edi.example.com),
|
|
** used to build the asynchronous AS2 receipt address
|
|
** {base}/qqq/hooks/edi/as2/{transport}/mdn.
|
|
*******************************************************************************/
|
|
public String getPublicBaseUrl()
|
|
{
|
|
return (publicBaseUrl);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
** Set the public base URL.
|
|
*******************************************************************************/
|
|
public EdiQBitConfig withPublicBaseUrl(String value)
|
|
{
|
|
publicBaseUrl = value;
|
|
return (this);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
** How old (in minutes) the oldest unprocessed inbound document may get before
|
|
** the EDI home says "Inbound documents are delayed". Default 60.
|
|
*******************************************************************************/
|
|
public Integer getInboundDelayedAfterMinutes()
|
|
{
|
|
return (inboundDelayedAfterMinutes);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
** Set the inbound delay threshold in minutes.
|
|
*******************************************************************************/
|
|
public EdiQBitConfig withInboundDelayedAfterMinutes(Integer value)
|
|
{
|
|
inboundDelayedAfterMinutes = value;
|
|
return (this);
|
|
}
|
|
}
|