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 1631cf96..faa9098a 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 @@ -68,7 +68,7 @@ private EblScenarioListBuilder thenAllPathsFrom( .thenHappyPathFrom(SI_RECEIVED)), uc4d_carrier_declineUpdatedShippingInstructions() .then(shipper_GetShippingInstructions(SI_RECEIVED, SI_DECLINED, TD_START) - .thenAllPathsFrom(SI_DECLINED))); + .thenHappyPathFrom(SI_DECLINED))); case SI_DECLINED -> thenEither( uc6_carrier_publishDraftTransportDocument() .then(shipper_GetShippingInstructions(SI_RECEIVED, TD_DRAFT, true) @@ -79,7 +79,13 @@ private EblScenarioListBuilder thenAllPathsFrom( .thenHappyPathFrom(SI_PENDING_UPDATE)), uc3_shipper_submitUpdatedShippingInstructions() .then(shipper_GetShippingInstructions(SI_UPDATE_RECEIVED, TD_START) + .thenAllPathsFrom(SI_UPDATE_RECEIVED))); + case SI_PENDING_UPDATE -> then(uc3_shipper_submitUpdatedShippingInstructions() + .then( + shipper_GetShippingInstructions(SI_RECEIVED, SI_UPDATE_RECEIVED, TD_START) .thenHappyPathFrom(SI_UPDATE_RECEIVED))); + case SI_ANY -> throw new AssertionError("Not a real/reachable state"); + case SI_COMPLETED -> then(noAction()); default -> then(noAction()); // TODO }; } @@ -100,6 +106,8 @@ private EblScenarioListBuilder thenHappyPathFrom( .then(shipper_GetShippingInstructions(SI_RECEIVED, TD_START) .thenHappyPathFrom(SI_RECEIVED)) ); + case SI_COMPLETED -> then(noAction()); + case SI_START, SI_ANY -> throw new AssertionError("Not a real/reachable state"); default -> then(noAction()); // TODO }; } @@ -111,11 +119,15 @@ private EblScenarioListBuilder thenAllPathsFrom(TransportDocumentStatus transpor .then( shipper_GetTransportDocument(TD_ISSUED) .thenAllPathsFrom(TD_ISSUED))); - case TD_ISSUED -> then( + case TD_ISSUED -> thenEither( + uc9_carrier_awaitSurrenderRequestForAmendment() + .then(shipper_GetTransportDocument(TD_PENDING_SURRENDER_FOR_AMENDMENT) + .thenAllPathsFrom(TD_PENDING_SURRENDER_FOR_AMENDMENT)), uc12_carrier_awaitSurrenderRequestForDelivery() .then(shipper_GetTransportDocument(TD_PENDING_SURRENDER_FOR_DELIVERY) .thenAllPathsFrom(TD_PENDING_SURRENDER_FOR_DELIVERY)) ); + case TD_PENDING_SURRENDER_FOR_AMENDMENT -> then(noAction()); // TODO: Implement case TD_PENDING_SURRENDER_FOR_DELIVERY -> thenEither( uc13a_carrier_acceptSurrenderRequestForDelivery().then( shipper_GetTransportDocument(TD_SURRENDERED_FOR_DELIVERY) @@ -127,7 +139,9 @@ private EblScenarioListBuilder thenAllPathsFrom(TransportDocumentStatus transpor ) ); case TD_SURRENDERED_FOR_DELIVERY -> thenHappyPathFrom(transportDocumentStatus); - default -> then(noAction()); // TODO + case TD_START, TD_ANY -> throw new AssertionError("Not a real/reachable state"); + case TD_VOIDED -> then(noAction()); + default -> throw new AssertionError("Not implemented: " + transportDocumentStatus.name()); }; } @@ -143,6 +157,7 @@ private EblScenarioListBuilder thenHappyPathFrom(TransportDocumentStatus transpo .then(shipper_GetTransportDocument(TD_PENDING_SURRENDER_FOR_DELIVERY) .thenHappyPathFrom(TD_PENDING_SURRENDER_FOR_DELIVERY)) ); + case TD_PENDING_SURRENDER_FOR_AMENDMENT -> then(noAction()); // TODO: Implement case TD_PENDING_SURRENDER_FOR_DELIVERY -> then( uc13a_carrier_acceptSurrenderRequestForDelivery().then( shipper_GetTransportDocument(TD_SURRENDERED_FOR_DELIVERY) @@ -154,7 +169,9 @@ private EblScenarioListBuilder thenHappyPathFrom(TransportDocumentStatus transpo shipper_GetShippingInstructions(SI_COMPLETED, TD_SURRENDERED_FOR_DELIVERY) ) ); - default -> then(noAction()); // TODO + case TD_START, TD_ANY -> throw new AssertionError("Not a real/reachable state"); + case TD_VOIDED -> then(noAction()); + default -> throw new AssertionError("Not implemented: " + transportDocumentStatus.name()); }; } @@ -330,6 +347,20 @@ private static EblScenarioListBuilder uc8_carrier_issueTransportDocument() { EBL_NOTIFICATIONS_API, EBL_TD_NOTIFICATION_SCHEMA_NAME))); } + private static EblScenarioListBuilder uc9_carrier_awaitSurrenderRequestForAmendment() { + EblComponentFactory componentFactory = threadLocalComponentFactory.get(); + String carrierPartyName = threadLocalCarrierPartyName.get(); + String shipperPartyName = threadLocalShipperPartyName.get(); + return new EblScenarioListBuilder( + previousAction -> + new UC9_Carrier_AwaitSurrenderRequestForAmendmentAction( + carrierPartyName, + shipperPartyName, + (EblAction) previousAction, + componentFactory.getMessageSchemaValidator( + EBL_NOTIFICATIONS_API, EBL_TD_NOTIFICATION_SCHEMA_NAME))); + } + private static EblScenarioListBuilder uc12_carrier_awaitSurrenderRequestForDelivery() { EblComponentFactory componentFactory = threadLocalComponentFactory.get(); String carrierPartyName = threadLocalCarrierPartyName.get(); 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 new file mode 100644 index 00000000..9e9782ed --- /dev/null +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC9_Carrier_AwaitSurrenderRequestForAmendmentAction.java @@ -0,0 +1,46 @@ +package org.dcsa.conformance.standards.ebl.action; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.stream.Stream; +import lombok.Getter; +import org.dcsa.conformance.core.check.*; + +@Getter +public class UC9_Carrier_AwaitSurrenderRequestForAmendmentAction extends StateChangingSIAction { + private final JsonSchemaValidator requestSchemaValidator; + + public UC9_Carrier_AwaitSurrenderRequestForAmendmentAction( + String carrierPartyName, + String shipperPartyName, + EblAction previousAction, + JsonSchemaValidator requestSchemaValidator) { + super(carrierPartyName, shipperPartyName, previousAction, "UC9", 204); + this.requestSchemaValidator = requestSchemaValidator; + } + + @Override + public String getHumanReadablePrompt() { + return ("UC9: Shipper requests surrender for amendment (via the surrender API if applicable) for transport document with reference %s and carrier sends notification that surrender has been requested. Note when the conformance toolkit is acting as carrier, no action is required from the shipper (the action will auto-resolve). When the conformance toolkit is acting like the Shipper, you will have to ensure that the carrier system sees a surrender request (the surrender uses a different API not in scope for this test)." + .formatted(getDspSupplier().get().transportDocumentReference())); + } + + @Override + public ObjectNode asJsonNode() { + return super.asJsonNode() + .put("documentReference", getDspSupplier().get().transportDocumentReference()); + } + + @Override + public ConformanceCheck createCheck(String expectedApiVersion) { + return new ConformanceCheck(getActionTitle()) { + @Override + protected Stream createSubChecks() { + return getTDNotificationChecks( + getMatchedExchangeUuid(), + expectedApiVersion, + requestSchemaValidator + ); + } + }; + } +} 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 eff8f503..32eaf0b8 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 @@ -221,12 +221,19 @@ public void issueTransportDocument(String documentReference) { .put(shippedDateField, date); } + public void surrenderForAmendmentRequest(String documentReference) { + checkState(documentReference, getTransportDocumentState(), s -> s == TD_ISSUED); + var td = getTransportDocument().orElseThrow(); + td.put(TRANSPORT_DOCUMENT_STATUS, TD_PENDING_SURRENDER_FOR_AMENDMENT.wireName()); + } + public void surrenderForDeliveryRequest(String documentReference) { checkState(documentReference, getTransportDocumentState(), s -> s == TD_ISSUED); var td = getTransportDocument().orElseThrow(); td.put(TRANSPORT_DOCUMENT_STATUS, TD_PENDING_SURRENDER_FOR_DELIVERY.wireName()); } + private void copyFieldIfPresent(JsonNode source, ObjectNode dest, String field) { var data = source.get(field); if (data != null) { 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 0a223d63..9a3ae3d3 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 @@ -74,6 +74,7 @@ protected Map, Consumer> getActionP Map.entry(UC4_Carrier_ProcessUpdateToShippingInstructionsAction.class, this::processUpdatedShippingInstructions), Map.entry(UC6_Carrier_PublishDraftTransportDocumentAction.class, this::publishDraftTransportDocument), Map.entry(UC8_Carrier_IssueTransportDocumentAction.class, this::issueTransportDocument), + Map.entry(UC9_Carrier_AwaitSurrenderRequestForAmendmentAction.class, this::notifyOfSurrenderForAmendment), Map.entry(UC12_Carrier_AwaitSurrenderRequestForDeliveryAction.class, this::notifyOfSurrenderForDelivery), Map.entry(UC13_Carrier_ProcessSurrenderRequestForDeliveryAction.class, this::processSurrenderRequestForDelivery), Map.entry(UC14_Carrier_ConfirmShippingInstructionsCompleteAction.class, this::confirmShippingInstructionsComplete) @@ -165,6 +166,20 @@ private void issueTransportDocument(JsonNode actionPrompt) { addOperatorLogEntry("Issued transport document '%s'".formatted(documentReference)); } + private void notifyOfSurrenderForAmendment(JsonNode actionPrompt) { + log.info("Carrier.notifyOfSurrenderForAmendment(%s)".formatted(actionPrompt.toPrettyString())); + + var documentReference = actionPrompt.required("documentReference").asText(); + var sir = tdrToSir.getOrDefault(documentReference, documentReference); + + var si = CarrierShippingInstructions.fromPersistentStore(persistentMap, sir); + si.surrenderForAmendmentRequest(documentReference); + si.save(persistentMap); + generateAndEmitNotificationFromTransportDocument(actionPrompt, si, true); + + addOperatorLogEntry("Sent notification for surrender for amendment of transport document with reference '%s'".formatted(documentReference)); + } + private void notifyOfSurrenderForDelivery(JsonNode actionPrompt) { log.info("Carrier.notifyOfSurrenderForDelivery(%s)".formatted(actionPrompt.toPrettyString()));