Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DT-628: UC7 - Shipper - Approve Draft Transport Document #29

Merged
merged 1 commit into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
nt-gt marked this conversation as resolved.
Show resolved Hide resolved
);
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
Loading