Skip to content

Commit

Permalink
DT-973: Change "Country" to "Country Code"
Browse files Browse the repository at this point in the history
Also performed a series of "unrelated" changes as a consequence of the
schema changes. This happened because a lot of schema changes were
done without the conformance toolkit being kept up to speed
unfortunately.
  • Loading branch information
nt-gt committed Apr 16, 2024
1 parent 49431f0 commit 777bd98
Show file tree
Hide file tree
Showing 14 changed files with 913 additions and 468 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static ActionCheck tdScenarioChecks(UUID matched, String standardsVersion
}

public static ActionCheck tdContentChecks(UUID matched, String standardsVersion) {
var checks = genericTDContentChecks(TransportDocumentStatus.TD_ISSUED, null);
var checks = genericTDContentChecks(TransportDocumentStatus.TD_ISSUED, standardsVersion, null);
return JsonAttribute.contentChecks(
"Complex validations of transport document",
EblIssuanceRole::isCarrier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@ public class EBLChecks {
private static final JsonPointer TD_TDR = JsonPointer.compile("/transportDocumentReference");
private static final JsonPointer TD_TRANSPORT_DOCUMENT_STATUS = JsonPointer.compile("/transportDocumentStatus");

private static final JsonPointer[] TD_UN_LOCATION_CODES = {
JsonPointer.compile("/invoicePayableAt/UNLocationCode"),
JsonPointer.compile("/transports/placeOfReceipt/UNLocationCode"),
JsonPointer.compile("/transports/portOfLoading/UNLocationCode"),
JsonPointer.compile("/transports/portOfDischarge/UNLocationCode"),
JsonPointer.compile("/transports/placeOfDelivery/UNLocationCode"),
JsonPointer.compile("/transports/onwardInlandRouting/UNLocationCode"),
};

private static final JsonRebaseableContentCheck ONLY_EBLS_CAN_BE_NEGOTIABLE = JsonAttribute.ifThen(
"Validate transportDocumentTypeCode vs. isToOrder",
JsonAttribute.isTrue(JsonPointer.compile("/isToOrder")),
Expand Down Expand Up @@ -80,6 +71,27 @@ public class EBLChecks {
JsonAttribute.unique("countryCode", "type")
);

private static final Consumer<MultiAttributeValidator> ALL_UN_LOCATION_CODES = (mav) -> {
mav.submitAllMatching("invoicePayableAt.UNLocationCode");
mav.submitAllMatching("transports.placeOfReceipt.UNLocationCode");
mav.submitAllMatching("transports.portOfLoading.UNLocationCode");
mav.submitAllMatching("transports.portOfDischarge.UNLocationCode");
mav.submitAllMatching("transports.placeOfDelivery.UNLocationCode");
mav.submitAllMatching("transports.onwardInlandRouting.UNLocationCode");

// Beta-2 only
mav.submitAllMatching("issuingParty.address.UNLocationCode");
mav.submitAllMatching("documentParties.shippers.address.UNLocationCode");
mav.submitAllMatching("documentParties.consignee.address.UNLocationCode");
mav.submitAllMatching("documentParties.endorsee.address.UNLocationCode");
mav.submitAllMatching("documentParties.other.*.party.address.UNLocationCode");
};

private static final JsonRebaseableContentCheck TD_UN_LOCATION_CODES_VALID = JsonAttribute.allIndividualMatchesMustBeValid(
"UN Location are valid",
ALL_UN_LOCATION_CODES,
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(EblDatasets.UN_LOCODE_DATASET)
);

private static final Consumer<MultiAttributeValidator> ALL_UTE = (mav) -> mav.submitAllMatching("utilizedTransportEquipments.*");

Expand Down Expand Up @@ -194,6 +206,13 @@ public class EBLChecks {
mav.submitAllMatching("utilizedTransportEquipments.*.customsReferences.*.countryCode");
mav.submitAllMatching("documentParties.*.party.taxLegalReferences.*.countryCode");
mav.submitAllMatching("issuingParty.taxLegalReferences.*.countryCode");

// Beta-2 only
mav.submitAllMatching("issuingParty.address.countryCode");
mav.submitAllMatching("documentParties.shippers.address.countryCode");
mav.submitAllMatching("documentParties.consignee.address.countryCode");
mav.submitAllMatching("documentParties.endorsee.address.countryCode");
mav.submitAllMatching("documentParties.other.*.party.address.countryCode");
},
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(EblDatasets.ISO_3166_ALPHA2_COUNTRY_CODES)
);
Expand All @@ -204,14 +223,79 @@ public class EBLChecks {
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(EblDatasets.OUTER_PACKAGING_CODE)
);

private static final JsonRebaseableContentCheck DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE = JsonAttribute.customValidator(
private static final JsonRebaseableContentCheck DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE_V3B1 = JsonAttribute.customValidator(
"Each document party can be used at most once",
JsonAttribute.path(
"documentParties",
JsonAttribute.unique("partyFunction")
));

private static final JsonRebaseableContentCheck VALIDATE_DOCUMENT_PARTIES = JsonAttribute.customValidator(
private static final JsonRebaseableContentCheck DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE_V3B2_OR_LATER = JsonAttribute.customValidator(
"Each document party can be used at most once",
JsonAttribute.path(
"documentParties",
JsonAttribute.path("other", JsonAttribute.unique("partyFunction"))
));


private static final JsonRebaseableContentCheck VALIDATE_DOCUMENT_PARTIES_MATCH_EBL_V3B2_OR_LATER = JsonAttribute.customValidator(
"Validate documentParties match the EBL type",
(body, contextPath) -> {
var issues = new LinkedHashSet<String>();
var documentParties = body.path("documentParties");
var isToOrder = body.path("isToOrder").asBoolean(false);

if (!documentParties.has("shipper")) {
var documentPartiesPath = concatContextPath(contextPath, "documentParties.shipper");
issues.add("The 'shipper' party is mandatory in the eBL phase at '%s' (SI/TD)".formatted(documentPartiesPath));
}
var isToOrderPath = concatContextPath(contextPath, "isToOrder");

if (isToOrder) {
if (documentParties.has("consignee")) {
var documentPartiesPath = concatContextPath(contextPath, "documentParties.consignee");
var endorseePartiesPath = concatContextPath(contextPath, "documentParties.endorsee");
issues.add("The '%s' party cannot be used when '%s' is true (use '%s' instead)".formatted(documentPartiesPath, isToOrderPath, endorseePartiesPath));
}
} else {
if (!documentParties.has("consignee")) {
var documentPartiesPath = concatContextPath(contextPath, "documentParties.consignee");
issues.add("The 'CN' party is mandatory when '%s' is false".formatted(documentPartiesPath, isToOrderPath));
}
if (documentParties.has("endorsee")) {
var documentPartiesPath = concatContextPath(contextPath, "documentParties.endorsee");
issues.add("The '%s' party cannot be used when '%s' is false".formatted(documentPartiesPath, isToOrderPath));
}
}
return issues;
});

private static final JsonRebaseableContentCheck VALIDATE_SCO_CONDITIONALLY_PRESENT_V3B2_OR_LATER = JsonAttribute.customValidator(
"Validate that SCO party is conditionally present",
(body, contextPath) -> {
var issues = new LinkedHashSet<String>();
var otherDocumentParties = body.path("documentParties").path("other");
var partyFunctions = StreamSupport.stream(otherDocumentParties.spliterator(), false)
.map(p -> p.path("partyFunction"))
.filter(JsonNode::isTextual)
.map(n -> n.asText(""))
.collect(Collectors.toSet());

if (!partyFunctions.contains("SCO")) {
if (!body.path("serviceContractReference").isTextual()) {
var scrPath = concatContextPath(contextPath, "serviceContractReference");
issues.add("The 'SCO' party is mandatory when '%s' is absent".formatted(scrPath));
}
if (!body.path("contractQuotationReference").isTextual()) {
var cqrPath = concatContextPath(contextPath, "contractQuotationReference");
issues.add("The 'SCO' party is mandatory when '%s' is absent".formatted(cqrPath));
}
}
return issues;
}
);

private static final JsonRebaseableContentCheck VALIDATE_DOCUMENT_PARTIES_V3B1 = JsonAttribute.customValidator(
"Validate documentParties",
(body, contextPath) -> {
var issues = new LinkedHashSet<String>();
Expand Down Expand Up @@ -493,10 +577,11 @@ private static JsonContentMatchedValidation consignmentItemCargoItemAlignment(
EblDatasets.EBL_PLATFORMS_DATASET
),
ONLY_EBLS_CAN_BE_NEGOTIABLE,
JsonAttribute.ifThen(
JsonAttribute.ifThenElse(
"'isElectronic' implies 'sendToPlatform'",
JsonAttribute.isTrue(JsonPointer.compile("/isElectronic")),
JsonAttribute.mustBePresent(JsonPointer.compile("/sendToPlatform"))
JsonAttribute.mustBePresent(JsonPointer.compile("/sendToPlatform")),
JsonAttribute.mustBeAbsent(JsonPointer.compile("/sendToPlatform"))
),
VALID_REFERENCE_TYPES,
ISO_EQUIPMENT_CODE_IMPLIES_REEFER,
Expand All @@ -513,11 +598,13 @@ private static JsonContentMatchedValidation consignmentItemCargoItemAlignment(
CONSIGNMENT_ITEM_VS_CARGO_ITEM_WEIGHT_IS_ALIGNED,
CONSIGNMENT_ITEM_VS_CARGO_ITEM_VOLUME_IS_ALIGNED,
TLR_CC_T_COMBINATION_VALIDATIONS,
TLR_CC_T_COMBINATION_UNIQUE,
DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE,
VALIDATE_DOCUMENT_PARTIES
TLR_CC_T_COMBINATION_UNIQUE
);

private static boolean isV300B1(String version) {
return version.equals("3.0.0-Beta-1");
}

private static final List<JsonRebaseableContentCheck> STATIC_TD_CHECKS = Arrays.asList(
ONLY_EBLS_CAN_BE_NEGOTIABLE,
JsonAttribute.ifThenElse(
Expand Down Expand Up @@ -641,8 +728,7 @@ private static JsonContentMatchedValidation consignmentItemCargoItemAlignment(
),
TLR_CC_T_COMBINATION_VALIDATIONS,
TLR_CC_T_COMBINATION_UNIQUE,
DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE,
VALIDATE_DOCUMENT_PARTIES
TD_UN_LOCATION_CODES_VALID
);

public static final JsonContentCheck SIR_REQUIRED_IN_REF_STATUS = JsonAttribute.mustBePresent(SI_REF_SIR_PTR);
Expand Down Expand Up @@ -1018,6 +1104,14 @@ private static JsonContentMatchedValidation checkHSCodes(Supplier<CarrierScenari

public static ActionCheck siRequestContentChecks(UUID matched, String standardVersion, Supplier<CarrierScenarioParameters> cspSupplier, Supplier<DynamicScenarioParameters> dspSupplier) {
var checks = new ArrayList<>(STATIC_SI_CHECKS);
if (isV300B1(standardVersion)) {
checks.add(DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE_V3B1);
checks.add(VALIDATE_DOCUMENT_PARTIES_V3B1);
} else {
checks.add(DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE_V3B2_OR_LATER);
checks.add(VALIDATE_DOCUMENT_PARTIES_MATCH_EBL_V3B2_OR_LATER);
checks.add(VALIDATE_SCO_CONDITIONALLY_PRESENT_V3B2_OR_LATER);
}
generateScenarioRelatedChecks(checks, standardVersion, cspSupplier, dspSupplier, false);
return JsonAttribute.contentChecks(
EblRole::isShipper,
Expand Down Expand Up @@ -1141,7 +1235,7 @@ public static ActionCheck tdNotificationContentChecks(UUID matched, String stand
);
}

public static void genericTdContentChecks(List<? super JsonRebaseableContentCheck> jsonContentChecks, Supplier<String> tdrSupplier, TransportDocumentStatus transportDocumentStatus) {
public static void genericTdContentChecks(List<? super JsonRebaseableContentCheck> jsonContentChecks, String standardVersion, Supplier<String> tdrSupplier, TransportDocumentStatus transportDocumentStatus) {
if (tdrSupplier != null) {
jsonContentChecks.add(JsonAttribute.mustEqual(TD_TDR, tdrSupplier));
}
Expand All @@ -1150,15 +1244,21 @@ public static void genericTdContentChecks(List<? super JsonRebaseableContentChec
transportDocumentStatus.wireName()
));
jsonContentChecks.addAll(STATIC_TD_CHECKS);
for (var ptr : TD_UN_LOCATION_CODES) {
jsonContentChecks.add(JsonAttribute.mustBeDatasetKeywordIfPresent(ptr, EblDatasets.UN_LOCODE_DATASET));
if (isV300B1(standardVersion)) {
jsonContentChecks.add(DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE_V3B1);
jsonContentChecks.add(VALIDATE_DOCUMENT_PARTIES_V3B1);
} else {
jsonContentChecks.add(DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE_V3B2_OR_LATER);
jsonContentChecks.add(VALIDATE_DOCUMENT_PARTIES_MATCH_EBL_V3B2_OR_LATER);
jsonContentChecks.add(VALIDATE_SCO_CONDITIONALLY_PRESENT_V3B2_OR_LATER);
}
}

public static List<JsonRebaseableContentCheck> genericTDContentChecks(TransportDocumentStatus transportDocumentStatus, Supplier<String> tdrReferenceSupplier) {
public static List<JsonRebaseableContentCheck> genericTDContentChecks(TransportDocumentStatus transportDocumentStatus, String eblStandardVersion, Supplier<String> tdrReferenceSupplier) {
List<JsonRebaseableContentCheck> jsonContentChecks = new ArrayList<>();
genericTdContentChecks(
jsonContentChecks,
eblStandardVersion,
tdrReferenceSupplier,
transportDocumentStatus
);
Expand All @@ -1169,6 +1269,7 @@ public static ActionCheck tdPlusScenarioContentChecks(UUID matched, String stand
List<JsonContentCheck> jsonContentChecks = new ArrayList<>();
genericTdContentChecks(
jsonContentChecks,
standardVersion,
() -> dspSupplier.get().transportDocumentReference(),
transportDocumentStatus
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class CarrierShippingInstructions {

private static final String SI_STATUS = "shippingInstructionsStatus";
private static final String UPDATED_SI_STATUS = "updatedShippingInstructionsStatus";
private static final String SUBSCRIPTION_REFERENCE = "subscriptionReference";


private static final String SHIPPING_INSTRUCTIONS_REFERENCE = "shippingInstructionsReference";
Expand Down Expand Up @@ -79,30 +80,40 @@ public class CarrierShippingInstructions {
Map.entry("ZIM", new VesselDetails("9699115", "ZIM WILMINGTON"))
);

private static JsonNode issuingCarrier(String name, String smdgCode) {
var issuingCarrier = OBJECT_MAPPER.createObjectNode()
.put("partyName", name);
issuingCarrier
.putArray("identifyingCodes")
.addObject()
.put("codeListProvider", "SMDG")
.put("codeListName", "LCL")
.put("partyCode", smdgCode);
return issuingCarrier;
private static Function<String, JsonNode> issuingCarrier(String name, String smdgCode, String countryCode) {
return (String version) -> {
var issuingCarrier = OBJECT_MAPPER.createObjectNode().put("partyName", name);
if (!version.equals("3.0.0-Beta-1")) {
issuingCarrier
.putObject("address")
.put("street", "The street name would be here")
.put("streetNumber", "... and here the street number")
.put("city", "... and here the city")
.put("countryCode", countryCode);
}
issuingCarrier
.putArray("identifyingCodes")
.addObject()
.put("codeListProvider", "SMDG")
.put("codeListName", "LCL")
.put("partyCode", smdgCode);
return issuingCarrier;
};
}

// Randomize the issuing carrier to avoid favouring a particular carrier
private static final JsonNode[] ISSUING_CARRIER_DEFINITIONS = {
@SuppressWarnings("unchecked")
private static final Function<String, JsonNode>[] ISSUING_CARRIER_DEFINITIONS = (Function<String, JsonNode>[]) new Function[]{
// Name is from the SMDG code list
issuingCarrier("CMA CGM", "CMA"),
issuingCarrier("Evergreen Marine Corporation", "EMC"),
issuingCarrier("Hapag Lloyd", "HLC"),
issuingCarrier("Hyundai", "HMM"),
issuingCarrier("Maersk", "MSK"),
issuingCarrier("Mediterranean Shipping Company", "MSC"),
issuingCarrier("Ocean Network Express Pte. Ltd.", "ONE"),
issuingCarrier("Yang Ming Line", "YML"),
issuingCarrier("Zim Israel Navigation Company", "ZIM")
issuingCarrier("CMA CGM", "CMA", "US"),
issuingCarrier("Evergreen Marine Corporation", "EMC", "TW"),
issuingCarrier("Hapag Lloyd", "HLC", "DE"),
issuingCarrier("Hyundai", "HMM", "KR"),
issuingCarrier("Maersk", "MSK", "DK"),
issuingCarrier("Mediterranean Shipping Company", "MSC", "CH"),
issuingCarrier("Ocean Network Express Pte. Ltd.", "ONE", "JP"),
issuingCarrier("Yang Ming Line", "YML", "TW"),
issuingCarrier("Zim Israel Navigation Company", "ZIM", "IL")
};

private static TDField initialFieldValue(String attribute, String value) {
Expand All @@ -129,7 +140,7 @@ private static TDField issuingParty() {
return initialFieldValue("issuingParty", (o, a, v) -> {
int choiceNo = RANDOM.nextInt(ISSUING_CARRIER_DEFINITIONS.length);
var choice = ISSUING_CARRIER_DEFINITIONS[choiceNo];
o.set(a, choice.deepCopy());
o.set(a, choice.apply(v).deepCopy());
});
}

Expand Down Expand Up @@ -349,6 +360,10 @@ public String getStandardsVersion() {
return this.state.required(STD_VERSION_FIELD).asText("");
}

public String getSubscriptionReference() {
return this.state.required(SUBSCRIPTION_REFERENCE).asText("");
}

public String getShippingInstructionsReference() {
return getShippingInstructions().required(SHIPPING_INSTRUCTIONS_REFERENCE).asText();
}
Expand Down Expand Up @@ -710,13 +725,14 @@ public TransportDocumentStatus getTransportDocumentState() {
return TD_START;
}

public static CarrierShippingInstructions initializeFromShippingInstructionsRequest(ObjectNode bookingRequest, String standardsVersion) {
public static CarrierShippingInstructions initializeFromShippingInstructionsRequest(ObjectNode siRequest, String standardsVersion) {
String sir = UUID.randomUUID().toString();
bookingRequest.put(SHIPPING_INSTRUCTIONS_REFERENCE, sir)
siRequest.put(SHIPPING_INSTRUCTIONS_REFERENCE, sir)
.put(SI_STATUS, SI_RECEIVED.wireName());
var state = OBJECT_MAPPER.createObjectNode()
.put(STD_VERSION_FIELD, standardsVersion);
state.set(SI_DATA_FIELD, bookingRequest);
.put(STD_VERSION_FIELD, standardsVersion)
.put(SUBSCRIPTION_REFERENCE, UUID.randomUUID().toString());
state.set(SI_DATA_FIELD, siRequest);
return new CarrierShippingInstructions(state);
}

Expand Down
Loading

0 comments on commit 777bd98

Please sign in to comment.