From 6dce89d5f55b10811de50590979ef41b9dfc2a4b Mon Sep 17 00:00:00 2001 From: Niels Thykier Date: Wed, 3 Jul 2024 11:37:23 +0200 Subject: [PATCH] DT-969: Add simple TD scenario group --- .../standards/ebl/EblScenarioListBuilder.java | 125 +++++++++++++++++- .../standards/ebl/EblStandard.java | 5 +- .../standards/ebl/action/EblAction.java | 4 +- .../Shipper_GetTransportDocumentAction.java | 70 ++++++++++ ...essSurrenderRequestForAmendmentAction.java | 11 +- ...r_IssueAmendedTransportDocumentAction.java | 10 ++ ...v_Carrier_VoidTransportDocumentAction.java | 10 ++ ...waitSurrenderRequestForDeliveryAction.java | 10 ++ ...cessSurrenderRequestForDeliveryAction.java | 10 ++ ...r_PublishDraftTransportDocumentAction.java | 39 +++++- ...r_ApproveDraftTransportDocumentAction.java | 10 ++ ..._Carrier_IssueTransportDocumentAction.java | 11 +- ...aitSurrenderRequestForAmendmentAction.java | 10 ++ ...ntRequestDraftTransportDocumentAction.java | 31 +++++ .../models/CarrierShippingInstructions.java | 12 +- .../ebl/party/DynamicScenarioParameters.java | 15 ++- .../standards/ebl/party/EblCarrier.java | 46 ++++++- .../standards/ebl/party/EblShipper.java | 98 ++++++++------ 18 files changed, 453 insertions(+), 74 deletions(-) create mode 100644 ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UCX_Carrier_TDOnlyProcessOutOfBandUpdateOrAmendmentRequestDraftTransportDocumentAction.java diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/EblScenarioListBuilder.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/EblScenarioListBuilder.java index e3d5d9bc..bf4d5c08 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/EblScenarioListBuilder.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/EblScenarioListBuilder.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; @@ -23,8 +24,15 @@ @Slf4j class EblScenarioListBuilder extends ScenarioListBuilder { static final String SCENARIO_SUITE_CONFORMANCE_SI_ONLY = "Conformance SI-only"; + static final String SCENARIO_SUITE_CONFORMANCE_TD_ONLY = "Conformance TD-only"; static final String SCENARIO_SUITE_RI = "Reference Implementation"; + static final Set SCENARIOS = Set.of( + SCENARIO_SUITE_CONFORMANCE_SI_ONLY, + SCENARIO_SUITE_CONFORMANCE_TD_ONLY, + SCENARIO_SUITE_RI + ); + private static final ThreadLocal STANDARD_VERSION = new ThreadLocal<>(); private static final ThreadLocal threadLocalCarrierPartyName = new ThreadLocal<>(); private static final ThreadLocal threadLocalShipperPartyName = new ThreadLocal<>(); @@ -54,6 +62,9 @@ public static LinkedHashMap createModuleScenario if (SCENARIO_SUITE_CONFORMANCE_SI_ONLY.equals(componentFactory.getScenarioSuite())) { return createConformanceSiOnlyScenarios(); } + if (SCENARIO_SUITE_CONFORMANCE_TD_ONLY.equals(componentFactory.getScenarioSuite())) { + return createConformanceTdOnlyScenarios(); + } if (SCENARIO_SUITE_RI.equals(componentFactory.getScenarioSuite())) { return createReferenceImplementationScenarios(); } @@ -91,6 +102,32 @@ private static LinkedHashMap createConformanceSi Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); } + + private static LinkedHashMap createConformanceTdOnlyScenarios() { + return Stream.of( + Map.entry( + "Supported shipment types scenarios", + noAction() + .thenEither( + Arrays.stream(ScenarioType.values()) + .map( + scenarioType -> + carrier_SupplyScenarioParameters(scenarioType) + .then(_uc6_get(true, _uc8_get(_uc12_get(_uc13_get()))))) + .toList() + .toArray(new EblScenarioListBuilder[] {}))), + Map.entry( + "Shipper interactions with transport document", + carrier_SupplyScenarioParameters(ScenarioType.REGULAR_BOL) + .then(_uc6_get(true, _uc7_get(_uc8_get(_uc12_get(_uc13_get()))), + _uc8_get(_uc12_get(_uc13_get())), + _oob_amendment(_uc6_get(false, _uc8_get(_uc12_get(_uc13_get())))), + _uc8_get(_oob_amendment(_uc9_get(_uc10_get(_uc11_get(_uc12_get(_uc13_get())))))))))) + .collect( + Collectors.toMap( + Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + @SuppressWarnings("unused") private static void _ignoreSingleValueArgs() { _uc1_get(SI_ANY); @@ -181,11 +218,75 @@ private static EblScenarioListBuilder _uc5_get( .thenEither(thenEither)); } + private static EblScenarioListBuilder _uc6_get(boolean start, EblScenarioListBuilder... thenEither) { + return uc6_carrier_publishDraftTransportDocument(start).then( + shipper_GetTransportDocument(TD_DRAFT) + .thenEither(thenEither) + ); + } + + private static EblScenarioListBuilder _uc7_get(EblScenarioListBuilder... thenEither) { + return uc7_shipper_approveDraftTransportDocument().then( + shipper_GetTransportDocument(TD_APPROVED) + .thenEither(thenEither) + ); + } + + private static EblScenarioListBuilder _uc8_get(EblScenarioListBuilder... thenEither) { + return uc8_carrier_issueTransportDocument().then( + shipper_GetTransportDocument(TD_ISSUED) + .thenEither(thenEither) + ); + } + + private static EblScenarioListBuilder _uc9_get(EblScenarioListBuilder... thenEither) { + return uc9_carrier_awaitSurrenderRequestForAmendment().then( + shipper_GetTransportDocument(TD_PENDING_SURRENDER_FOR_AMENDMENT) + .thenEither(thenEither) + ); + } + + private static EblScenarioListBuilder _uc10_get(EblScenarioListBuilder... thenEither) { + return uc10a_carrier_acceptSurrenderRequestForAmendment().then( + shipper_GetTransportDocument(TD_SURRENDERED_FOR_AMENDMENT) + .thenEither(thenEither) + ); + } + + private static EblScenarioListBuilder _uc11_get(EblScenarioListBuilder... thenEither) { + return uc11_carrier_voidTransportDocument().then( + uc11i_carrier_issueAmendedTransportDocument().then( + shipper_GetTransportDocument(TD_ISSUED) + .thenEither(thenEither) + + )); + } + + private static EblScenarioListBuilder _uc12_get(EblScenarioListBuilder... thenEither) { + return uc12_carrier_awaitSurrenderRequestForDelivery().then( + shipper_GetTransportDocument(TD_PENDING_SURRENDER_FOR_DELIVERY) + .thenEither(thenEither) + ); + } + + private static EblScenarioListBuilder _uc13_get(EblScenarioListBuilder... thenEither) { + return uc13a_carrier_acceptSurrenderRequestForDelivery().then( + shipper_GetTransportDocument(TD_SURRENDERED_FOR_DELIVERY) + .thenEither(thenEither) + ); + } + + private static EblScenarioListBuilder _uc14_get(ShippingInstructionsStatus siState) { return uc14_carrier_confirmShippingInstructionsComplete() .then(shipper_GetShippingInstructions(siState, false)); } + private static EblScenarioListBuilder _oob_amendment(EblScenarioListBuilder... thenEither) { + return oob_carrier_processOutOfBoundTDUpdateRequest() + .thenEither(thenEither); + } + private static LinkedHashMap createReferenceImplementationScenarios() { return Stream.of( Map.entry( @@ -248,10 +349,10 @@ yield then( .then(shipper_GetShippingInstructions(SI_RECEIVED, SI_UPDATE_RECEIVED, true, useTDRef) .thenAllPathsFrom(SI_UPDATE_RECEIVED, SI_RECEIVED, transportDocumentStatus, useTDRef))), switch (transportDocumentStatus) { - case TD_START -> uc6_carrier_publishDraftTransportDocument().then( + case TD_START -> uc6_carrier_publishDraftTransportDocument(false).then( shipper_GetShippingInstructionsRecordTDRef() .then(shipper_GetTransportDocument(TD_DRAFT).thenAllPathsFrom(TD_DRAFT))); - case TD_DRAFT -> uc6_carrier_publishDraftTransportDocument().then( + case TD_DRAFT -> uc6_carrier_publishDraftTransportDocument(false).then( shipper_GetShippingInstructionsRecordTDRef() .then(shipper_GetTransportDocument(TD_DRAFT).thenHappyPathFrom(TD_DRAFT))); case TD_ISSUED -> uc9_carrier_awaitSurrenderRequestForAmendment().then( @@ -427,7 +528,7 @@ private EblScenarioListBuilder thenHappyPathFrom( .thenHappyPathFrom(SI_RECEIVED, transportDocumentStatus, useTDRef))); case SI_UPDATE_CONFIRMED, SI_RECEIVED -> then( switch (transportDocumentStatus) { - case TD_START, TD_DRAFT -> uc6_carrier_publishDraftTransportDocument().then( + case TD_START, TD_DRAFT -> uc6_carrier_publishDraftTransportDocument(false).then( shipper_GetShippingInstructionsRecordTDRef() .then(shipper_GetTransportDocument(TD_DRAFT).thenHappyPathFrom(TD_DRAFT))); case TD_ISSUED -> uc9_carrier_awaitSurrenderRequestForAmendment().then( @@ -794,7 +895,7 @@ private static EblScenarioListBuilder uc5_shipper_cancelUpdateToShippingInstruct EBL_NOTIFICATIONS_API, EBL_SI_NOTIFICATION_SCHEMA_NAME))); } - private static EblScenarioListBuilder uc6_carrier_publishDraftTransportDocument() { + private static EblScenarioListBuilder uc6_carrier_publishDraftTransportDocument(boolean skipSI) { String carrierPartyName = threadLocalCarrierPartyName.get(); String shipperPartyName = threadLocalShipperPartyName.get(); return new EblScenarioListBuilder( @@ -804,9 +905,12 @@ private static EblScenarioListBuilder uc6_carrier_publishDraftTransportDocument( shipperPartyName, (EblAction) previousAction, resolveMessageSchemaValidator( - EBL_NOTIFICATIONS_API, EBL_TD_NOTIFICATION_SCHEMA_NAME))); + EBL_NOTIFICATIONS_API, EBL_TD_NOTIFICATION_SCHEMA_NAME), + skipSI)); } + + private static EblScenarioListBuilder uc7_shipper_approveDraftTransportDocument() { String carrierPartyName = threadLocalCarrierPartyName.get(); String shipperPartyName = threadLocalShipperPartyName.get(); @@ -961,6 +1065,17 @@ private static EblScenarioListBuilder uc14_carrier_confirmShippingInstructionsCo EBL_NOTIFICATIONS_API, EBL_SI_NOTIFICATION_SCHEMA_NAME))); } + private static EblScenarioListBuilder oob_carrier_processOutOfBoundTDUpdateRequest() { + String carrierPartyName = threadLocalCarrierPartyName.get(); + String shipperPartyName = threadLocalShipperPartyName.get(); + return new EblScenarioListBuilder( + previousAction -> + new UCX_Carrier_TDOnlyProcessOutOfBandUpdateOrAmendmentRequestDraftTransportDocumentAction( + carrierPartyName, + shipperPartyName, + (EblAction) previousAction)); + } + private static EblScenarioListBuilder auc_shipper_sendOutOfOrderSIMessage(OutOfOrderMessageType outOfOrderMessageType, boolean useTDRef) { String carrierPartyName = threadLocalCarrierPartyName.get(); String shipperPartyName = threadLocalShipperPartyName.get(); diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/EblStandard.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/EblStandard.java index e18d7fa7..a358b763 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/EblStandard.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/EblStandard.java @@ -17,10 +17,7 @@ public SortedMap> getScenarioSuitesByStandardVersion() Map.ofEntries( Map.entry( "3.0.0", - new TreeSet<>( - Set.of( - EblScenarioListBuilder.SCENARIO_SUITE_CONFORMANCE_SI_ONLY, - EblScenarioListBuilder.SCENARIO_SUITE_RI))))); + new TreeSet<>(EblScenarioListBuilder.SCENARIOS)))); } @Override diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/EblAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/EblAction.java index 71e186d9..cad0feb8 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/EblAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/EblAction.java @@ -16,6 +16,8 @@ import org.dcsa.conformance.standards.ebl.checks.ScenarioType; import org.dcsa.conformance.standards.ebl.party.*; +import static org.dcsa.conformance.core.toolkit.JsonToolkit.OBJECT_MAPPER; + public abstract class EblAction extends ConformanceAction { protected final int expectedStatus; private final OverwritingReference dspReference; @@ -31,7 +33,7 @@ public EblAction( this.dspReference = previousAction == null ? new OverwritingReference<>( - null, new DynamicScenarioParameters(ScenarioType.REGULAR_SWB, null, null, null, null, null, null, null)) + null, new DynamicScenarioParameters(ScenarioType.REGULAR_SWB, null, null, null, null, null, null, null, false, OBJECT_MAPPER.createObjectNode(), OBJECT_MAPPER.createObjectNode())) : new OverwritingReference<>(previousAction.dspReference, null); } diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/Shipper_GetTransportDocumentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/Shipper_GetTransportDocumentAction.java index 0392b734..cc9e3257 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/Shipper_GetTransportDocumentAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/Shipper_GetTransportDocumentAction.java @@ -1,10 +1,16 @@ package org.dcsa.conformance.standards.ebl.action; import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.Set; +import java.util.UUID; import java.util.stream.Stream; + import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.core.traffic.HttpMessageType; import org.dcsa.conformance.standards.ebl.checks.EBLChecks; +import org.dcsa.conformance.standards.ebl.crypto.Checksums; import org.dcsa.conformance.standards.ebl.party.*; public class Shipper_GetTransportDocumentAction extends EblAction { @@ -36,6 +42,16 @@ public String getHumanReadablePrompt() { .formatted(getDspSupplier().get().transportDocumentReference()); } + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + var dsp = getDspSupplier().get(); + var response = exchange.getResponse().message().body().getJsonBody(); + var previousTD = dsp.transportDocument(); + dsp = dsp.withPreviousTransportDocument(previousTD) + .withTransportDocument(response); + getDspConsumer().accept(dsp); + } + @Override public ConformanceCheck createCheck(String expectedApiVersion) { var dsp = getDspSupplier().get(); @@ -65,8 +81,62 @@ protected Stream createSubChecks() { getMatchedExchangeUuid(), HttpMessageType.RESPONSE, responseSchemaValidator), + checkTDChanged(getMatchedExchangeUuid(), expectedApiVersion, dsp), EBLChecks.tdPlusScenarioContentChecks(getMatchedExchangeUuid(), expectedApiVersion, expectedTdStatus, getCspSupplier(), getDspSupplier())); } }; } + + private static ActionCheck checkTDChanged(UUID matched, String standardsVersion, DynamicScenarioParameters dsp) { + var deltaCheck = JsonAttribute.lostAttributeCheck( + "(ignored)", + dsp::previousTransportDocument, + (baselineTD, currentTD) -> { + if (baselineTD instanceof ObjectNode td) { + td.remove("transportDocumentStatus"); + } + }); + JsonContentMatchedValidation hadChangesCheck = (nodeToValidate, contextPath) -> { + var currentStatus = nodeToValidate.path("transportDocumentStatus").asText(""); + var comparisonTD = dsp.previousTransportDocument(); + var comparisonStatus = comparisonTD.path("transportDocumentStatus").asText(""); + if (dsp.newTransportDocumentContent()) { + return Set.of(); + } + if (!(nodeToValidate instanceof ObjectNode currentTDObj) || !(comparisonTD instanceof ObjectNode comparisonTDObj)) { + // Schema validation takes care of this. + return Set.of(); + } + + var currentTDObjCopy = currentTDObj.deepCopy(); + var comparisonTDObjCopy = comparisonTDObj.deepCopy(); + currentTDObjCopy.remove("transportDocumentStatus"); + comparisonTDObjCopy.remove("transportDocumentStatus"); + var checksum = Checksums.sha256CanonicalJson(currentTDObjCopy); + var previousChecksum = Checksums.sha256CanonicalJson(comparisonTDObjCopy); + if (checksum.equals(previousChecksum)) { + return Set.of("Expected a change, but it is the same TD. " + currentStatus + " - " + comparisonStatus); + } + return Set.of(); + }; + return JsonAttribute.contentChecks( + "", + "[Scenario] Validate TD changes match the expected", + EblRole::isCarrier, + matched, + HttpMessageType.RESPONSE, + standardsVersion, + JsonAttribute.customValidator("The TD match the scenario step", + JsonAttribute.ifMatchedThenElse( + // For some cases, we assume the TD will change in ways we cannot predict, so here + // we just effectively skip the check + // + // Common cases are new drafts and amendments. + (ignored) -> dsp.newTransportDocumentContent(), + hadChangesCheck, + deltaCheck::validate + ) + ) + ); + } } diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC10_Carrier_ProcessSurrenderRequestForAmendmentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC10_Carrier_ProcessSurrenderRequestForAmendmentAction.java index db06b837..d32dddb9 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC10_Carrier_ProcessSurrenderRequestForAmendmentAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC10_Carrier_ProcessSurrenderRequestForAmendmentAction.java @@ -4,7 +4,7 @@ import java.util.stream.Stream; import lombok.Getter; import org.dcsa.conformance.core.check.*; -import org.dcsa.conformance.standards.ebl.checks.EBLChecks; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus; @Getter @@ -40,6 +40,15 @@ public ObjectNode asJsonNode() { .put("acceptAmendmentRequest", acceptAmendmentRequest); } + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + var dsp = getDspSupplier().get(); + // Clear the flag if set. + if (dsp.newTransportDocumentContent()) { + getDspConsumer().accept(dsp.withNewTransportDocumentContent(false)); + } + } + @Override public ConformanceCheck createCheck(String expectedApiVersion) { return new ConformanceCheck(getActionTitle()) { diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC11i_Carrier_IssueAmendedTransportDocumentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC11i_Carrier_IssueAmendedTransportDocumentAction.java index 4502e54d..817b0112 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC11i_Carrier_IssueAmendedTransportDocumentAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC11i_Carrier_IssueAmendedTransportDocumentAction.java @@ -4,6 +4,7 @@ import java.util.stream.Stream; import lombok.Getter; import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus; @Getter @@ -25,6 +26,15 @@ public String getHumanReadablePrompt() { .formatted(getDspSupplier().get().transportDocumentReference())); } + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + var dsp = getDspSupplier().get(); + // This is a re-issuance; those will de facto change the TD. + if (!dsp.newTransportDocumentContent()) { + getDspConsumer().accept(dsp.withNewTransportDocumentContent(true)); + } + } + @Override public ObjectNode asJsonNode() { var dsp = getDspSupplier().get(); diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC11v_Carrier_VoidTransportDocumentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC11v_Carrier_VoidTransportDocumentAction.java index e5429e9e..80b367e5 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC11v_Carrier_VoidTransportDocumentAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC11v_Carrier_VoidTransportDocumentAction.java @@ -4,6 +4,7 @@ import java.util.stream.Stream; import lombok.Getter; import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus; @Getter @@ -31,6 +32,15 @@ public ObjectNode asJsonNode() { .put("documentReference", getDspSupplier().get().transportDocumentReference()); } + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + var dsp = getDspSupplier().get(); + // Clear the flag if set. + if (dsp.newTransportDocumentContent()) { + getDspConsumer().accept(dsp.withNewTransportDocumentContent(false)); + } + } + @Override public ConformanceCheck createCheck(String expectedApiVersion) { return new ConformanceCheck(getActionTitle()) { diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC12_Carrier_AwaitSurrenderRequestForDeliveryAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC12_Carrier_AwaitSurrenderRequestForDeliveryAction.java index 2b8fcc01..7c84750d 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC12_Carrier_AwaitSurrenderRequestForDeliveryAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC12_Carrier_AwaitSurrenderRequestForDeliveryAction.java @@ -4,6 +4,7 @@ import java.util.stream.Stream; import lombok.Getter; import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus; @Getter @@ -31,6 +32,15 @@ public ObjectNode asJsonNode() { .put("documentReference", getDspSupplier().get().transportDocumentReference()); } + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + var dsp = getDspSupplier().get(); + // Clear the flag if set. + if (dsp.newTransportDocumentContent()) { + getDspConsumer().accept(dsp.withNewTransportDocumentContent(false)); + } + } + @Override public ConformanceCheck createCheck(String expectedApiVersion) { return new ConformanceCheck(getActionTitle()) { diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC13_Carrier_ProcessSurrenderRequestForDeliveryAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC13_Carrier_ProcessSurrenderRequestForDeliveryAction.java index 3eb8adb0..df811ea1 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC13_Carrier_ProcessSurrenderRequestForDeliveryAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC13_Carrier_ProcessSurrenderRequestForDeliveryAction.java @@ -4,6 +4,7 @@ import java.util.stream.Stream; import lombok.Getter; import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus; @Getter @@ -39,6 +40,15 @@ public ObjectNode asJsonNode() { .put("acceptDeliveryRequest", acceptDeliveryRequest); } + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + var dsp = getDspSupplier().get(); + // Clear the flag if set. + if (dsp.newTransportDocumentContent()) { + getDspConsumer().accept(dsp.withNewTransportDocumentContent(false)); + } + } + @Override public ConformanceCheck createCheck(String expectedApiVersion) { return new ConformanceCheck(getActionTitle()) { diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC6_Carrier_PublishDraftTransportDocumentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC6_Carrier_PublishDraftTransportDocumentAction.java index 271e251d..915ed920 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC6_Carrier_PublishDraftTransportDocumentAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC6_Carrier_PublishDraftTransportDocumentAction.java @@ -4,33 +4,58 @@ import java.util.stream.Stream; import lombok.Getter; import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus; @Getter public class UC6_Carrier_PublishDraftTransportDocumentAction extends StateChangingSIAction { - private final JsonSchemaValidator requestSchemaValidator; + private final JsonSchemaValidator notificationSchemaValidator; + private final boolean skipSI; public UC6_Carrier_PublishDraftTransportDocumentAction( String carrierPartyName, String shipperPartyName, EblAction previousAction, - JsonSchemaValidator requestSchemaValidator) { + JsonSchemaValidator notificationSchemaValidator, + boolean skipSI) { super(carrierPartyName, shipperPartyName, previousAction, "UC6", 204); - this.requestSchemaValidator = requestSchemaValidator; + this.notificationSchemaValidator = notificationSchemaValidator; + this.skipSI = skipSI; } @Override public String getHumanReadablePrompt() { + if (skipSI) { + return "UC6: Publish draft transport document matching the scenario parameters provided in the previous step"; + } return ("UC6: Publish draft transport document for shipping instructions with reference %s" .formatted(getDspSupplier().get().shippingInstructionsReference())); } + + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + + var dsp = getDspSupplier().get(); + if (skipSI) { + var tdr = exchange.getRequest().message().body().getJsonBody().path("data").path("transportDocumentReference"); + if (!tdr.isMissingNode()) { + dsp = dsp.withTransportDocumentReference(tdr.asText()); + } + } + getDspConsumer().accept(dsp.withNewTransportDocumentContent(true)); + } + @Override public ObjectNode asJsonNode() { var dsp = getDspSupplier().get(); - return super.asJsonNode() - .put("documentReference", dsp.shippingInstructionsReference()) - .put("scenarioType", dsp.scenarioType().name()); + var dr = dsp.transportDocumentReference() != null ? dsp.transportDocumentReference() : dsp.shippingInstructionsReference(); + var node = super.asJsonNode() + .put("documentReference", dr) + .put("scenarioType", dsp.scenarioType().name()) + .put("skipSI", skipSI); + node.set("csp", getCspSupplier().get().toJson()); + return node; } @Override @@ -41,7 +66,7 @@ protected Stream createSubChecks() { return getTDNotificationChecks( getMatchedExchangeUuid(), expectedApiVersion, - requestSchemaValidator, + notificationSchemaValidator, TransportDocumentStatus.TD_DRAFT, false); } diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC7_Shipper_ApproveDraftTransportDocumentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC7_Shipper_ApproveDraftTransportDocumentAction.java index 499d90ed..1c18bd21 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC7_Shipper_ApproveDraftTransportDocumentAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC7_Shipper_ApproveDraftTransportDocumentAction.java @@ -5,6 +5,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.core.traffic.HttpMessageType; import org.dcsa.conformance.standards.ebl.checks.EBLChecks; import org.dcsa.conformance.standards.ebl.party.EblRole; @@ -42,6 +43,15 @@ public ObjectNode asJsonNode() { .put("documentReference", getDspSupplier().get().transportDocumentReference()); } + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + var dsp = getDspSupplier().get(); + // Clear the flag if set. + if (dsp.newTransportDocumentContent()) { + getDspConsumer().accept(dsp.withNewTransportDocumentContent(false)); + } + } + @Override protected boolean expectsNotificationExchange() { return true; diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC8_Carrier_IssueTransportDocumentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC8_Carrier_IssueTransportDocumentAction.java index c1119352..fb27bc86 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC8_Carrier_IssueTransportDocumentAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC8_Carrier_IssueTransportDocumentAction.java @@ -4,6 +4,7 @@ import java.util.stream.Stream; import lombok.Getter; import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus; @Getter @@ -28,7 +29,15 @@ public String getHumanReadablePrompt() { @Override public ObjectNode asJsonNode() { return super.asJsonNode() - .put("documentReference", getDspSupplier().get().shippingInstructionsReference()); + .put("documentReference", getDspSupplier().get().transportDocumentReference()); + } + + + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + var dsp = getDspSupplier().get(); + // Issuance can bump the issuance date. + getDspConsumer().accept(dsp.withNewTransportDocumentContent(true)); } @Override diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC9_Carrier_AwaitSurrenderRequestForAmendmentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC9_Carrier_AwaitSurrenderRequestForAmendmentAction.java index 978b1e79..552ea110 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC9_Carrier_AwaitSurrenderRequestForAmendmentAction.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC9_Carrier_AwaitSurrenderRequestForAmendmentAction.java @@ -4,6 +4,7 @@ import java.util.stream.Stream; import lombok.Getter; import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus; @Getter @@ -31,6 +32,15 @@ public ObjectNode asJsonNode() { .put("documentReference", getDspSupplier().get().transportDocumentReference()); } + protected void doHandleExchange(ConformanceExchange exchange) { + super.doHandleExchange(exchange); + var dsp = getDspSupplier().get(); + // Clear the flag if set. + if (dsp.newTransportDocumentContent()) { + getDspConsumer().accept(dsp.withNewTransportDocumentContent(false)); + } + } + @Override public ConformanceCheck createCheck(String expectedApiVersion) { return new ConformanceCheck(getActionTitle()) { diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UCX_Carrier_TDOnlyProcessOutOfBandUpdateOrAmendmentRequestDraftTransportDocumentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UCX_Carrier_TDOnlyProcessOutOfBandUpdateOrAmendmentRequestDraftTransportDocumentAction.java new file mode 100644 index 00000000..bff276ad --- /dev/null +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UCX_Carrier_TDOnlyProcessOutOfBandUpdateOrAmendmentRequestDraftTransportDocumentAction.java @@ -0,0 +1,31 @@ +package org.dcsa.conformance.standards.ebl.action; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; + +@Getter +public class UCX_Carrier_TDOnlyProcessOutOfBandUpdateOrAmendmentRequestDraftTransportDocumentAction extends StateChangingSIAction { + + public UCX_Carrier_TDOnlyProcessOutOfBandUpdateOrAmendmentRequestDraftTransportDocumentAction( + String carrierPartyName, + String shipperPartyName, + EblAction previousAction) { + super(carrierPartyName, shipperPartyName, previousAction, "TD Change (Out of Band)", 204); + } + + @Override + public String getHumanReadablePrompt() { + return ("Process and accept an out of band request for a change to the TD with reference %s" + .formatted(getDspSupplier().get().transportDocumentReference())); + } + + @Override + public ObjectNode asJsonNode() { + var dsp = getDspSupplier().get(); + var node = super.asJsonNode() + .put("documentReference", dsp.transportDocumentReference()) + .put("scenarioType", dsp.scenarioType().name()); + node.set("csp", getCspSupplier().get().toJson()); + return node; + } +} diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/models/CarrierShippingInstructions.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/models/CarrierShippingInstructions.java index 45be4eb4..e49b20ec 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/models/CarrierShippingInstructions.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/models/CarrierShippingInstructions.java @@ -10,6 +10,8 @@ import java.time.LocalDate; import java.util.*; import java.util.function.*; + +import lombok.NonNull; import org.dcsa.conformance.core.state.JsonNodeMap; import org.dcsa.conformance.standards.ebl.checks.ScenarioType; import org.dcsa.conformance.standards.ebl.party.ShippingInstructionsStatus; @@ -685,8 +687,16 @@ public JsonNode asPersistentState() { return this.state; } + private static @NonNull String notNull(String reference) { + var r = Objects.requireNonNull(reference, "Reference was null"); + if (r.equals("null")) { + throw new IllegalArgumentException("Reference was \"null\" (string version of null)!"); + } + return r; + } + public static CarrierShippingInstructions fromPersistentStore(JsonNodeMap jsonNodeMap, String shippingInstructionsReference) { - var data = jsonNodeMap.load(shippingInstructionsReference); + var data = jsonNodeMap.load(notNull(shippingInstructionsReference)); if (data == null) { throw new IllegalArgumentException("Unknown SI Reference: " + shippingInstructionsReference); } diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/DynamicScenarioParameters.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/DynamicScenarioParameters.java index 65b7013b..5408ae13 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/DynamicScenarioParameters.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/DynamicScenarioParameters.java @@ -19,7 +19,10 @@ public record DynamicScenarioParameters( JsonNode updatedShippingInstructions, ShippingInstructionsStatus shippingInstructionsStatus, ShippingInstructionsStatus updatedShippingInstructionsStatus, - TransportDocumentStatus transportDocumentStatus) { + TransportDocumentStatus transportDocumentStatus, + boolean newTransportDocumentContent, + JsonNode transportDocument, + JsonNode previousTransportDocument) { public ObjectNode toJson() { var node = OBJECT_MAPPER.createObjectNode() .put("scenarioType", scenarioType.name()) @@ -27,9 +30,12 @@ public ObjectNode toJson() { .put("transportDocumentReference", transportDocumentReference) .put("shippingInstructionsStatus", serializeEnum(shippingInstructionsStatus, ShippingInstructionsStatus::wireName)) .put("updatedShippingInstructionsStatus", serializeEnum(updatedShippingInstructionsStatus, ShippingInstructionsStatus::wireName)) - .put("transportDocumentStatus", serializeEnum(transportDocumentStatus, TransportDocumentStatus::wireName)); + .put("transportDocumentStatus", serializeEnum(transportDocumentStatus, TransportDocumentStatus::wireName)) + .put("newTransportDocumentContent", newTransportDocumentContent); node.replace("shippingInstructions", shippingInstructions); node.replace("updatedShippingInstructions", updatedShippingInstructions); + node.replace("transportDocument", transportDocument); + node.replace("previousTransportDocument", previousTransportDocument); return node; } @@ -56,7 +62,10 @@ public static DynamicScenarioParameters fromJson(JsonNode jsonNode) { jsonNode.path("updatedShippingInstructions"), readEnum(jsonNode.required("shippingInstructionsStatus").asText(null), ShippingInstructionsStatus::fromWireName), readEnum(jsonNode.required("updatedShippingInstructionsStatus").asText(null), ShippingInstructionsStatus::fromWireName), - readEnum(jsonNode.required("transportDocumentStatus").asText(null), TransportDocumentStatus::fromWireName) + readEnum(jsonNode.required("transportDocumentStatus").asText(null), TransportDocumentStatus::fromWireName), + jsonNode.path("newTransportDocumentContent").asBoolean(false), + jsonNode.path("transportDocument"), + jsonNode.path("previousTransportDocument") ); } } diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/EblCarrier.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/EblCarrier.java index d88d5cfa..fc3e6791 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/EblCarrier.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/EblCarrier.java @@ -24,6 +24,8 @@ import org.dcsa.conformance.standards.ebl.checks.ScenarioType; import org.dcsa.conformance.standards.ebl.models.CarrierShippingInstructions; +import static org.dcsa.conformance.standards.ebl.party.EblShipper.siFromScenarioType; + @Slf4j public class EblCarrier extends ConformanceParty { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -76,7 +78,8 @@ protected Map, Consumer> getActionP Map.entry(UC11i_Carrier_IssueAmendedTransportDocumentAction.class, this::issueAmendedTransportDocument), Map.entry(UC12_Carrier_AwaitSurrenderRequestForDeliveryAction.class, this::notifyOfSurrenderForDelivery), Map.entry(UC13_Carrier_ProcessSurrenderRequestForDeliveryAction.class, this::processSurrenderRequestForDelivery), - Map.entry(UC14_Carrier_ConfirmShippingInstructionsCompleteAction.class, this::confirmShippingInstructionsComplete) + Map.entry(UC14_Carrier_ConfirmShippingInstructionsCompleteAction.class, this::confirmShippingInstructionsComplete), + Map.entry(UCX_Carrier_TDOnlyProcessOutOfBandUpdateOrAmendmentRequestDraftTransportDocumentAction.class, this::processOutOfBandUpdateOrAmendmentRequestTransportDocumentAction) ); } @@ -242,14 +245,45 @@ private void processUpdatedShippingInstructions(JsonNode actionPrompt) { addOperatorLogEntry("Processed update to the shipping instructions with document reference '%s'".formatted(documentReference)); } - private void publishDraftTransportDocument(JsonNode actionPrompt) { - log.info("Carrier.publishDraftTransportDocument(%s)".formatted(actionPrompt.toPrettyString())); + + + private void processOutOfBandUpdateOrAmendmentRequestTransportDocumentAction(JsonNode actionPrompt) { + log.info("Carrier.processOutOfBandUpdateOrAmendmentRequestTransportDocumentAction(%s)".formatted(actionPrompt.toPrettyString())); var documentReference = actionPrompt.required("documentReference").asText(); - var scenarioType = ScenarioType.valueOf(actionPrompt.required("scenarioType").asText()); var sir = tdrToSir.getOrDefault(documentReference, documentReference); - var si = CarrierShippingInstructions.fromPersistentStore(persistentMap, sir); + var updatedSI = EblShipper.updateShippingInstructions(si.getShippingInstructions()); + si.putShippingInstructions(documentReference, updatedSI); + si.acceptUpdatedShippingInstructions(documentReference); + asyncOrchestratorPostPartyInput( + OBJECT_MAPPER + .createObjectNode() + .put("actionId", actionPrompt.required("actionId").asText())); + addOperatorLogEntry("Process out of band amendment for transport document '%s'".formatted(documentReference)); + } + + private void publishDraftTransportDocument(JsonNode actionPrompt) { + log.info("Carrier.publishDraftTransportDocument(%s)".formatted(actionPrompt.toPrettyString())); + + var scenarioType = ScenarioType.valueOf(actionPrompt.required("scenarioType").asText()); + var skipSI = actionPrompt.required("skipSI").asBoolean(false); + String documentReference; + CarrierShippingInstructions si; + if (skipSI) { + var csp = CarrierScenarioParameters.fromJson(actionPrompt.required("csp")); + var jsonRequestBody = siFromScenarioType( + scenarioType, + csp, + apiVersion + ); + si = CarrierShippingInstructions.initializeFromShippingInstructionsRequest(jsonRequestBody, apiVersion); + documentReference = si.getShippingInstructionsReference(); + } else { + documentReference = actionPrompt.required("documentReference").asText(); + var sir = tdrToSir.getOrDefault(documentReference, documentReference); + si = CarrierShippingInstructions.fromPersistentStore(persistentMap, sir); + } si.publishDraftTransportDocument(documentReference, scenarioType); si.save(persistentMap); tdrToSir.put(si.getTransportDocumentReference(), si.getShippingInstructionsReference()); @@ -291,7 +325,6 @@ private void notifyOfSurrenderForDelivery(JsonNode actionPrompt) { var documentReference = actionPrompt.required("documentReference").asText(); var sir = tdrToSir.getOrDefault(documentReference, documentReference); - var si = CarrierShippingInstructions.fromPersistentStore(persistentMap, sir); si.surrenderForDeliveryRequest(documentReference); si.save(persistentMap); @@ -324,7 +357,6 @@ private void voidTransportDocument(JsonNode actionPrompt) { var documentReference = actionPrompt.required("documentReference").asText(); var sir = tdrToSir.getOrDefault(documentReference, documentReference); - var si = CarrierShippingInstructions.fromPersistentStore(persistentMap, sir); si.voidTransportDocument(documentReference); si.save(persistentMap); diff --git a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/EblShipper.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/EblShipper.java index 6f4d213e..bb6e22ad 100644 --- a/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/EblShipper.java +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/party/EblShipper.java @@ -68,43 +68,36 @@ protected Map, Consumer> getActionP ); } - - private void sendShippingInstructionsRequest(JsonNode actionPrompt) { - log.info("Shipper.sendShippingInstructionsRequest(%s)".formatted(actionPrompt.toPrettyString())); - - CarrierScenarioParameters carrierScenarioParameters = - CarrierScenarioParameters.fromJson(actionPrompt.get("csp")); - var scenarioType = ScenarioType.valueOf(actionPrompt.required("scenarioType").asText()); - + static ObjectNode siFromScenarioType(ScenarioType scenarioType, CarrierScenarioParameters carrierScenarioParameters, String apiVersion) { var jsonRequestBody = (ObjectNode) - JsonToolkit.templateFileToJsonNode( - "/standards/ebl/messages/" + scenarioType.shipperTemplate(apiVersion), - Map.ofEntries( - Map.entry( - "CARRIER_BOOKING_REFERENCE_PLACEHOLDER", - carrierScenarioParameters.carrierBookingReference()), - Map.entry( - "COMMODITY_SUBREFERENCE_PLACEHOLDER", - carrierScenarioParameters.commoditySubreference()), - Map.entry( - "COMMODITY_SUBREFERENCE_2_PLACEHOLDER", - Objects.requireNonNullElse(carrierScenarioParameters.commoditySubreference2(), "")), - Map.entry( - "EQUIPMENT_REFERENCE_PLACEHOLDER", - Objects.requireNonNullElse(carrierScenarioParameters.equipmentReference(), "")), - Map.entry( - "EQUIPMENT_REFERENCE_2_PLACEHOLDER", - Objects.requireNonNullElse(carrierScenarioParameters.equipmentReference2(), "")), - Map.entry("INVOICE_PAYABLE_AT_UNLOCATION_CODE", carrierScenarioParameters.invoicePayableAtUNLocationCode()), - Map.entry("CONSIGNMENT_ITEM_HS_CODE", carrierScenarioParameters.consignmentItemHSCode()), - Map.entry("CONSIGNMENT_ITEM_2_HS_CODE", Objects.requireNonNullElse(carrierScenarioParameters.consignmentItem2HSCode(), "")), - Map.entry("DESCRIPTION_OF_GOODS_PLACEHOLDER", carrierScenarioParameters.descriptionOfGoods()), - Map.entry("DESCRIPTION_OF_GOODS_2_PLACEHOLDER", Objects.requireNonNullElse(carrierScenarioParameters.descriptionOfGoods2(), "")), - Map.entry("OUTER_PACKAGING_DESCRIPTION_PLACEHOLDER", Objects.requireNonNullElse(carrierScenarioParameters.outerPackagingDescription(), "")), - Map.entry("SERVICE_CONTRACT_REFERENCE_PLACEHOLDER", carrierScenarioParameters.serviceContractReference()), - Map.entry("CONTRACT_QUOTATION_REFERENCE_PLACEHOLDER", carrierScenarioParameters.contractQuotationReference()), - Map.entry("TRANSPORT_DOCUMENT_TYPE_CODE_PLACEHOLDER", scenarioType.transportDocumentTypeCode()) - )); + JsonToolkit.templateFileToJsonNode( + "/standards/ebl/messages/" + scenarioType.shipperTemplate(apiVersion), + Map.ofEntries( + Map.entry( + "CARRIER_BOOKING_REFERENCE_PLACEHOLDER", + carrierScenarioParameters.carrierBookingReference()), + Map.entry( + "COMMODITY_SUBREFERENCE_PLACEHOLDER", + carrierScenarioParameters.commoditySubreference()), + Map.entry( + "COMMODITY_SUBREFERENCE_2_PLACEHOLDER", + Objects.requireNonNullElse(carrierScenarioParameters.commoditySubreference2(), "")), + Map.entry( + "EQUIPMENT_REFERENCE_PLACEHOLDER", + Objects.requireNonNullElse(carrierScenarioParameters.equipmentReference(), "")), + Map.entry( + "EQUIPMENT_REFERENCE_2_PLACEHOLDER", + Objects.requireNonNullElse(carrierScenarioParameters.equipmentReference2(), "")), + Map.entry("INVOICE_PAYABLE_AT_UNLOCATION_CODE", carrierScenarioParameters.invoicePayableAtUNLocationCode()), + Map.entry("CONSIGNMENT_ITEM_HS_CODE", carrierScenarioParameters.consignmentItemHSCode()), + Map.entry("CONSIGNMENT_ITEM_2_HS_CODE", Objects.requireNonNullElse(carrierScenarioParameters.consignmentItem2HSCode(), "")), + Map.entry("DESCRIPTION_OF_GOODS_PLACEHOLDER", carrierScenarioParameters.descriptionOfGoods()), + Map.entry("DESCRIPTION_OF_GOODS_2_PLACEHOLDER", Objects.requireNonNullElse(carrierScenarioParameters.descriptionOfGoods2(), "")), + Map.entry("OUTER_PACKAGING_DESCRIPTION_PLACEHOLDER", Objects.requireNonNullElse(carrierScenarioParameters.outerPackagingDescription(), "")), + Map.entry("SERVICE_CONTRACT_REFERENCE_PLACEHOLDER", carrierScenarioParameters.serviceContractReference()), + Map.entry("CONTRACT_QUOTATION_REFERENCE_PLACEHOLDER", carrierScenarioParameters.contractQuotationReference()), + Map.entry("TRANSPORT_DOCUMENT_TYPE_CODE_PLACEHOLDER", scenarioType.transportDocumentTypeCode()) + )); // Cannot substitute this because it is a boolean jsonRequestBody.put("isToOrder", scenarioType.isToOrder()); @@ -118,7 +111,20 @@ private void sendShippingInstructionsRequest(JsonNode actionPrompt) { .put("name", "DCSA another test person") .put("email", "no-reply@dcsa-consignee.example.org"); } + return jsonRequestBody; + } + + private void sendShippingInstructionsRequest(JsonNode actionPrompt) { + log.info("Shipper.sendShippingInstructionsRequest(%s)".formatted(actionPrompt.toPrettyString())); + CarrierScenarioParameters carrierScenarioParameters = + CarrierScenarioParameters.fromJson(actionPrompt.get("csp")); + var scenarioType = ScenarioType.valueOf(actionPrompt.required("scenarioType").asText()); + var jsonRequestBody = siFromScenarioType( + scenarioType, + carrierScenarioParameters, + apiVersion + ); ConformanceResponse conformanceResponse = syncCounterpartPost("/v3/shipping-instructions", jsonRequestBody); JsonNode jsonBody = conformanceResponse.message().body().getJsonBody(); @@ -144,14 +150,7 @@ private void sendUpdatedShippingInstructionsRequest(JsonNode actionPrompt) { } private ObjectNode sendUpdatedShippingInstructions(String sir, String documentReference) { - var si = (ObjectNode) persistentMap.load(sir); - var seal = si.required("utilizedTransportEquipments").required(0).required("seals").path(0); - var newSealNumber = "NSL13388"; - if (newSealNumber.equals(seal.required("number").asText())) { - // Ensure we do a change in case we do multiple UC3 in the same run - newSealNumber = "NSL13386"; - } - ((ObjectNode)seal).put("number", newSealNumber); + var si = updateShippingInstructions((ObjectNode) persistentMap.load(sir)); var siWithoutStatus = si.deepCopy(); siWithoutStatus.remove("shippingInstructionsStatus"); @@ -162,6 +161,17 @@ private ObjectNode sendUpdatedShippingInstructions(String sir, String documentRe return si.put("shippingInstructionsStatus", shippingInstructionsStatus); } + static ObjectNode updateShippingInstructions(ObjectNode si) { + var seal = si.required("utilizedTransportEquipments").required(0).required("seals").path(0); + var newSealNumber = "NSL13388"; + if (newSealNumber.equals(seal.required("number").asText())) { + // Ensure we do a change in case we do multiple UC3 in the same run + newSealNumber = "NSL13386"; + } + ((ObjectNode)seal).put("number", newSealNumber); + return si; + } + private void sendCancellationToUpdatedShippingInstructions(String documentReference) { var approvePayload = OBJECT_MAPPER.createObjectNode() .put("updatedShippingInstructionsStatus", ShippingInstructionsStatus.SI_UPDATE_CANCELLED.wireName());