Skip to content

Commit

Permalink
DT-633: UC8 - Carrier - issue 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 5, 2023
1 parent 0368e6b commit b57e2c5
Show file tree
Hide file tree
Showing 9 changed files with 357 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ private EblScenarioListBuilder thenAllPathsFrom(TransportDocumentStatus transpor
case TD_DRAFT -> then(
uc8_carrier_issueTransportDocument()
.then(
shipper_GetShippingInstructions(SI_ANY, TD_ISSUED).thenAllPathsFrom(TD_ISSUED)));
shipper_GetTransportDocument(TD_ISSUED)
.thenAllPathsFrom(TD_ISSUED)));
case TD_ISSUED -> then(noAction()); // TODO
default -> then(noAction()); // TODO
};
Expand Down Expand Up @@ -215,6 +216,16 @@ private static EblScenarioListBuilder uc6_carrier_publishDraftTransportDocument(
}

private static EblScenarioListBuilder uc8_carrier_issueTransportDocument() {
return noAction();
EblComponentFactory componentFactory = threadLocalComponentFactory.get();
String carrierPartyName = threadLocalCarrierPartyName.get();
String shipperPartyName = threadLocalShipperPartyName.get();
return new EblScenarioListBuilder(
previousAction ->
new UC8_Carrier_IssueTransportDocumentAction(
carrierPartyName,
shipperPartyName,
(EblAction) previousAction,
componentFactory.getMessageSchemaValidator(
EBL_NOTIFICATIONS_API, EBL_TD_NOTIFICATION_SCHEMA_NAME)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.dcsa.conformance.standards.ebl.party.*;

import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
Expand Down Expand Up @@ -156,36 +157,78 @@ private <T> DynamicScenarioParameters updateIfNotNull(
}

protected Stream<ActionCheck> getSINotificationChecks(
String expectedApiVersion, JsonSchemaValidator notificationSchemaValidator) {
String expectedApiVersion, JsonSchemaValidator notificationSchemaValidator) {
return getSINotificationChecks(getMatchedNotificationExchangeUuid(), expectedApiVersion, notificationSchemaValidator);
}

protected Stream<ActionCheck> getSINotificationChecks(
UUID notificationExchangeUuid, String expectedApiVersion, JsonSchemaValidator notificationSchemaValidator) {
String titlePrefix = "[Notification]";
return Stream.of(
new HttpMethodCheck(
titlePrefix, EblRole::isCarrier, getMatchedNotificationExchangeUuid(), "POST"),
titlePrefix, EblRole::isCarrier, notificationExchangeUuid, "POST"),
new UrlPathCheck(
titlePrefix,
EblRole::isCarrier,
getMatchedNotificationExchangeUuid(),
notificationExchangeUuid,
"/v3/shipping-instructions-notifications"),
new ResponseStatusCheck(
titlePrefix, EblRole::isShipper, getMatchedNotificationExchangeUuid(), 204),
titlePrefix, EblRole::isShipper, notificationExchangeUuid, 204),
new ApiHeaderCheck(
titlePrefix,
EblRole::isCarrier,
getMatchedNotificationExchangeUuid(),
notificationExchangeUuid,
HttpMessageType.REQUEST,
expectedApiVersion),
new ApiHeaderCheck(
titlePrefix,
EblRole::isShipper,
getMatchedNotificationExchangeUuid(),
notificationExchangeUuid,
HttpMessageType.RESPONSE,
expectedApiVersion),
new JsonSchemaCheck(
titlePrefix,
EblRole::isCarrier,
getMatchedNotificationExchangeUuid(),
notificationExchangeUuid,
HttpMessageType.REQUEST,
notificationSchemaValidator))
.filter(Objects::nonNull);
notificationSchemaValidator));
}

protected Stream<ActionCheck> getTDNotificationChecks(
String expectedApiVersion, JsonSchemaValidator notificationSchemaValidator) {
return getTDNotificationChecks(getMatchedNotificationExchangeUuid(), expectedApiVersion, notificationSchemaValidator);
}

