Skip to content

Commit

Permalink
DT-628: UC7 - Shipper - Approve Draft Transport Document
Browse files Browse the repository at this point in the history
Signed-off-by: Niels Thykier <[email protected]>
  • Loading branch information
nt-gt committed Dec 11, 2023
1 parent 9b96d08 commit be2a5f2
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public class EblScenarioListBuilder extends ScenarioListBuilder<EblScenarioListB
private static final String GET_TD_SCHEMA_NAME = "getTransportDocument";
private static final String POST_EBL_SCHEMA_NAME = "ShippingInstructionsRequest";
private static final String PUT_EBL_SCHEMA_NAME = "ShippingInstructionsUpdate";
private static final String PATCH_TD_SCHEMA_NAME = "transportdocuments_transportDocumentReference_body";
private static final String EBL_REF_STATUS_SCHEMA_NAME = "ShippingInstructionsRefStatus";
private static final String TD_REF_STATUS_SCHEMA_NAME = "TransportDocumentRefStatus";
private static final String EBL_SI_NOTIFICATION_SCHEMA_NAME = "ShippingInstructionsNotification";
private static final String EBL_TD_NOTIFICATION_SCHEMA_NAME = "TransportDocumentNotification";

Expand Down Expand Up @@ -114,11 +116,23 @@ private EblScenarioListBuilder thenHappyPathFrom(

private EblScenarioListBuilder thenAllPathsFrom(TransportDocumentStatus transportDocumentStatus) {
return switch (transportDocumentStatus) {
case TD_DRAFT -> then(
case TD_DRAFT -> thenEither(
uc8_carrier_issueTransportDocument()
.then(
shipper_GetTransportDocument(TD_ISSUED)
.thenAllPathsFrom(TD_ISSUED)));
// Using happy path here as requested in
// https://github.com/dcsaorg/Conformance-Gateway/pull/29#discussion_r1421732797
.thenHappyPathFrom(TD_ISSUED)),
uc7_shipper_approveDraftTransportDocument()
.then(shipper_GetTransportDocument(TD_APPROVED)
.thenAllPathsFrom(TD_APPROVED))
);
case TD_APPROVED -> then(
uc8_carrier_issueTransportDocument()
.then(
shipper_GetTransportDocument(TD_ISSUED)
.thenAllPathsFrom(TD_ISSUED))
);
case TD_ISSUED -> thenEither(
uc9_carrier_awaitSurrenderRequestForAmendment()
.then(shipper_GetTransportDocument(TD_PENDING_SURRENDER_FOR_AMENDMENT)
Expand Down Expand Up @@ -157,11 +171,20 @@ private EblScenarioListBuilder thenAllPathsFrom(TransportDocumentStatus transpor

private EblScenarioListBuilder thenHappyPathFrom(TransportDocumentStatus transportDocumentStatus) {
return switch (transportDocumentStatus) {
case TD_DRAFT -> then(
case TD_DRAFT -> thenEither(
uc8_carrier_issueTransportDocument()
.then(
shipper_GetTransportDocument(TD_ISSUED)
.thenHappyPathFrom(TD_ISSUED)));
.thenHappyPathFrom(TD_ISSUED)),
uc7_shipper_approveDraftTransportDocument()
.then(shipper_GetTransportDocument(TD_APPROVED)
.thenHappyPathFrom(TD_APPROVED)));
case TD_APPROVED -> then(
uc8_carrier_issueTransportDocument()
.then(
shipper_GetTransportDocument(TD_ISSUED)
.thenHappyPathFrom(TD_ISSUED))
);
case TD_ISSUED -> then(
uc12_carrier_awaitSurrenderRequestForDelivery()
.then(shipper_GetTransportDocument(TD_PENDING_SURRENDER_FOR_DELIVERY)
Expand Down Expand Up @@ -349,6 +372,24 @@ private static EblScenarioListBuilder uc6_carrier_publishDraftTransportDocument(
EBL_NOTIFICATIONS_API, EBL_TD_NOTIFICATION_SCHEMA_NAME)));
}

private static EblScenarioListBuilder uc7_shipper_approveDraftTransportDocument() {
EblComponentFactory componentFactory = threadLocalComponentFactory.get();
String carrierPartyName = threadLocalCarrierPartyName.get();
String shipperPartyName = threadLocalShipperPartyName.get();
return new EblScenarioListBuilder(
previousAction ->
new UC7_Shipper_ApproveDraftTransportDocumentAction(
carrierPartyName,
shipperPartyName,
(EblAction) previousAction,
componentFactory.getMessageSchemaValidator(
EBL_API, PATCH_TD_SCHEMA_NAME),
componentFactory.getMessageSchemaValidator(
EBL_API, TD_REF_STATUS_SCHEMA_NAME),
componentFactory.getMessageSchemaValidator(
EBL_NOTIFICATIONS_API, EBL_TD_NOTIFICATION_SCHEMA_NAME)));
}

private static EblScenarioListBuilder uc8_carrier_issueTransportDocument() {
EblComponentFactory componentFactory = threadLocalComponentFactory.get();
String carrierPartyName = threadLocalCarrierPartyName.get();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.dcsa.conformance.standards.ebl.action;

import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.stream.Stream;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.dcsa.conformance.core.check.*;
import org.dcsa.conformance.core.traffic.HttpMessageType;
import org.dcsa.conformance.standards.ebl.party.EblRole;

@Getter
@Slf4j
public class UC7_Shipper_ApproveDraftTransportDocumentAction extends StateChangingSIAction {
private final JsonSchemaValidator requestSchemaValidator;
private final JsonSchemaValidator responseSchemaValidator;
private final JsonSchemaValidator notificationSchemaValidator;

public UC7_Shipper_ApproveDraftTransportDocumentAction(
String carrierPartyName,
String shipperPartyName,
EblAction previousAction,
JsonSchemaValidator requestSchemaValidator,
JsonSchemaValidator responseSchemaValidator,
JsonSchemaValidator notificationSchemaValidator) {
super(shipperPartyName, carrierPartyName, previousAction, "UC7", 200);
this.requestSchemaValidator = requestSchemaValidator;
this.responseSchemaValidator = responseSchemaValidator;
this.notificationSchemaValidator = notificationSchemaValidator;
}

@Override
public String getHumanReadablePrompt() {
return ("UC7: Approve the draft transport document with document reference %s".formatted(
getDspSupplier().get().transportDocumentReference()));
}

@Override
public ObjectNode asJsonNode() {
return super.asJsonNode()
.put("documentReference", getDspSupplier().get().transportDocumentReference());
}

@Override
protected boolean expectsNotificationExchange() {
return true;
}

@Override
public ConformanceCheck createCheck(String expectedApiVersion) {
return new ConformanceCheck(getActionTitle()) {
@Override
protected Stream<? extends ConformanceCheck> createSubChecks() {
var dsp = getDspSupplier().get();
var tdr = dsp.transportDocumentReference() != null ? dsp.transportDocumentReference() : "<DSP MISSING TD REFERENCE>";
Stream<ActionCheck> primaryExchangeChecks =
Stream.of(
new HttpMethodCheck(EblRole::isShipper, getMatchedExchangeUuid(), "PATCH"),
new UrlPathCheck(EblRole::isShipper, getMatchedExchangeUuid(), "/v3/transport-documents/%s".formatted(tdr)),
new ResponseStatusCheck(
EblRole::isCarrier, getMatchedExchangeUuid(), expectedStatus),
new ApiHeaderCheck(
EblRole::isShipper,
getMatchedExchangeUuid(),
HttpMessageType.REQUEST,
expectedApiVersion),
new ApiHeaderCheck(
EblRole::isCarrier,
getMatchedExchangeUuid(),
HttpMessageType.RESPONSE,
expectedApiVersion),
// TODO: Add Carrier Ref Status Payload response check
// TODO: Add Shipper content conformance check
new JsonSchemaCheck(
EblRole::isShipper,
getMatchedExchangeUuid(),
HttpMessageType.REQUEST,
requestSchemaValidator),
new JsonSchemaCheck(
EblRole::isCarrier,
getMatchedExchangeUuid(),
HttpMessageType.RESPONSE,
responseSchemaValidator));
return Stream.concat(
primaryExchangeChecks,
getTDNotificationChecks(
expectedApiVersion,
notificationSchemaValidator));
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ public void confirmShippingInstructionsComplete(String documentReference) {
changeSIState(SI_STATUS, SI_COMPLETED);
}

public void approveDraftTransportDocument(String documentReference) {
checkState(documentReference, getTransportDocumentState(), s -> s == TD_DRAFT);
var td = getTransportDocument().orElseThrow();
td.put(TRANSPORT_DOCUMENT_STATUS, TD_APPROVED.wireName());
}

public void acceptSurrenderForAmendment(String documentReference) {
checkState(documentReference, getTransportDocumentState(), s -> s == TD_PENDING_SURRENDER_FOR_AMENDMENT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,11 +375,11 @@ private ConformanceResponse _handleGetTransportDocument(ConformanceRequest reque
if (sir == null) {
return return404(request);
}
var persistedBookingData = persistentMap.load(sir);
if (persistedBookingData == null) {
var persistedSi = persistentMap.load(sir);
if (persistedSi == null) {
throw new IllegalStateException("We had a TDR -> SIR mapping, but there is no data related to that reference");
}
var si = CarrierShippingInstructions.fromPersistentStore(persistedBookingData);
var si = CarrierShippingInstructions.fromPersistentStore(persistedSi);
// If the TDR is resolvable, then the document must have a TD.
var body = si.getTransportDocument().orElseThrow();
ConformanceResponse response =
Expand All @@ -393,6 +393,41 @@ private ConformanceResponse _handleGetTransportDocument(ConformanceRequest reque
return response;
}

private ConformanceResponse _handlePatchTransportDocument(ConformanceRequest request, String documentReference) {
// bookingReference can either be a CBR or CBRR.
var sir = tdrToSir.get(documentReference);
if (sir == null) {
return return404(request);
}
var persistedSi = persistentMap.load(sir);
if (persistedSi == null) {
throw new IllegalStateException("We had a TDR -> SIR mapping, but there is no data related to that reference");
}
var si = CarrierShippingInstructions.fromPersistentStore(persistedSi);
si.approveDraftTransportDocument(documentReference);
si.save(persistentMap);
var td = si.getTransportDocument().orElseThrow();
if (isShipperNotificationEnabled) {
executor.schedule(
() ->
asyncCounterpartPost(
"/v3/transport-document-notifications",
TransportDocumentNotification.builder()
.apiVersion(apiVersion)
.transportDocument(td)
.build()
.asJsonNode()),
1,
TimeUnit.SECONDS);
}
return returnTransportDocumentRefStatusResponse(
200,
request,
td,
documentReference
);
}

private ConformanceResponse returnShippingInstructionsRefStatusResponse(
int responseCode, ConformanceRequest request, ObjectNode shippingInstructions, String documentReference) {
var sir = shippingInstructions.required("shippingInstructionsReference").asText();
Expand Down Expand Up @@ -425,6 +460,27 @@ private ConformanceResponse returnShippingInstructionsRefStatusResponse(
return response;
}


private ConformanceResponse returnTransportDocumentRefStatusResponse(
int responseCode, ConformanceRequest request, ObjectNode transportDocument, String documentReference) {
var tdr = transportDocument.required("transportDocumentReference").asText();
var tdStatus = transportDocument.required("transportDocumentStatus").asText();
var statusObject =
OBJECT_MAPPER
.createObjectNode()
.put("transportDocumentStatus", tdStatus)
.put("transportDocumentReference", tdr);
ConformanceResponse response =
request.createResponse(
responseCode,
Map.of("Api-Version", List.of(apiVersion)),
new ConformanceMessageBody(statusObject));
addOperatorLogEntry(
"Responded %d to %s TD '%s' (resulting state '%s')"
.formatted(responseCode, request.method(), documentReference, tdStatus));
return response;
}

@SneakyThrows
private ConformanceResponse _handlePostShippingInstructions(ConformanceRequest request) {
ObjectNode siPayload =
Expand Down Expand Up @@ -506,7 +562,15 @@ public ConformanceResponse handleRequest(ConformanceRequest request) {
}
yield return404(request);
}
// case "PATCH" -> _handlePatchRequest(request);
case "PATCH" -> {
var url = request.url().replaceAll("/++$", "");
var lastSegment = lastUrlSegment(url);
var urlStem = url.substring(0, url.length() - lastSegment.length()).replaceAll("/++$", "");
if (urlStem.endsWith("/v3/transport-documents")) {
yield _handlePatchTransportDocument(request, lastSegment);
}
yield return404(request);
}
case "PUT" -> _handlePutShippingInstructions(request);
default -> return405(request, "GET", "POST", "PUT", "PATCH");
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@
import org.dcsa.conformance.core.traffic.ConformanceMessageBody;
import org.dcsa.conformance.core.traffic.ConformanceRequest;
import org.dcsa.conformance.core.traffic.ConformanceResponse;
import org.dcsa.conformance.standards.ebl.action.Shipper_GetShippingInstructionsAction;
import org.dcsa.conformance.standards.ebl.action.Shipper_GetTransportDocumentAction;
import org.dcsa.conformance.standards.ebl.action.UC1_Shipper_SubmitShippingInstructionsAction;
import org.dcsa.conformance.standards.ebl.action.UC3_Shipper_SubmitUpdatedShippingInstructionsAction;
import org.dcsa.conformance.standards.ebl.action.*;

@Slf4j
public class EblShipper extends ConformanceParty {
Expand Down Expand Up @@ -61,7 +58,8 @@ protected Map<Class<? extends ConformanceAction>, Consumer<JsonNode>> getActionP
Map.entry(UC1_Shipper_SubmitShippingInstructionsAction.class, this::sendShippingInstructionsRequest),
Map.entry(Shipper_GetShippingInstructionsAction.class, this::getShippingInstructionsRequest),
Map.entry(Shipper_GetTransportDocumentAction.class, this::getTransportDocument),
Map.entry(UC3_Shipper_SubmitUpdatedShippingInstructionsAction.class, this::sendUpdatedShippingInstructionsRequest)
Map.entry(UC3_Shipper_SubmitUpdatedShippingInstructionsAction.class, this::sendUpdatedShippingInstructionsRequest),
Map.entry(UC7_Shipper_ApproveDraftTransportDocumentAction.class, this::approveDraftTransportDocument)
);
}

Expand Down Expand Up @@ -131,6 +129,23 @@ private void sendUpdatedShippingInstructionsRequest(JsonNode actionPrompt) {
.formatted(actionPrompt.toPrettyString()));
}

private void approveDraftTransportDocument(JsonNode actionPrompt) {
log.info("Shipper.approveDraftTransportDocument(%s)".formatted(actionPrompt.toPrettyString()));

var sir = actionPrompt.required("documentReference").asText();
var approvePayload = new ObjectMapper().createObjectNode()
.put("transportDocumentStatus", TransportDocumentStatus.TD_APPROVED.wireName());

asyncCounterpartPatch(
"/v3/transport-documents/%s".formatted(sir),
approvePayload);

addOperatorLogEntry(
"Approved transport document the parameters: %s"
.formatted(actionPrompt.toPrettyString()));
}


private void getShippingInstructionsRequest(JsonNode actionPrompt) {
log.info("Shipper.getShippingInstructionsRequest(%s)".formatted(actionPrompt.toPrettyString()));
String sir = actionPrompt.get("sir").asText();
Expand Down

0 comments on commit be2a5f2

Please sign in to comment.