From aab4223de08fe56612161ce111dfc6db15cbbc73 Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Wed, 22 Jul 2026 13:34:53 +0200 Subject: [PATCH 1/2] Added custom fields per outbound transaction; #64 --- .../ap/api/IOutboundTransactionManager.java | 11 +- .../ap/api/dto/OutboundS3SubmitRequest.java | 54 ++++++++ .../api/dto/OutboundTransactionResponse.java | 57 +++++++++ .../ap/api/model/IOutboundTransaction.java | 18 +++ .../api/dto/OutboundS3SubmitRequestTest.java | 9 ++ .../dto/OutboundTransactionResponseTest.java | 28 ++++- .../helger/phoss/ap/core/mls/MlsHandler.java | 10 +- .../core/outbound/OutboundOrchestrator.java | 32 ++++- .../core/reporting/APPeppolReportHelper.java | 9 +- .../ap/db/OutboundTransactionManagerJdbc.java | 15 ++- .../ap/db/dto/OutboundTransactionRow.java | 27 ++++ .../V4__OutboundCustomFields.sql | 28 +++++ .../V4__OutboundCustomFields.sql | 28 +++++ .../V4__OutboundCustomFields.sql | 28 +++++ .../V4__OutboundCustomFields.sql | 28 +++++ .../ap/db/JdbcManagerIntegrationTest.java | 66 +++++++++- .../ap/db/dto/OutboundTransactionRowTest.java | 24 +++- .../ap/dirsender/DirectoryFileProcessor.java | 3 +- .../webapp/controller/OutboundController.java | 115 ++++++++++++++++-- 19 files changed, 565 insertions(+), 25 deletions(-) create mode 100644 phoss-ap-db/src/main/resources/db/phoss-ap-flyway-db2/V4__OutboundCustomFields.sql create mode 100644 phoss-ap-db/src/main/resources/db/phoss-ap-flyway-h2/V4__OutboundCustomFields.sql create mode 100644 phoss-ap-db/src/main/resources/db/phoss-ap-flyway-mysql/V4__OutboundCustomFields.sql create mode 100644 phoss-ap-db/src/main/resources/db/phoss-ap-flyway-postgresql/V4__OutboundCustomFields.sql diff --git a/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/IOutboundTransactionManager.java b/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/IOutboundTransactionManager.java index 7b710ae1..101e6573 100644 --- a/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/IOutboundTransactionManager.java +++ b/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/IOutboundTransactionManager.java @@ -83,6 +83,12 @@ public interface IOutboundTransactionManager * @param sPayloadMimeType * MIME type for binary payloads (e.g. {@code application/pdf}). May be null * for XML payloads. + * @param sCustom1 + * First optional custom field (max 255 characters). May be null. + * @param sCustom2 + * Second optional custom field (max 255 characters). May be null. + * @param sCustom3 + * Third optional custom field (max 255 characters). May be null. * @return The ID of the created transaction. Only null if insertion fails. */ @Nullable @@ -103,7 +109,10 @@ String create (@NonNull ETransactionType eTransactionType, @Nullable String sSbdhStandard, @Nullable String sSbdhTypeVersion, @Nullable String sSbdhType, - @Nullable String sPayloadMimeType); + @Nullable String sPayloadMimeType, + @Nullable String sCustom1, + @Nullable String sCustom2, + @Nullable String sCustom3); /** * Look up a transaction by its unique ID. diff --git a/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/dto/OutboundS3SubmitRequest.java b/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/dto/OutboundS3SubmitRequest.java index 65cd4bc9..ce429077 100644 --- a/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/dto/OutboundS3SubmitRequest.java +++ b/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/dto/OutboundS3SubmitRequest.java @@ -84,6 +84,15 @@ public class OutboundS3SubmitRequest example = "application/pdf") private String payloadMimeType; + @Schema (description = "Optional custom field 1 (max 255 characters). Stored with the transaction and returned by the status APIs.") + private String custom1; + + @Schema (description = "Optional custom field 2 (max 255 characters). Stored with the transaction and returned by the status APIs.") + private String custom2; + + @Schema (description = "Optional custom field 3 (max 255 characters). Stored with the transaction and returned by the status APIs.") + private String custom3; + /** @return the sender participant ID */ public String getSenderID () { @@ -278,4 +287,49 @@ public void setPayloadMimeType (final String s) { payloadMimeType = s; } + + /** @return the optional custom field 1 */ + public String getCustom1 () + { + return custom1; + } + + /** + * @param s + * The custom field 1 to set. + */ + public void setCustom1 (final String s) + { + custom1 = s; + } + + /** @return the optional custom field 2 */ + public String getCustom2 () + { + return custom2; + } + + /** + * @param s + * The custom field 2 to set. + */ + public void setCustom2 (final String s) + { + custom2 = s; + } + + /** @return the optional custom field 3 */ + public String getCustom3 () + { + return custom3; + } + + /** + * @param s + * The custom field 3 to set. + */ + public void setCustom3 (final String s) + { + custom3 = s; + } } diff --git a/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/dto/OutboundTransactionResponse.java b/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/dto/OutboundTransactionResponse.java index 38a5073d..5747ed3c 100644 --- a/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/dto/OutboundTransactionResponse.java +++ b/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/dto/OutboundTransactionResponse.java @@ -87,6 +87,15 @@ public class OutboundTransactionResponse nullable = true) private String mlsStatus; + @Schema (description = "First optional custom field (max 255 characters); null if not set", nullable = true) + private String custom1; + + @Schema (description = "Second optional custom field (max 255 characters); null if not set", nullable = true) + private String custom2; + + @Schema (description = "Third optional custom field (max 255 characters); null if not set", nullable = true) + private String custom3; + /** * Default constructor for JSON deserialization. */ @@ -119,6 +128,9 @@ public static OutboundTransactionResponse fromDomain (@NonNull final IOutboundTr aResp.nextRetryDT = aTx.getNextRetryDT () != null ? aTx.getNextRetryDT ().toString () : null; aResp.errorDetails = aTx.getErrorDetails (); aResp.mlsStatus = aTx.getMlsStatus () != null ? aTx.getMlsStatus ().getID () : null; + aResp.custom1 = aTx.getCustom1 (); + aResp.custom2 = aTx.getCustom2 (); + aResp.custom3 = aTx.getCustom3 (); return aResp; } @@ -346,4 +358,49 @@ public void setMlsStatus (final String s) { mlsStatus = s; } + + /** @return the first custom field */ + public String getCustom1 () + { + return custom1; + } + + /** + * @param s + * The first custom field to set. + */ + public void setCustom1 (final String s) + { + custom1 = s; + } + + /** @return the second custom field */ + public String getCustom2 () + { + return custom2; + } + + /** + * @param s + * The second custom field to set. + */ + public void setCustom2 (final String s) + { + custom2 = s; + } + + /** @return the third custom field */ + public String getCustom3 () + { + return custom3; + } + + /** + * @param s + * The third custom field to set. + */ + public void setCustom3 (final String s) + { + custom3 = s; + } } diff --git a/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/model/IOutboundTransaction.java b/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/model/IOutboundTransaction.java index 5763ae29..2712ab3b 100644 --- a/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/model/IOutboundTransaction.java +++ b/phoss-ap-api/src/main/java/com/helger/phoss/ap/api/model/IOutboundTransaction.java @@ -220,4 +220,22 @@ public interface IOutboundTransaction extends IHasID */ @Nullable String getPayloadMimeType (); + + /** + * @return The first optional custom field (max 255 characters), or null if not set. + */ + @Nullable + String getCustom1 (); + + /** + * @return The second optional custom field (max 255 characters), or null if not set. + */ + @Nullable + String getCustom2 (); + + /** + * @return The third optional custom field (max 255 characters), or null if not set. + */ + @Nullable + String getCustom3 (); } diff --git a/phoss-ap-api/src/test/java/com/helger/phoss/ap/api/dto/OutboundS3SubmitRequestTest.java b/phoss-ap-api/src/test/java/com/helger/phoss/ap/api/dto/OutboundS3SubmitRequestTest.java index 891ae057..e72bd51a 100644 --- a/phoss-ap-api/src/test/java/com/helger/phoss/ap/api/dto/OutboundS3SubmitRequestTest.java +++ b/phoss-ap-api/src/test/java/com/helger/phoss/ap/api/dto/OutboundS3SubmitRequestTest.java @@ -45,6 +45,9 @@ public void testDefaultConstructor () assertNull (a.getSbdhTypeVersion ()); assertNull (a.getSbdhType ()); assertNull (a.getPayloadMimeType ()); + assertNull (a.getCustom1 ()); + assertNull (a.getCustom2 ()); + assertNull (a.getCustom3 ()); } @Test @@ -64,6 +67,9 @@ public void testSetters () a.setSbdhTypeVersion ("1.0"); a.setSbdhType ("type1"); a.setPayloadMimeType ("application/pdf"); + a.setCustom1 ("c1"); + a.setCustom2 ("c2"); + a.setCustom3 ("c3"); assertEquals ("sender1", a.getSenderID ()); assertEquals ("receiver1", a.getReceiverID ()); @@ -78,5 +84,8 @@ public void testSetters () assertEquals ("1.0", a.getSbdhTypeVersion ()); assertEquals ("type1", a.getSbdhType ()); assertEquals ("application/pdf", a.getPayloadMimeType ()); + assertEquals ("c1", a.getCustom1 ()); + assertEquals ("c2", a.getCustom2 ()); + assertEquals ("c3", a.getCustom3 ()); } } diff --git a/phoss-ap-api/src/test/java/com/helger/phoss/ap/api/dto/OutboundTransactionResponseTest.java b/phoss-ap-api/src/test/java/com/helger/phoss/ap/api/dto/OutboundTransactionResponseTest.java index f6ea53d0..4aa4a986 100644 --- a/phoss-ap-api/src/test/java/com/helger/phoss/ap/api/dto/OutboundTransactionResponseTest.java +++ b/phoss-ap-api/src/test/java/com/helger/phoss/ap/api/dto/OutboundTransactionResponseTest.java @@ -60,6 +60,9 @@ public void testDefaultConstructor () assertNull (a.getNextRetryDT ()); assertNull (a.getErrorDetails ()); assertNull (a.getMlsStatus ()); + assertNull (a.getCustom1 ()); + assertNull (a.getCustom2 ()); + assertNull (a.getCustom3 ()); } @Test @@ -81,6 +84,9 @@ public void testSettersAndGetters () a.setNextRetryDT ("2026-03-29T11:00:00Z"); a.setErrorDetails ("some error"); a.setMlsStatus ("received_ap"); + a.setCustom1 ("c1"); + a.setCustom2 ("c2"); + a.setCustom3 ("c3"); assertEquals ("id1", a.getID ()); assertEquals ("business_document", a.getTransactionType ()); @@ -97,6 +103,9 @@ public void testSettersAndGetters () assertEquals ("2026-03-29T11:00:00Z", a.getNextRetryDT ()); assertEquals ("some error", a.getErrorDetails ()); assertEquals ("received_ap", a.getMlsStatus ()); + assertEquals ("c1", a.getCustom1 ()); + assertEquals ("c2", a.getCustom2 ()); + assertEquals ("c3", a.getCustom3 ()); } @Test @@ -133,7 +142,10 @@ public void testFromDomainAllFieldsPopulated () null, null, null, - null); + null, + "custom-a", + "custom-b", + "custom-c"); final OutboundTransactionResponse aResp = OutboundTransactionResponse.fromDomain (aTx); assertNotNull (aResp); @@ -152,6 +164,9 @@ public void testFromDomainAllFieldsPopulated () assertNotNull (aResp.getNextRetryDT ()); assertEquals ("err details", aResp.getErrorDetails ()); assertEquals ("received_ap", aResp.getMlsStatus ()); + assertEquals ("custom-a", aResp.getCustom1 ()); + assertEquals ("custom-b", aResp.getCustom2 ()); + assertEquals ("custom-c", aResp.getCustom3 ()); } @Test @@ -186,6 +201,9 @@ public void testFromDomainNullableFieldsNull () null, null, null, + null, + null, + null, null); final OutboundTransactionResponse aResp = OutboundTransactionResponse.fromDomain (aTx); @@ -228,7 +246,10 @@ private static IOutboundTransaction _createMock (@NonNull final String sID, @Nullable final String sSbdhStandard, @Nullable final String sSbdhTypeVersion, @Nullable final String sSbdhType, - @Nullable final String sPayloadMimeType) + @Nullable final String sPayloadMimeType, + @Nullable final String sCustom1, + @Nullable final String sCustom2, + @Nullable final String sCustom3) { return new IOutboundTransaction () { @@ -260,6 +281,9 @@ private static IOutboundTransaction _createMock (@NonNull final String sID, public String getSbdhTypeVersion () { return sSbdhTypeVersion; } public String getSbdhType () { return sSbdhType; } public String getPayloadMimeType () { return sPayloadMimeType; } + public String getCustom1 () { return sCustom1; } + public String getCustom2 () { return sCustom2; } + public String getCustom3 () { return sCustom3; } }; } } diff --git a/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/mls/MlsHandler.java b/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/mls/MlsHandler.java index aea09a47..1f2a2205 100644 --- a/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/mls/MlsHandler.java +++ b/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/mls/MlsHandler.java @@ -213,6 +213,11 @@ public static ESuccess triggerSendingInboundResultMls (@NonNull final IInboundTr final String sSbdhType = null; final String sPayloadMimeType = null; + // Custom fields do not apply to system-generated MLS responses + final String sCustom1 = null; + final String sCustom2 = null; + final String sCustom3 = null; + // Create outbound transaction final String sMlsTxID = aOutboundMgr.create (ETransactionType.MLS_RESPONSE, aMLSSenderPID.getURIEncoded (), @@ -231,7 +236,10 @@ public static ESuccess triggerSendingInboundResultMls (@NonNull final IInboundTr sSbdhStandard, sSbdhTypeVersion, sSbdhType, - sPayloadMimeType); + sPayloadMimeType, + sCustom1, + sCustom2, + sCustom3); final var aMlsTx = aOutboundMgr.getByID (sMlsTxID); if (aMlsTx == null) { diff --git a/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/outbound/OutboundOrchestrator.java b/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/outbound/OutboundOrchestrator.java index df38ce5e..47cb7ff1 100644 --- a/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/outbound/OutboundOrchestrator.java +++ b/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/outbound/OutboundOrchestrator.java @@ -141,6 +141,12 @@ private OutboundOrchestrator () * @param sPayloadMimeType * Optional payload MIME type (e.g. "application/pdf"). May be null for XML * payloads. + * @param sCustom1 + * First optional custom field (max 255 characters). May be null. + * @param sCustom2 + * Second optional custom field (max 255 characters). May be null. + * @param sCustom3 + * Third optional custom field (max 255 characters). May be null. * @return The created {@link IOutboundTransaction} or null if the document could not * be stored or verification failed. */ @@ -157,7 +163,10 @@ public static IOutboundTransaction submitRawDocument (@NonNull final String sLog @Nullable final String sSbdhStandard, @Nullable final String sSbdhTypeVersion, @Nullable final String sSbdhType, - @Nullable final String sPayloadMimeType) + @Nullable final String sPayloadMimeType, + @Nullable final String sCustom1, + @Nullable final String sCustom2, + @Nullable final String sCustom3) { LOGGER.info (sLogPrefix + "Submitting raw document with SBDH Instance ID '" + sSbdhInstanceID + "'"); @@ -263,7 +272,10 @@ public static IOutboundTransaction submitRawDocument (@NonNull final String sLog sSbdhStandard, sSbdhTypeVersion, sSbdhType, - sPayloadMimeType); + sPayloadMimeType, + sCustom1, + sCustom2, + sCustom3); for (final var aHandler : APCoreMetaManager.getAllLifecycleHandlers ()) aHandler.onOutboundDocumentAccepted (sTransactionID, aSenderID.getURIEncoded (), @@ -285,13 +297,22 @@ public static IOutboundTransaction submitRawDocument (@NonNull final String sLog * The input stream containing the complete pre-built SBD. May not be null. * @param sMlsTo * Optional MLS "To" address. May be null. + * @param sCustom1 + * First optional custom field (max 255 characters). May be null. + * @param sCustom2 + * Second optional custom field (max 255 characters). May be null. + * @param sCustom3 + * Third optional custom field (max 255 characters). May be null. * @return The created {@link IOutboundTransaction} or null if the SBD could not be * parsed. */ @Nullable public static IOutboundTransaction submitPrebuiltSBD (@NonNull final String sLogPrefix, @NonNull final InputStream aSbdIS, - @Nullable final String sMlsTo) + @Nullable final String sMlsTo, + @Nullable final String sCustom1, + @Nullable final String sCustom2, + @Nullable final String sCustom3) { LOGGER.info (sLogPrefix + "Submitting pre-built SBD"); @@ -403,7 +424,10 @@ public static IOutboundTransaction submitPrebuiltSBD (@NonNull final String sLog sSbdhStandard, sSbdhTypeVersion, sSbdhType, - sPayloadMimeType); + sPayloadMimeType, + sCustom1, + sCustom2, + sCustom3); for (final var aHandler : APCoreMetaManager.getAllLifecycleHandlers ()) aHandler.onOutboundDocumentAccepted (sTransactionID, aSbdData.getSenderURIEncoded (), diff --git a/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/reporting/APPeppolReportHelper.java b/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/reporting/APPeppolReportHelper.java index 0c4af928..79eb5de0 100644 --- a/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/reporting/APPeppolReportHelper.java +++ b/phoss-ap-core/src/main/java/com/helger/phoss/ap/core/reporting/APPeppolReportHelper.java @@ -309,6 +309,10 @@ public static ESuccess createAndSendPeppolReports (@NonNull final YearMonth aYea final String sSbdhTypeVersion = null; final String sSbdhType = null; final String sPayloadMimeType = null; + // Custom fields do not apply to system-generated Peppol Reporting documents + final String sCustom1 = null; + final String sCustom2 = null; + final String sCustom3 = null; final IOutboundTransaction aTx = OutboundOrchestrator.submitRawDocument ("[PeppolReporting] ", aSenderID, aReceiverID, @@ -321,7 +325,10 @@ public static ESuccess createAndSendPeppolReports (@NonNull final YearMonth aYea sSbdhStandard, sSbdhTypeVersion, sSbdhType, - sPayloadMimeType); + sPayloadMimeType, + sCustom1, + sCustom2, + sCustom3); if (aTx == null) throw new IllegalStateException ("Failed to submit Peppol Reporting document for transmission"); diff --git a/phoss-ap-db/src/main/java/com/helger/phoss/ap/db/OutboundTransactionManagerJdbc.java b/phoss-ap-db/src/main/java/com/helger/phoss/ap/db/OutboundTransactionManagerJdbc.java index 4f239be7..b12b792e 100644 --- a/phoss-ap-db/src/main/java/com/helger/phoss/ap/db/OutboundTransactionManagerJdbc.java +++ b/phoss-ap-db/src/main/java/com/helger/phoss/ap/db/OutboundTransactionManagerJdbc.java @@ -58,7 +58,8 @@ public class OutboundTransactionManagerJdbc extends AbstractAPJdbcManager implem " c1_country_code, status, attempt_count, created_dt, completed_dt," + " reporting_status, next_retry_dt, error_details, mls_to, mls_status," + " mls_received_dt, mls_id, mls_inbound_transaction_id," + - " sbdh_standard, sbdh_type_version, sbdh_type, payload_mime_type"; + " sbdh_standard, sbdh_type_version, sbdh_type, payload_mime_type," + + " custom1, custom2, custom3"; private final String m_sTableName; @@ -96,7 +97,10 @@ public String create (@NonNull final ETransactionType eTransactionType, @Nullable final String sSbdhStandard, @Nullable final String sSbdhTypeVersion, @Nullable final String sSbdhType, - @Nullable final String sPayloadMimeType) + @Nullable final String sPayloadMimeType, + @Nullable final String sCustom1, + @Nullable final String sCustom2, + @Nullable final String sCustom3) { final String sID = createUniqueRowID (); @@ -106,7 +110,7 @@ public String create (@NonNull final ETransactionType eTransactionType, " (" + COLS + ")" + - " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", new ConstantPreparedStatementDataProvider (sID, eTransactionType.getID (), sSenderID, @@ -135,7 +139,10 @@ public String create (@NonNull final ETransactionType eTransactionType, sSbdhStandard, sSbdhTypeVersion, sSbdhType, - sPayloadMimeType)); + sPayloadMimeType, + sCustom1, + sCustom2, + sCustom3)); if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Stored new outbound transaction in DB. " + nRowsAffected + " rows affected."); diff --git a/phoss-ap-db/src/main/java/com/helger/phoss/ap/db/dto/OutboundTransactionRow.java b/phoss-ap-db/src/main/java/com/helger/phoss/ap/db/dto/OutboundTransactionRow.java index 7e051019..39be08ef 100644 --- a/phoss-ap-db/src/main/java/com/helger/phoss/ap/db/dto/OutboundTransactionRow.java +++ b/phoss-ap-db/src/main/java/com/helger/phoss/ap/db/dto/OutboundTransactionRow.java @@ -67,6 +67,9 @@ public class OutboundTransactionRow implements IOutboundTransaction private final String m_sSbdhTypeVersion; private final String m_sSbdhType; private final String m_sPayloadMimeType; + private final String m_sCustom1; + private final String m_sCustom2; + private final String m_sCustom3; /** * Construct an outbound transaction row from a JDBC result row. @@ -104,6 +107,9 @@ public OutboundTransactionRow (@NonNull final DBResultRow aRow) m_sSbdhTypeVersion = aRow.getAsString (25); m_sSbdhType = aRow.getAsString (26); m_sPayloadMimeType = aRow.getAsString (27); + m_sCustom1 = aRow.getAsString (28); + m_sCustom2 = aRow.getAsString (29); + m_sCustom3 = aRow.getAsString (30); ValueEnforcer.notEmpty (m_sID, "ID"); ValueEnforcer.notNull (m_eTransactionType, "TransactionType"); ValueEnforcer.notEmpty (m_sSenderID, "SenderID"); @@ -326,4 +332,25 @@ public String getPayloadMimeType () { return m_sPayloadMimeType; } + + /** {@inheritDoc} */ + @Nullable + public String getCustom1 () + { + return m_sCustom1; + } + + /** {@inheritDoc} */ + @Nullable + public String getCustom2 () + { + return m_sCustom2; + } + + /** {@inheritDoc} */ + @Nullable + public String getCustom3 () + { + return m_sCustom3; + } } diff --git a/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-db2/V4__OutboundCustomFields.sql b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-db2/V4__OutboundCustomFields.sql new file mode 100644 index 00000000..2c17e370 --- /dev/null +++ b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-db2/V4__OutboundCustomFields.sql @@ -0,0 +1,28 @@ +-- +-- Copyright (C) 2026 Philip Helger (www.helger.com) +-- philip[at]helger[dot]com +-- +-- 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. +-- + +-- Custom outbound transaction fields (issue #64) +-- Three optional custom identifier fields, max 255 characters each. +-- Existing rows default to NULL. Mirrored into the archive table. + +ALTER TABLE outbound_transaction ADD COLUMN custom1 VARCHAR(255); +ALTER TABLE outbound_transaction ADD COLUMN custom2 VARCHAR(255); +ALTER TABLE outbound_transaction ADD COLUMN custom3 VARCHAR(255); + +ALTER TABLE outbound_transaction_archive ADD COLUMN custom1 VARCHAR(255); +ALTER TABLE outbound_transaction_archive ADD COLUMN custom2 VARCHAR(255); +ALTER TABLE outbound_transaction_archive ADD COLUMN custom3 VARCHAR(255); diff --git a/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-h2/V4__OutboundCustomFields.sql b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-h2/V4__OutboundCustomFields.sql new file mode 100644 index 00000000..2c17e370 --- /dev/null +++ b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-h2/V4__OutboundCustomFields.sql @@ -0,0 +1,28 @@ +-- +-- Copyright (C) 2026 Philip Helger (www.helger.com) +-- philip[at]helger[dot]com +-- +-- 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. +-- + +-- Custom outbound transaction fields (issue #64) +-- Three optional custom identifier fields, max 255 characters each. +-- Existing rows default to NULL. Mirrored into the archive table. + +ALTER TABLE outbound_transaction ADD COLUMN custom1 VARCHAR(255); +ALTER TABLE outbound_transaction ADD COLUMN custom2 VARCHAR(255); +ALTER TABLE outbound_transaction ADD COLUMN custom3 VARCHAR(255); + +ALTER TABLE outbound_transaction_archive ADD COLUMN custom1 VARCHAR(255); +ALTER TABLE outbound_transaction_archive ADD COLUMN custom2 VARCHAR(255); +ALTER TABLE outbound_transaction_archive ADD COLUMN custom3 VARCHAR(255); diff --git a/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-mysql/V4__OutboundCustomFields.sql b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-mysql/V4__OutboundCustomFields.sql new file mode 100644 index 00000000..b24a8194 --- /dev/null +++ b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-mysql/V4__OutboundCustomFields.sql @@ -0,0 +1,28 @@ +-- +-- Copyright (C) 2026 Philip Helger (www.helger.com) +-- philip[at]helger[dot]com +-- +-- 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. +-- + +-- Custom outbound transaction fields (issue #64) +-- Three optional custom identifier fields, max 255 characters each. +-- Existing rows default to NULL. Mirrored into the archive table. + +ALTER TABLE `outbound_transaction` ADD COLUMN `custom1` VARCHAR(255); +ALTER TABLE `outbound_transaction` ADD COLUMN `custom2` VARCHAR(255); +ALTER TABLE `outbound_transaction` ADD COLUMN `custom3` VARCHAR(255); + +ALTER TABLE `outbound_transaction_archive` ADD COLUMN `custom1` VARCHAR(255); +ALTER TABLE `outbound_transaction_archive` ADD COLUMN `custom2` VARCHAR(255); +ALTER TABLE `outbound_transaction_archive` ADD COLUMN `custom3` VARCHAR(255); diff --git a/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-postgresql/V4__OutboundCustomFields.sql b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-postgresql/V4__OutboundCustomFields.sql new file mode 100644 index 00000000..2c17e370 --- /dev/null +++ b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-postgresql/V4__OutboundCustomFields.sql @@ -0,0 +1,28 @@ +-- +-- Copyright (C) 2026 Philip Helger (www.helger.com) +-- philip[at]helger[dot]com +-- +-- 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. +-- + +-- Custom outbound transaction fields (issue #64) +-- Three optional custom identifier fields, max 255 characters each. +-- Existing rows default to NULL. Mirrored into the archive table. + +ALTER TABLE outbound_transaction ADD COLUMN custom1 VARCHAR(255); +ALTER TABLE outbound_transaction ADD COLUMN custom2 VARCHAR(255); +ALTER TABLE outbound_transaction ADD COLUMN custom3 VARCHAR(255); + +ALTER TABLE outbound_transaction_archive ADD COLUMN custom1 VARCHAR(255); +ALTER TABLE outbound_transaction_archive ADD COLUMN custom2 VARCHAR(255); +ALTER TABLE outbound_transaction_archive ADD COLUMN custom3 VARCHAR(255); diff --git a/phoss-ap-db/src/test/java/com/helger/phoss/ap/db/JdbcManagerIntegrationTest.java b/phoss-ap-db/src/test/java/com/helger/phoss/ap/db/JdbcManagerIntegrationTest.java index 0c4f0d24..5baf080a 100644 --- a/phoss-ap-db/src/test/java/com/helger/phoss/ap/db/JdbcManagerIntegrationTest.java +++ b/phoss-ap-db/src/test/java/com/helger/phoss/ap/db/JdbcManagerIntegrationTest.java @@ -100,6 +100,9 @@ private static String _createOutboundTx () null, null, null, + null, + null, + null, null); } @@ -135,6 +138,9 @@ public void testOutboundCreateAndGetByID () assertNull (aTx.getMlsReceivedDT ()); assertNull (aTx.getMlsID ()); assertNull (aTx.getMlsInboundTransactionID ()); + assertNull (aTx.getCustom1 ()); + assertNull (aTx.getCustom2 ()); + assertNull (aTx.getCustom3 ()); } @Test @@ -165,6 +171,9 @@ public void testOutboundGetBySbdhInstanceID () null, null, null, + null, + null, + null, null); assertNotNull (sID); @@ -201,6 +210,9 @@ public void testOutboundCreateWithMlsFields () null, null, null, + null, + null, + null, null); assertNotNull (sID); @@ -231,6 +243,9 @@ public void testOutboundCreateMlsResponse () null, null, null, + null, + null, + null, null); assertNotNull (sID); @@ -262,7 +277,10 @@ public void testOutboundCreateWithSbdhOverrides () "urn:peppol:doctype:pdf+xml", "0", "factur-x", - "application/pdf"); + "application/pdf", + null, + null, + null); assertNotNull (sID); final IOutboundTransaction aTx = aMgr.getByID (sID); @@ -275,6 +293,40 @@ public void testOutboundCreateWithSbdhOverrides () assertEquals ("FR", aTx.getC1CountryCode ()); } + @Test + public void testOutboundCreateWithCustomFields () + { + final IOutboundTransactionManager aMgr = APJdbcMetaManager.getOutboundTransactionMgr (); + final String sID = aMgr.create (ETransactionType.BUSINESS_DOCUMENT, + "iso6523-actorid-upis::9915:sender", + "iso6523-actorid-upis::9915:receiver", + "busdox-docid-qns::urn:test:invoice", + "cenbii-procid-ubl::urn:test:process", + _uniqueID (), + ESourceType.PAYLOAD_ONLY, + "/tmp/test-custom.sbd", + 768L, + "hashCustom0123456789012345678901234567890123456789012345678901", + "IT", + _now (), + null, + null, + null, + null, + null, + null, + "my-custom-1", + "my-custom-2", + "my-custom-3"); + assertNotNull (sID); + + final IOutboundTransaction aTx = aMgr.getByID (sID); + assertNotNull (aTx); + assertEquals ("my-custom-1", aTx.getCustom1 ()); + assertEquals ("my-custom-2", aTx.getCustom2 ()); + assertEquals ("my-custom-3", aTx.getCustom3 ()); + } + @Test public void testOutboundCreateWithoutSbdhOverrides () { @@ -311,6 +363,9 @@ public void testOutboundCreatePrebuiltSBD () null, null, null, + null, + null, + null, null); assertNotNull (sID); @@ -1073,7 +1128,10 @@ public void testOutboundGetBySbdhInstanceIDIncludingArchive () null, null, null, - null); + null, + "archive-custom-1", + "archive-custom-2", + "archive-custom-3"); assertNotNull (sTxID); // Active table only: both methods find the transaction @@ -1092,6 +1150,10 @@ public void testOutboundGetBySbdhInstanceIDIncludingArchive () assertNotNull (aTxArchived); assertEquals (sTxID, aTxArchived.getID ()); assertEquals (sSbdhID, aTxArchived.getSbdhInstanceID ()); + // Custom fields must be mirrored into the archive table (issue #64) + assertEquals ("archive-custom-1", aTxArchived.getCustom1 ()); + assertEquals ("archive-custom-2", aTxArchived.getCustom2 ()); + assertEquals ("archive-custom-3", aTxArchived.getCustom3 ()); } @Test diff --git a/phoss-ap-db/src/test/java/com/helger/phoss/ap/db/dto/OutboundTransactionRowTest.java b/phoss-ap-db/src/test/java/com/helger/phoss/ap/db/dto/OutboundTransactionRowTest.java index 3a3f8af4..80110592 100644 --- a/phoss-ap-db/src/test/java/com/helger/phoss/ap/db/dto/OutboundTransactionRowTest.java +++ b/phoss-ap-db/src/test/java/com/helger/phoss/ap/db/dto/OutboundTransactionRowTest.java @@ -51,7 +51,7 @@ private static DBResultRow _createValidRow () { final OffsetDateTime aNow = APBasicMetaManager.getTimestampMgr ().getCurrentDateTimeUTC (); - // 24 columns, matching OutboundTransactionRow constructor order + // 31 columns, matching OutboundTransactionRow constructor order // 0 id // 1 transactionType // 2 senderID @@ -76,6 +76,13 @@ private static DBResultRow _createValidRow () // 21 mlsReceivedDT (nullable) // 22 mlsID (nullable) // 23 mlsInboundTransactionID (nullable) + // 24 sbdhStandard (nullable) + // 25 sbdhTypeVersion (nullable) + // 26 sbdhType (nullable) + // 27 payloadMimeType (nullable) + // 28 custom1 (nullable) + // 29 custom2 (nullable) + // 30 custom3 (nullable) return DBResultRowHelper.createRow ("tx-001", ETransactionType.BUSINESS_DOCUMENT.getID (), "iso6523-actorid-upis::sender", @@ -105,6 +112,10 @@ private static DBResultRow _createValidRow () null, null, null, + null, + // 28 custom1, 29 custom2, 30 custom3 + null, + null, null); } @@ -134,6 +145,9 @@ public void testAllMandatoryFieldsMappedCorrectly () assertNull (aTx.getSbdhTypeVersion ()); assertNull (aTx.getSbdhType ()); assertNull (aTx.getPayloadMimeType ()); + assertNull (aTx.getCustom1 ()); + assertNull (aTx.getCustom2 ()); + assertNull (aTx.getCustom3 ()); } @Test @@ -184,7 +198,10 @@ public void testAllFieldsPopulated () "sbdh-std", "sbdh-type-ver", "sbdh-type", - "application/xml"); + "application/xml", + "custom-1", + "custom-2", + "custom-3"); final OutboundTransactionRow aTx = new OutboundTransactionRow (aRow); assertEquals ("tx-full", aTx.getID ()); @@ -205,5 +222,8 @@ public void testAllFieldsPopulated () assertEquals ("sbdh-type-ver", aTx.getSbdhTypeVersion ()); assertEquals ("sbdh-type", aTx.getSbdhType ()); assertEquals ("application/xml", aTx.getPayloadMimeType ()); + assertEquals ("custom-1", aTx.getCustom1 ()); + assertEquals ("custom-2", aTx.getCustom2 ()); + assertEquals ("custom-3", aTx.getCustom3 ()); } } diff --git a/phoss-ap-dirsender/src/main/java/com/helger/phoss/ap/dirsender/DirectoryFileProcessor.java b/phoss-ap-dirsender/src/main/java/com/helger/phoss/ap/dirsender/DirectoryFileProcessor.java index f8452f54..2b198da4 100644 --- a/phoss-ap-dirsender/src/main/java/com/helger/phoss/ap/dirsender/DirectoryFileProcessor.java +++ b/phoss-ap-dirsender/src/main/java/com/helger/phoss/ap/dirsender/DirectoryFileProcessor.java @@ -215,7 +215,8 @@ public static void processFile (@NonNull final File aWatchDir, @NonNull final Fi final IOutboundTransaction aTx; try (final InputStream aIS = FileHelper.getBufferedInputStream (aPendingFile)) { - aTx = OutboundOrchestrator.submitPrebuiltSBD (sLogPrefix, aIS, null); + // Custom fields are not available for directory-based ingestion + aTx = OutboundOrchestrator.submitPrebuiltSBD (sLogPrefix, aIS, null, null, null, null); } catch (final Exception ex) { diff --git a/phoss-ap-webapp/src/main/java/com/helger/phoss/ap/webapp/controller/OutboundController.java b/phoss-ap-webapp/src/main/java/com/helger/phoss/ap/webapp/controller/OutboundController.java index 6dd79672..c0c3c94a 100644 --- a/phoss-ap-webapp/src/main/java/com/helger/phoss/ap/webapp/controller/OutboundController.java +++ b/phoss-ap-webapp/src/main/java/com/helger/phoss/ap/webapp/controller/OutboundController.java @@ -21,6 +21,7 @@ import java.util.List; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; @@ -87,6 +88,38 @@ public class OutboundController { private static final Logger LOGGER = LoggerFactory.getLogger (OutboundController.class); + /** Maximum length of each custom field (issue #64). */ + private static final int MAX_CUSTOM_FIELD_LENGTH = 255; + + /** + * Validate that none of the optional custom fields exceeds the maximum length. + * + * @param sCustom1 + * First custom field. May be null. + * @param sCustom2 + * Second custom field. May be null. + * @param sCustom3 + * Third custom field. May be null. + * @return A 400 Bad Request response describing the first offending field, or null + * if all fields are valid. + */ + @Nullable + private static ResponseEntity _validateCustomFields (@Nullable final String sCustom1, + @Nullable final String sCustom2, + @Nullable final String sCustom3) + { + final String [] aValues = { sCustom1, sCustom2, sCustom3 }; + for (int i = 0; i < aValues.length; ++i) + if (aValues[i] != null && aValues[i].length () > MAX_CUSTOM_FIELD_LENGTH) + return ResponseEntity.badRequest () + .body (JsonValue.create ("The 'custom" + + (i + 1) + + "' field exceeds the maximum length of " + + MAX_CUSTOM_FIELD_LENGTH + + " characters").getAsJsonString ()); + return null; + } + /** * Submit a raw (payload-only) document for outbound sending via the Peppol network. The document * payload is read from the HTTP request body. Peppol identifiers are parsed from the URL path @@ -116,6 +149,12 @@ public class OutboundController * Optional SBDH type. * @param sPayloadMimeType * Optional payload MIME type. + * @param sCustom1 + * Optional custom field 1 (max 255 characters). + * @param sCustom2 + * Optional custom field 2 (max 255 characters). + * @param sCustom3 + * Optional custom field 3 (max 255 characters). * @return The {@link Phase4PeppolSendingReport} as JSON on success, or an error response. * @throws Exception * On unexpected errors. @@ -163,7 +202,13 @@ public ResponseEntity submitRawDocument (@Parameter (description = "Pep @Parameter (description = "SBDH Type override (e.g., factur-x). Auto-derived from the document type when omitted.") @RequestParam (value = "sbdhType", required = false) final String sSbdhType, @Parameter (description = "MIME type for binary payloads (e.g., application/pdf). When set, the payload is wrapped in ; otherwise treated as XML.") @RequestParam (value = "payloadMimeType", - required = false) final String sPayloadMimeType) throws Exception + required = false) final String sPayloadMimeType, + @Parameter (description = "Optional custom field 1 (max 255 characters). Stored with the transaction and returned by the status APIs.") @RequestParam (value = "custom1", + required = false) final String sCustom1, + @Parameter (description = "Optional custom field 2 (max 255 characters). Stored with the transaction and returned by the status APIs.") @RequestParam (value = "custom2", + required = false) final String sCustom2, + @Parameter (description = "Optional custom field 3 (max 255 characters). Stored with the transaction and returned by the status APIs.") @RequestParam (value = "custom3", + required = false) final String sCustom3) throws Exception { if (!APCoreConfig.isSendingEnabled ()) { @@ -171,6 +216,10 @@ public ResponseEntity submitRawDocument (@Parameter (description = "Pep return ResponseEntity.notFound ().build (); } + final ResponseEntity aCustomErr = _validateCustomFields (sCustom1, sCustom2, sCustom3); + if (aCustomErr != null) + return aCustomErr; + final String sEffectiveSbdhInstanceID = StringHelper.isNotEmpty (sSbdhInstanceID) ? sSbdhInstanceID : PeppolSBDHData.createRandomSBDHInstanceIdentifier (); @@ -247,7 +296,10 @@ public ResponseEntity submitRawDocument (@Parameter (description = "Pep sSbdhStandard, sSbdhTypeVersion, sSbdhType, - sPayloadMimeType); + sPayloadMimeType, + sCustom1, + sCustom2, + sCustom3); if (aTx == null) { return ResponseEntity.unprocessableContent () @@ -275,6 +327,12 @@ public ResponseEntity submitRawDocument (@Parameter (description = "Pep * The HTTP servlet request containing the SBD payload. * @param sMlsTo * Optional MLS "To" address. + * @param sCustom1 + * Optional custom field 1 (max 255 characters). + * @param sCustom2 + * Optional custom field 2 (max 255 characters). + * @param sCustom3 + * Optional custom field 3 (max 255 characters). * @return The {@link Phase4PeppolSendingReport} as JSON on success, or an error response. * @throws Exception * On unexpected errors. @@ -294,7 +352,13 @@ public ResponseEntity submitRawDocument (@Parameter (description = "Pep @ApiResponse (responseCode = "422", description = "Sending failed — see the report body for details") }) public ResponseEntity submitPrebuiltSBD (@Parameter (hidden = true) @NonNull final HttpServletRequest aServletRequest, @Parameter (description = "Alternative Peppol Participant ID to receive MLS responses") @RequestParam (value = "mlsTo", - required = false) final String sMlsTo) throws Exception + required = false) final String sMlsTo, + @Parameter (description = "Optional custom field 1 (max 255 characters). Stored with the transaction and returned by the status APIs.") @RequestParam (value = "custom1", + required = false) final String sCustom1, + @Parameter (description = "Optional custom field 2 (max 255 characters). Stored with the transaction and returned by the status APIs.") @RequestParam (value = "custom2", + required = false) final String sCustom2, + @Parameter (description = "Optional custom field 3 (max 255 characters). Stored with the transaction and returned by the status APIs.") @RequestParam (value = "custom3", + required = false) final String sCustom3) throws Exception { if (!APCoreConfig.isSendingEnabled ()) { @@ -302,11 +366,20 @@ public ResponseEntity submitPrebuiltSBD (@Parameter (hidden = true) @No return ResponseEntity.notFound ().build (); } + final ResponseEntity aCustomErr = _validateCustomFields (sCustom1, sCustom2, sCustom3); + if (aCustomErr != null) + return aCustomErr; + // Read the InputStream only once try (final InputStream aIS = aServletRequest.getInputStream ()) { // Store in DB - final IOutboundTransaction aTx = OutboundOrchestrator.submitPrebuiltSBD ("[SubmitPrebuiltSBD] ", aIS, sMlsTo); + final IOutboundTransaction aTx = OutboundOrchestrator.submitPrebuiltSBD ("[SubmitPrebuiltSBD] ", + aIS, + sMlsTo, + sCustom1, + sCustom2, + sCustom3); if (aTx == null) { return ResponseEntity.badRequest () @@ -343,6 +416,12 @@ public ResponseEntity submitPrebuiltSBD (@Parameter (hidden = true) @No * Optional SBDH Instance ID. A random one is generated if not provided. * @param sMlsTo * Optional MLS "To" address. + * @param sCustom1 + * Optional custom field 1 (max 255 characters). + * @param sCustom2 + * Optional custom field 2 (max 255 characters). + * @param sCustom3 + * Optional custom field 3 (max 255 characters). * @return The {@link Phase4PeppolSendingReport} as JSON on success, or an error response. * @throws Exception * On unexpected errors. @@ -377,7 +456,13 @@ public ResponseEntity submitAutoDetect (@Parameter (description = "Pepp @Parameter (description = "Custom SBDH Instance Identifier. A random UUID-based identifier is generated when omitted.") @RequestParam (value = "sbdhInstanceID", required = false) final String sSbdhInstanceID, @Parameter (description = "Alternative Peppol Participant ID to receive MLS responses") @RequestParam (value = "mlsTo", - required = false) final String sMlsTo) throws Exception + required = false) final String sMlsTo, + @Parameter (description = "Optional custom field 1 (max 255 characters). Stored with the transaction and returned by the status APIs.") @RequestParam (value = "custom1", + required = false) final String sCustom1, + @Parameter (description = "Optional custom field 2 (max 255 characters). Stored with the transaction and returned by the status APIs.") @RequestParam (value = "custom2", + required = false) final String sCustom2, + @Parameter (description = "Optional custom field 3 (max 255 characters). Stored with the transaction and returned by the status APIs.") @RequestParam (value = "custom3", + required = false) final String sCustom3) throws Exception { if (!APCoreConfig.isSendingEnabled ()) { @@ -385,6 +470,10 @@ public ResponseEntity submitAutoDetect (@Parameter (description = "Pepp return ResponseEntity.notFound ().build (); } + final ResponseEntity aCustomErr = _validateCustomFields (sCustom1, sCustom2, sCustom3); + if (aCustomErr != null) + return aCustomErr; + final String sLogPrefix = "[SubmitAutoDetect] "; final IIdentifierFactory aIF = APBasicMetaManager.getIdentifierFactory (); @@ -477,7 +566,10 @@ public ResponseEntity submitAutoDetect (@Parameter (description = "Pepp null, null, null, - null); + null, + sCustom1, + sCustom2, + sCustom3); if (aTx == null) return ResponseEntity.badRequest () .body (JsonValue.create ("Failed to submit outbound transaction").getAsJsonString ()); @@ -547,6 +639,12 @@ public ResponseEntity submitFromS3 (@RequestBody final OutboundS3Submit .getAsJsonString ()); } + final ResponseEntity aCustomErr = _validateCustomFields (aRequest.getCustom1 (), + aRequest.getCustom2 (), + aRequest.getCustom3 ()); + if (aCustomErr != null) + return aCustomErr; + final String sEffectiveSbdhInstanceID = StringHelper.isNotEmpty (aRequest.getSbdhInstanceID ()) ? aRequest.getSbdhInstanceID () : PeppolSBDHData.createRandomSBDHInstanceIdentifier (); @@ -658,7 +756,10 @@ public ResponseEntity submitFromS3 (@RequestBody final OutboundS3Submit aRequest.getSbdhStandard (), aRequest.getSbdhTypeVersion (), aRequest.getSbdhType (), - aRequest.getPayloadMimeType ()); + aRequest.getPayloadMimeType (), + aRequest.getCustom1 (), + aRequest.getCustom2 (), + aRequest.getCustom3 ()); if (aTx == null) { return ResponseEntity.unprocessableContent () From 992ddbcdd0994c49903e783e7b8de86b0b3e7e88 Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Wed, 22 Jul 2026 13:48:44 +0200 Subject: [PATCH 2/2] Added reorg --- .../db/phoss-ap-flyway-db2/V4__OutboundCustomFields.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-db2/V4__OutboundCustomFields.sql b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-db2/V4__OutboundCustomFields.sql index 2c17e370..e8adc9c5 100644 --- a/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-db2/V4__OutboundCustomFields.sql +++ b/phoss-ap-db/src/main/resources/db/phoss-ap-flyway-db2/V4__OutboundCustomFields.sql @@ -19,10 +19,15 @@ -- Three optional custom identifier fields, max 255 characters each. -- Existing rows default to NULL. Mirrored into the archive table. +-- DB2 puts a table into REORG-pending state after ALTER TABLE ... ADD COLUMN, +-- so a REORG is required before the table is fully usable (and before exceeding +-- DB2's limit of 3 pending ALTERs per table). ALTER TABLE outbound_transaction ADD COLUMN custom1 VARCHAR(255); ALTER TABLE outbound_transaction ADD COLUMN custom2 VARCHAR(255); ALTER TABLE outbound_transaction ADD COLUMN custom3 VARCHAR(255); +CALL SYSPROC.ADMIN_CMD('REORG TABLE outbound_transaction'); ALTER TABLE outbound_transaction_archive ADD COLUMN custom1 VARCHAR(255); ALTER TABLE outbound_transaction_archive ADD COLUMN custom2 VARCHAR(255); ALTER TABLE outbound_transaction_archive ADD COLUMN custom3 VARCHAR(255); +CALL SYSPROC.ADMIN_CMD('REORG TABLE outbound_transaction_archive');