protected Stream<ActionCheck> getTDNotificationChecks(
UUID notificationExchangeUuid, String expectedApiVersion, JsonSchemaValidator notificationSchemaValidator) {
String titlePrefix = "[Notification]";
return Stream.of(
new HttpMethodCheck(
titlePrefix, EblRole::isCarrier, notificationExchangeUuid, "POST"),
new UrlPathCheck(
titlePrefix,
EblRole::isCarrier,
notificationExchangeUuid,
"/v3/transport-document-notifications"),
new ResponseStatusCheck(
titlePrefix, EblRole::isShipper, notificationExchangeUuid, 204),
new ApiHeaderCheck(
titlePrefix,
EblRole::isCarrier,
notificationExchangeUuid,
HttpMessageType.REQUEST,
expectedApiVersion),
new ApiHeaderCheck(
titlePrefix,
EblRole::isShipper,
notificationExchangeUuid,
HttpMessageType.RESPONSE,
expectedApiVersion),
new JsonSchemaCheck(
titlePrefix,
EblRole::isCarrier,
notificationExchangeUuid,
HttpMessageType.REQUEST,
notificationSchemaValidator));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,11 @@ public ConformanceCheck createCheck(String expectedApiVersion) {
return new ConformanceCheck(getActionTitle()) {
@Override
protected Stream<? extends ConformanceCheck> createSubChecks() {
return Stream.of(
new HttpMethodCheck(EblRole::isCarrier, getMatchedExchangeUuid(), "POST"),
new UrlPathCheck(
EblRole::isCarrier, getMatchedExchangeUuid(), "/v3/shipping-instructions-notifications"),
new ResponseStatusCheck(
EblRole::isShipper, getMatchedExchangeUuid(), expectedStatus),
// TODO: Notification payload check
new ApiHeaderCheck(
EblRole::isCarrier,
getMatchedExchangeUuid(),
HttpMessageType.REQUEST,
expectedApiVersion),
new ApiHeaderCheck(
EblRole::isShipper,
getMatchedExchangeUuid(),
HttpMessageType.RESPONSE,
expectedApiVersion),
new JsonSchemaCheck(
EblRole::isCarrier,
getMatchedExchangeUuid(),
HttpMessageType.REQUEST,
requestSchemaValidator));
return getSINotificationChecks(
getMatchedExchangeUuid(),
expectedApiVersion,
requestSchemaValidator
);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,11 @@ public ConformanceCheck createCheck(String expectedApiVersion) {
return new ConformanceCheck(getActionTitle()) {
@Override
protected Stream<? extends ConformanceCheck> createSubChecks() {
return Stream.of(
new HttpMethodCheck(EblRole::isCarrier, getMatchedExchangeUuid(), "POST"),
new UrlPathCheck(
EblRole::isCarrier, getMatchedExchangeUuid(), "/v3/transport-document-notifications"),
new ResponseStatusCheck(
EblRole::isShipper, getMatchedExchangeUuid(), expectedStatus),
// TODO: Notification payload check
new ApiHeaderCheck(
EblRole::isCarrier,
getMatchedExchangeUuid(),
HttpMessageType.REQUEST,
expectedApiVersion),
new ApiHeaderCheck(
EblRole::isShipper,
getMatchedExchangeUuid(),
HttpMessageType.RESPONSE,
expectedApiVersion),
new JsonSchemaCheck(
EblRole::isCarrier,
getMatchedExchangeUuid(),
HttpMessageType.REQUEST,
requestSchemaValidator));
return getTDNotificationChecks(
getMatchedExchangeUuid(),
expectedApiVersion,
requestSchemaValidator
);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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.*;
import org.dcsa.conformance.core.traffic.HttpMessageType;
import org.dcsa.conformance.standards.ebl.party.EblRole;

@Getter
public class UC8_Carrier_IssueTransportDocumentAction extends StateChangingSIAction {
private final JsonSchemaValidator requestSchemaValidator;

public UC8_Carrier_IssueTransportDocumentAction(
String carrierPartyName,
String shipperPartyName,
EblAction previousAction,
JsonSchemaValidator requestSchemaValidator) {
super(carrierPartyName, shipperPartyName, previousAction, "UC8", 204);
this.requestSchemaValidator = requestSchemaValidator;
}

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

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

@Override
public ConformanceCheck createCheck(String expectedApiVersion) {
return new ConformanceCheck(getActionTitle()) {
@Override
protected Stream<? extends ConformanceCheck> createSubChecks() {
return getTDNotificationChecks(
getMatchedExchangeUuid(),
expectedApiVersion,
requestSchemaValidator
);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.function.*;
import org.dcsa.conformance.core.state.JsonNodeMap;
import org.dcsa.conformance.standards.ebl.party.ShippingInstructionsStatus;
import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus;

public class CarrierShippingInstructions {

Expand Down Expand Up @@ -148,7 +149,7 @@ private void setReason(String reason) {

public void cancelShippingInstructionsUpdate(String shippingInstructionsReference, String reason) {
checkState(shippingInstructionsReference, getShippingInstructionsState(), s -> s == SI_UPDATE_RECEIVED);
changeState(UPDATED_SI_STATUS, SI_CANCELLED);
changeSIState(UPDATED_SI_STATUS, SI_CANCELLED);
if (reason == null || reason.isBlank()) {
reason = "Update cancelled by shipper (no reason given)";
}
Expand All @@ -158,7 +159,7 @@ public void cancelShippingInstructionsUpdate(String shippingInstructionsReferenc
public void requestChangesToShippingInstructions(String documentReference, Consumer<ArrayNode> requestedChangesGenerator) {
checkState(documentReference, getShippingInstructionsState(), PENDING_UPDATE_PREREQUISITE_STATES::contains);
clearUpdatedShippingInstructions();
changeState(SI_STATUS, SI_PENDING_UPDATE);
changeSIState(SI_STATUS, SI_PENDING_UPDATE);
setReason(null);
mutateShippingInstructionsAndUpdate(siData -> requestedChangesGenerator.accept(siData.putArray("requestedChanges")));
}
Expand All @@ -170,7 +171,7 @@ public void acceptUpdatedShippingInstructions(String documentReference) {
clearUpdatedShippingInstructions();
setReason(null);
mutateShippingInstructionsAndUpdate(siData -> siData.remove("requestedChanges"));
changeState(SI_STATUS, SI_RECEIVED);
changeSIState(SI_STATUS, SI_RECEIVED);
}

public void publishDraftTransportDocument(String documentReference) {
Expand All @@ -182,6 +183,20 @@ public void publishDraftTransportDocument(String documentReference) {
mutateShippingInstructionsAndUpdate(si -> si.put(TRANSPORT_DOCUMENT_REFERENCE, tdr));
}

public void issueTransportDocument(String documentReference) {
checkState(documentReference, getTransportDocumentState(), s -> s == TD_DRAFT || s == TD_APPROVED);
var td = getTransportDocument().orElseThrow();
var date = LocalDate.now().toString();
var shippedDateField = td.path("isShippedOnBoardType").asBoolean(true)
? "shippedOnBoardDate"
: "receivedForShipmentDate";
td.put(TRANSPORT_DOCUMENT_STATUS, TD_ISSUED.wireName())
.put("issueDate", date)
// Reset the shippedOnBoardDate as it generally cannot happen before the issueDate.
// It is less clear whether we should do it for receivedForShipmentDate but ¯\_(ツ)_/¯
.put(shippedDateField, date);
}

private void copyFieldIfPresent(JsonNode source, ObjectNode dest, String field) {
var data = source.get(field);
if (data != null) {
Expand Down Expand Up @@ -221,7 +236,7 @@ private void generateTDFromSI() {
state.set(TD_DATA_FIELD, td);
}

private void changeState(String attributeName, ShippingInstructionsStatus newState) {
private void changeSIState(String attributeName, ShippingInstructionsStatus newState) {
mutateShippingInstructionsAndUpdate(b -> b.put(attributeName, newState.wireName()));
}

Expand All @@ -234,7 +249,15 @@ private static void checkState(
String reference, ShippingInstructionsStatus currentState, Predicate<ShippingInstructionsStatus> expectedState) {
if (!expectedState.test(currentState)) {
throw new IllegalStateException(
"Booking '%s' is in state '%s'".formatted(reference, currentState));
"SI '%s' is in state '%s'".formatted(reference, currentState));
}
}

private static void checkState(
String reference, TransportDocumentStatus currentState, Predicate<TransportDocumentStatus> expectedState) {
if (!expectedState.test(currentState)) {
throw new IllegalStateException(
"TD '%s' is in state '%s'".formatted(reference, currentState));
}
}

Expand All @@ -251,19 +274,32 @@ public void putShippingInstructions(String documentReference, ObjectNode newShip
currentState,
s -> s != SI_DECLINED && s != SI_COMPLETED
);
changeState(UPDATED_SI_STATUS, SI_UPDATE_RECEIVED);
changeSIState(UPDATED_SI_STATUS, SI_UPDATE_RECEIVED);
copyMetadataFields(getShippingInstructions(), newShippingInstructionData);
setUpdatedShippingInstructions(newShippingInstructionData);
removeRequestedChanges();
}

public ShippingInstructionsStatus getShippingInstructionsState() {
var booking = getShippingInstructions();
var s = booking.path(UPDATED_SI_STATUS);
var siData = getShippingInstructions();
var s = siData.path(UPDATED_SI_STATUS);
if (s.isTextual()) {
return ShippingInstructionsStatus.fromWireName(s.asText());
}
return ShippingInstructionsStatus.fromWireName(booking.required(SI_STATUS).asText());
return ShippingInstructionsStatus.fromWireName(siData.required(SI_STATUS).asText());
}


public TransportDocumentStatus getTransportDocumentState() {
var tdData = getTransportDocument().orElse(null);
if (tdData == null) {
return TD_START;
}
var s = tdData.required(TRANSPORT_DOCUMENT_STATUS);
if (s.isTextual()) {
return TransportDocumentStatus.fromWireName(s.asText());
}
return TD_START;
}

public static CarrierShippingInstructions initializeFromShippingInstructionsRequest(ObjectNode bookingRequest) {
Expand Down
Loading

0 comments on commit b57e2c5

Please sign in to comment.