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-639: UC11 - Carrier - Void and reissue transport document #34

Merged
merged 2 commits into from
Dec 13, 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 @@ -432,7 +432,7 @@ private void addCharge(ObjectNode booking) {
} else {
charges = booking.putArray("charges");
}
if (!charges.isEmpty()) {
if (charges.isEmpty()) {
charges
.addObject()
.put("chargeName", "Fictive booking fee")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ private EblScenarioListBuilder thenAllPathsFrom(TransportDocumentStatus transpor
.thenAllPathsFrom(TD_ISSUED))
);
case TD_ISSUED -> thenEither(
// TODO: We should have uc3 + uc4a + uc9 for an SI based amendment in parallel with UC9
// (without uc3 + uc4a, which is basically a booking based amendment)
uc9_carrier_awaitSurrenderRequestForAmendment()
.then(shipper_GetTransportDocument(TD_PENDING_SURRENDER_FOR_AMENDMENT)
.thenAllPathsFrom(TD_PENDING_SURRENDER_FOR_AMENDMENT)),
Expand All @@ -175,7 +177,11 @@ private EblScenarioListBuilder thenAllPathsFrom(TransportDocumentStatus transpor
.thenHappyPathFrom(TD_ISSUED)
)
);
case TD_SURRENDERED_FOR_AMENDMENT -> then(noAction()); // TODO: Implement
case TD_SURRENDERED_FOR_AMENDMENT -> then(
uc11_carrier_voidTransportDocument()
.then(uc11i_carrier_issueAmendedTransportDocument()
.then(shipper_GetTransportDocument(TD_ISSUED)
.thenHappyPathFrom(TD_ISSUED))));
case TD_PENDING_SURRENDER_FOR_DELIVERY -> thenEither(
uc13a_carrier_acceptSurrenderRequestForDelivery().then(
shipper_GetTransportDocument(TD_SURRENDERED_FOR_DELIVERY)
Expand Down Expand Up @@ -219,7 +225,11 @@ private EblScenarioListBuilder thenHappyPathFrom(TransportDocumentStatus transpo
.thenHappyPathFrom(TD_SURRENDERED_FOR_AMENDMENT)
)
);
case TD_SURRENDERED_FOR_AMENDMENT -> then(noAction()); // TODO: Implement
case TD_SURRENDERED_FOR_AMENDMENT -> then(
uc11_carrier_voidTransportDocument()
.then(uc11i_carrier_issueAmendedTransportDocument()
.then(shipper_GetTransportDocument(TD_ISSUED)
.thenHappyPathFrom(TD_ISSUED))));
case TD_PENDING_SURRENDER_FOR_DELIVERY -> then(
uc13a_carrier_acceptSurrenderRequestForDelivery().then(
shipper_GetTransportDocument(TD_SURRENDERED_FOR_DELIVERY)
Expand Down Expand Up @@ -489,6 +499,36 @@ private static EblScenarioListBuilder uc10r_carrier_rejectSurrenderRequestForAme
false));
}

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

private static EblScenarioListBuilder uc11i_carrier_issueAmendedTransportDocument() {
EblComponentFactory componentFactory = threadLocalComponentFactory.get();
String carrierPartyName = threadLocalCarrierPartyName.get();
String shipperPartyName = threadLocalShipperPartyName.get();
return new EblScenarioListBuilder(
previousAction ->
new UC11i_Carrier_IssueAmendedTransportDocumentAction(
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*;
import org.dcsa.conformance.standards.ebl.checks.EBLChecks;
import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus;

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

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

@Override
public String getHumanReadablePrompt() {
return ("UC11i: Issue amended transport document to replace the transport document with reference %s."
.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<? extends ConformanceCheck> createSubChecks() {
return Stream.concat(
Stream.concat(
EBLChecks.tdNotificationTDR(getMatchedExchangeUuid(), getDspSupplier().get().transportDocumentReference()),
EBLChecks.tdNotificationStatusChecks(getMatchedExchangeUuid(), TransportDocumentStatus.TD_ISSUED)
),
getTDNotificationChecks(
getMatchedExchangeUuid(),
expectedApiVersion,
requestSchemaValidator,
TransportDocumentStatus.TD_ISSUED
));
}
};
}
}
Original file line number Diff line number Diff line change
@@ -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.*;
import org.dcsa.conformance.standards.ebl.checks.EBLChecks;
import org.dcsa.conformance.standards.ebl.party.TransportDocumentStatus;

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

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

@Override
public String getHumanReadablePrompt() {
return ("UC11v: Void original transport document with reference %s."
.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<? extends ConformanceCheck> createSubChecks() {
return Stream.concat(
Stream.concat(
EBLChecks.tdNotificationTDR(getMatchedExchangeUuid(), getDspSupplier().get().transportDocumentReference()),
EBLChecks.tdNotificationStatusChecks(getMatchedExchangeUuid(), TransportDocumentStatus.TD_VOIDED)
),
getTDNotificationChecks(
getMatchedExchangeUuid(),
expectedApiVersion,
requestSchemaValidator,
TransportDocumentStatus.TD_VOIDED
));
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,15 @@ private static TDField initialFieldValue(String attribute, Supplier<String> valu
}

private static TDField initialFieldValue(String attribute, BiConsumer<ObjectNode, String> valueSetter) {
return new TDField(attribute, valueSetter);
return new TDField(attribute, valueSetter, null);
}

private static TDField preserveIfPresent(String attribute) {
return new TDField(attribute, (o, a) -> {});
return new TDField(attribute, null, null);
}

private static TDField field(String attribute, BiConsumer<ObjectNode, String> initializer, BiConsumer<ObjectNode, String> updater) {
return new TDField(attribute, initializer, updater);
}

private static TDField issuingParty() {
Expand All @@ -115,8 +119,22 @@ private static TDField issuingParty() {

private record TDField(
String attribute,
BiConsumer<ObjectNode, String> provider
) {}
BiConsumer<ObjectNode, String> initializer,
BiConsumer<ObjectNode, String> updater
) {

public void provideField(JsonNode source, ObjectNode dest) {
var data = source != null ? source.get(attribute) : null;
if (data != null) {
dest.set(attribute, data.deepCopy());
if (updater != null) {
updater.accept(dest, attribute);
}
} else if (initializer != null) {
initializer.accept(dest, attribute);
}
}
}

private static final TDField[] CARRIER_PROVIDED_TD_FIELDS = {
initialFieldValue(
Expand Down Expand Up @@ -159,9 +177,43 @@ private record TDField(
assert result.isTextual();
o.set(a, result);
}),
initialFieldValue("carrierCodeListProvider", "SMDG")
initialFieldValue("carrierCodeListProvider", "SMDG"),
// We always add a charge per amendment to ensure an amendment is never identical to the document
// it replaces (which could cause issues with the issuance API). In reality, the carrier would also
// change some other detail (like the requested change or the issuance date). But in the
// conformance tests every thing happens in the same day and when the conformance toolkit is
// the carrier, booking amendments are "zero-change" amendments.
field("charges", (o, a) -> addCharge(o.putArray(a)), (o, a) -> addCharge(o.path(a)))
};

private static void addCharge(JsonNode chargesArray) {
if (!chargesArray.isArray()) {
return;
}
var charges = (ArrayNode)chargesArray;
if (charges.isEmpty()) {
charges
.addObject()
.put("chargeName", "Fictive transport document fee")
.put("currencyAmount", 1f)
.put("currencyCode", "EUR")
.put("paymentTermCode", "COL")
.put("calculationBasis", "For the concrete transport document")
.put("unitPrice", 1f)
.put("quantity", 1);
} else {
charges
.addObject()
.put("chargeName", "Fictive amendment fee")
.put("currencyAmount", 1f)
.put("currencyCode", "EUR")
.put("paymentTermCode", "PRE")
.put("calculationBasis", "For the entire amendment")
.put("unitPrice", 1f)
.put("quantity", 1);
}
}

private static final String SI_DATA_FIELD = "si";
private static final String UPDATED_SI_DATA_FIELD = "updatedSi";

Expand Down Expand Up @@ -269,6 +321,21 @@ public void acceptSurrenderForAmendment(String documentReference) {
td.put(TRANSPORT_DOCUMENT_STATUS, TD_SURRENDERED_FOR_AMENDMENT.wireName());
}

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

public void issueAmendedTransportDocument(String documentReference) {
checkState(documentReference, getTransportDocumentState(), s -> s == TD_VOIDED);
this.generateDraftTD();
updateTDForIssuance();
var tdData = getTransportDocument().orElseThrow();
var tdr = tdData.required(TRANSPORT_DOCUMENT_REFERENCE).asText();
mutateShippingInstructionsAndUpdate(si -> si.put(TRANSPORT_DOCUMENT_REFERENCE, tdr));
}

public void rejectSurrenderForAmendment(String documentReference) {
checkState(documentReference, getTransportDocumentState(), s -> s == TD_PENDING_SURRENDER_FOR_AMENDMENT);
var td = getTransportDocument().orElseThrow();
Expand All @@ -293,14 +360,18 @@ public void publishDraftTransportDocument(String documentReference) {
// 2) There is no update received (that is "grey" is not UPDATE_RECEIVED)
checkState(documentReference, getOriginalShippingInstructionState(), s -> s == SI_RECEIVED);
checkState(documentReference, getOriginalShippingInstructionState(), s -> s != SI_UPDATE_RECEIVED);
this.generateTDFromSI();
this.generateDraftTD();
var tdData = getTransportDocument().orElseThrow();
var tdr = tdData.required(TRANSPORT_DOCUMENT_REFERENCE).asText();
mutateShippingInstructionsAndUpdate(si -> si.put(TRANSPORT_DOCUMENT_REFERENCE, tdr));
}

public void issueTransportDocument(String documentReference) {
checkState(documentReference, getTransportDocumentState(), s -> s == TD_DRAFT || s == TD_APPROVED);
updateTDForIssuance();
}

private void updateTDForIssuance() {
var td = getTransportDocument().orElseThrow();
var date = LocalDate.now().toString();
var shippedDateField = td.path("isShippedOnBoardType").asBoolean(true)
Expand Down Expand Up @@ -336,14 +407,7 @@ private void copyFieldsWherePresent(JsonNode source, ObjectNode dest, String ...

private void preserveOrGenerateCarrierFields(JsonNode source, ObjectNode dest) {
for (var entry : CARRIER_PROVIDED_TD_FIELDS) {
var field = entry.attribute();
var data = source != null ? source.get(field) : null;
if (data != null) {
dest.set(field, data.deepCopy());
} else {
var generator = entry.provider();
generator.accept(dest, field);
}
entry.provideField(source, dest);
}
}

Expand Down Expand Up @@ -380,7 +444,7 @@ private void fixupUtilizedTransportEquipments(ObjectNode transportDocument) {
}
}

private void generateTDFromSI() {
private void generateDraftTD() {
var td = OBJECT_MAPPER.createObjectNode();
var siData = getShippingInstructions();
var existingTd = getTransportDocument().orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ protected Map<Class<? extends ConformanceAction>, Consumer<JsonNode>> getActionP
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(UC11v_Carrier_VoidTransportDocumentAction.class, this::voidTransportDocument),
Map.entry(UC11i_Carrier_IssueAmendedTransportDocumentAction.class, this::issueAmendedTransportDocument),
Map.entry(UC12_Carrier_AwaitSurrenderRequestForDeliveryAction.class, this::notifyOfSurrenderForDelivery),
Map.entry(UC13_Carrier_ProcessSurrenderRequestForDeliveryAction.class, this::processSurrenderRequestForDelivery),
Map.entry(UC14_Carrier_ConfirmShippingInstructionsCompleteAction.class, this::confirmShippingInstructionsComplete)
Expand Down Expand Up @@ -214,6 +216,35 @@ private void processSurrenderRequestForAmendment(JsonNode actionPrompt) {
addOperatorLogEntry("Processed surrender request for delivery of transport document with reference '%s'".formatted(documentReference));
}

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

var documentReference = actionPrompt.required("documentReference").asText();
var sir = tdrToSir.getOrDefault(documentReference, documentReference);

var si = CarrierShippingInstructions.fromPersistentStore(persistentMap, sir);
si.voidTransportDocument(documentReference);
si.save(persistentMap);
generateAndEmitNotificationFromTransportDocument(actionPrompt, si, true);

addOperatorLogEntry("Voided transport document '%s'".formatted(documentReference));
}

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

var documentReference = actionPrompt.required("documentReference").asText();
var sir = tdrToSir.getOrDefault(documentReference, documentReference);

var si = CarrierShippingInstructions.fromPersistentStore(persistentMap, sir);
si.issueAmendedTransportDocument(documentReference);
si.save(persistentMap);
tdrToSir.put(si.getTransportDocumentReference(), si.getShippingInstructionsReference());
generateAndEmitNotificationFromTransportDocument(actionPrompt, si, true);

addOperatorLogEntry("Issued amended transport document '%s'".formatted(documentReference));
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ private void sendUpdatedShippingInstructionsRequest(JsonNode actionPrompt) {
var sir = actionPrompt.required("sir").asText();
var si = (ObjectNode) persistentMap.load(sir);

// TODO: Make a repeatable change (like change the weight)
si.put("transportDocumentTypeCode", "SWB");
asyncCounterpartPut(
"/v3/shipping-instructions/%s".formatted(sir),
Expand Down
Loading