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..e42d9520 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,23 +119,39 @@ 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 -> thenEither( + uc10a_carrier_acceptSurrenderRequestForAmendment().then( + shipper_GetTransportDocument(TD_SURRENDERED_FOR_AMENDMENT) + .thenAllPathsFrom(TD_SURRENDERED_FOR_AMENDMENT) + ), + uc10r_carrier_rejectSurrenderRequestForAmendment().then( + shipper_GetTransportDocument(TD_ISSUED) + .thenHappyPathFrom(TD_ISSUED) + ) + ); + case TD_SURRENDERED_FOR_AMENDMENT -> then(noAction()); // TODO: Implement case TD_PENDING_SURRENDER_FOR_DELIVERY -> thenEither( uc13a_carrier_acceptSurrenderRequestForDelivery().then( shipper_GetTransportDocument(TD_SURRENDERED_FOR_DELIVERY) .thenAllPathsFrom(TD_SURRENDERED_FOR_DELIVERY) ), uc13r_carrier_rejectSurrenderRequestForDelivery().then( - shipper_GetTransportDocument(TD_SURRENDERED_FOR_DELIVERY) + shipper_GetTransportDocument(TD_ISSUED) .thenHappyPathFrom(TD_ISSUED) ) ); 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()); }; } @@ -137,12 +161,19 @@ private EblScenarioListBuilder thenHappyPathFrom(TransportDocumentStatus transpo uc8_carrier_issueTransportDocument() .then( shipper_GetTransportDocument(TD_ISSUED) - .thenAllPathsFrom(TD_ISSUED))); + .thenHappyPathFrom(TD_ISSUED))); case TD_ISSUED -> then( uc12_carrier_awaitSurrenderRequestForDelivery() .then(shipper_GetTransportDocument(TD_PENDING_SURRENDER_FOR_DELIVERY) .thenHappyPathFrom(TD_PENDING_SURRENDER_FOR_DELIVERY)) ); + case TD_PENDING_SURRENDER_FOR_AMENDMENT -> then( + uc10a_carrier_acceptSurrenderRequestForAmendment().then( + shipper_GetTransportDocument(TD_SURRENDERED_FOR_AMENDMENT) + .thenHappyPathFrom(TD_SURRENDERED_FOR_AMENDMENT) + ) + ); + case TD_SURRENDERED_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 +185,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 +363,50 @@ 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 uc10a_carrier_acceptSurrenderRequestForAmendment() { + EblComponentFactory componentFactory = threadLocalComponentFactory.get(); + String carrierPartyName = threadLocalCarrierPartyName.get(); + String shipperPartyName = threadLocalShipperPartyName.get(); + return new EblScenarioListBuilder( + previousAction -> + new UC10_Carrier_ProcessSurrenderRequestForAmendmentAction( + carrierPartyName, + shipperPartyName, + (EblAction) previousAction, + componentFactory.getMessageSchemaValidator( + EBL_NOTIFICATIONS_API, EBL_TD_NOTIFICATION_SCHEMA_NAME), + true)); + } + + private static EblScenarioListBuilder uc10r_carrier_rejectSurrenderRequestForAmendment() { + EblComponentFactory componentFactory = threadLocalComponentFactory.get(); + String carrierPartyName = threadLocalCarrierPartyName.get(); + String shipperPartyName = threadLocalShipperPartyName.get(); + return new EblScenarioListBuilder( + previousAction -> + new UC10_Carrier_ProcessSurrenderRequestForAmendmentAction( + carrierPartyName, + shipperPartyName, + (EblAction) previousAction, + componentFactory.getMessageSchemaValidator( + EBL_NOTIFICATIONS_API, EBL_TD_NOTIFICATION_SCHEMA_NAME), + false)); + } + 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/UC10_Carrier_ProcessSurrenderRequestForAmendmentAction.java b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC10_Carrier_ProcessSurrenderRequestForAmendmentAction.java new file mode 100644 index 00000000..d117a446 --- /dev/null +++ b/ebl/src/main/java/org/dcsa/conformance/standards/ebl/action/UC10_Carrier_ProcessSurrenderRequestForAmendmentAction.java @@ -0,0 +1,54 @@ +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 UC10_Carrier_ProcessSurrenderRequestForAmendmentAction extends StateChangingSIAction { + private final JsonSchemaValidator requestSchemaValidator; + private final boolean acceptAmendmentRequest; + + public UC10_Carrier_ProcessSurrenderRequestForAmendmentAction( + String carrierPartyName, + String shipperPartyName, + EblAction previousAction, + JsonSchemaValidator requestSchemaValidator, + boolean acceptAmendmentRequest) { + super(carrierPartyName, shipperPartyName, previousAction, acceptAmendmentRequest ? "UC10a" : "UC10r", 204); + this.requestSchemaValidator = requestSchemaValidator; + this.acceptAmendmentRequest = acceptAmendmentRequest; + } + + @Override + public String getHumanReadablePrompt() { + if (acceptAmendmentRequest) { + return ("UC10a: Accept surrender request for amendment for transport document with reference %s" + .formatted(getDspSupplier().get().transportDocumentReference())); + } + return ("UC10r: Reject surrender request for amendment for transport document with reference %s" + .formatted(getDspSupplier().get().transportDocumentReference())); + } + + @Override + public ObjectNode asJsonNode() { + return super.asJsonNode() + .put("documentReference", getDspSupplier().get().transportDocumentReference()) + .put("acceptAmendmentRequest", acceptAmendmentRequest); + } + + @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/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..d3b6b167 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 @@ -185,10 +185,23 @@ public void confirmShippingInstructionsComplete(String documentReference) { changeSIState(SI_STATUS, SI_COMPLETED); } + + public void acceptSurrenderForAmendment(String documentReference) { + checkState(documentReference, getTransportDocumentState(), s -> s == TD_PENDING_SURRENDER_FOR_AMENDMENT); + var td = getTransportDocument().orElseThrow(); + td.put(TRANSPORT_DOCUMENT_STATUS, TD_SURRENDERED_FOR_AMENDMENT.wireName()); + } + + public void rejectSurrenderForAmendment(String documentReference) { + checkState(documentReference, getTransportDocumentState(), s -> s == TD_PENDING_SURRENDER_FOR_AMENDMENT); + var td = getTransportDocument().orElseThrow(); + td.put(TRANSPORT_DOCUMENT_STATUS, TD_ISSUED.wireName()); + } + public void acceptSurrenderForDelivery(String documentReference) { checkState(documentReference, getTransportDocumentState(), s -> s == TD_PENDING_SURRENDER_FOR_DELIVERY); var td = getTransportDocument().orElseThrow(); - td.put(TRANSPORT_DOCUMENT_STATUS, TD_SURRENDERED_FOR_AMENDMENT.wireName()); + td.put(TRANSPORT_DOCUMENT_STATUS, TD_SURRENDERED_FOR_DELIVERY.wireName()); } public void rejectSurrenderForDelivery(String documentReference) { @@ -221,12 +234,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..1b491c7e 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,8 @@ 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(UC10_Carrier_ProcessSurrenderRequestForAmendmentAction.class, this::processSurrenderRequestForAmendment), 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 +167,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())); @@ -179,6 +195,24 @@ private void notifyOfSurrenderForDelivery(JsonNode actionPrompt) { addOperatorLogEntry("Sent notification for surrender for delivery of transport document with reference '%s'".formatted(documentReference)); } + private void processSurrenderRequestForAmendment(JsonNode actionPrompt) { + log.info("Carrier.processSurrenderRequestForAmendment(%s)".formatted(actionPrompt.toPrettyString())); + + var documentReference = actionPrompt.required("documentReference").asText(); + var sir = tdrToSir.getOrDefault(documentReference, documentReference); + var accept = actionPrompt.required("acceptAmendmentRequest").asBoolean(true); + + var si = CarrierShippingInstructions.fromPersistentStore(persistentMap, sir); + if (accept) { + si.acceptSurrenderForAmendment(documentReference); + } else { + si.rejectSurrenderForAmendment(documentReference); + } + si.save(persistentMap); + generateAndEmitNotificationFromTransportDocument(actionPrompt, si, true); + + addOperatorLogEntry("Processed surrender request for delivery of transport document with reference '%s'".formatted(documentReference)); + } private void processSurrenderRequestForDelivery(JsonNode actionPrompt) { log.info("Carrier.processSurrenderRequestForDelivery(%s)".formatted(actionPrompt.toPrettyString()));