A Shipping Order (SO) is issued by the shipping company to a shipper with a confirmed space booking, authorizing the receiving clerk (cargo checker) at the container terminal or dock to receive a specified amount of goods from the named shipper. The document typically contains the space booking number, names and addresses of the shipper and customs broker or forwarder, vessel and voyage number, sailing time, delivery date and location, customs closing date, and number and type of packages.
On Initialize
On Pre Create
This is the moment for hard validation stops on the Shipping Order (SO). This runs after the onSave technically, but before the SO is actually saved in the database. Validation messages here will appear in the SO's hard-stop missing fields validation logic, preventing the SO from saving or progressing.
On Create
The on Create extension point will fire before a Shipping Order is saved The shipping order object will be completely new and all the parameters will be initialized here .
On Save
The onSave extension point will fire whenever a Shipping Order is saved. It is important to note that the shipping order does not save when navigating between pages of the shipping order, only on click of the save button on the 4-Shipping tab.
On Validate
The onValidate extension will run after the onPreCreate extension point. This is the point to raise or suppress Shipping Order System Exceptions; however, you cannot perform any hard stops. See the below explanation on suppressing SO automated exceptions and raising custom exceptions in this extension point.
On Submit
The onSubmit extension point will fire when the ShippingOrder is transitioned from the Draft state to the Submitted state using the submit action.
Events | Roles | Query | Modify | Modify Others |
---|---|---|---|---|
On Initialize | Supplier, Consolidator, Customer | Yes | No | No |
On Pre Create | Supplier, Consolidator, Customer | Yes | No | No |
On Create | Supplier, Consolidator, Customer | Yes | Yes | Yes |
On Save | Supplier, Consolidator, Customer | Yes | Yes | Yes |
On Validate | Supplier, Consolidator, Customer | Yes | No | No |
On Submit | Supplier, Consolidator, Customer | Yes | No | Yes |
Below is an example of the type of logic that can be incorporated into you business workflow using AppXpress's type extensions.
The Business Context
We want our type extension to enforce business rules on our shipping orders. For this example, let's say we have a business rule that states that shipping orders that are shipped over the ocean must have an expected ship time of 3 days or longer. For this example, we will say that it is physically impossible to complete a shipment over the ocean in less than 3 days. A type extension is the perfect place to put our business rule into effect by making it automatically enforced on all of our shipping orders. The important fields of the shipping order we will need to validate to enforce our rule are:
- TransportationMode = Ocean
- The difference between ETDPortOfLoadDate and ETAPlaceOfDeliveryDate must be equal to or greater than 3 days.
The Setup
We want to run our type extension on PreCreate of a shipping order. We will call our type extension function onPreCreateFn. Make sure to enable the type extension for the extension to run on your organization's shipping order documents.
The Code
The javascript snippet of our type extension is below:
function onPreCreateFn(so, event, params) {
if( so.TransportationMode == "Ocean") {
if((!so.ETDPortOfLoadDate) || (!so.ETAPlaceOfDeliveryDate )) {
//The eta or etd on this SO is not set - we will do nothing
//Could show error if your org requires an estimated ETA or
//ETD for ShippingOrders
return;
}
//estimated time of departure
var etd = so.ETDPortOfLoadDate;
//estimated time of arrival
var eta = so.ETAPlaceOfDeliveryDate;
//ensure dates are not less than 3 days apart
var lengthOfTransit = daysInBetween(etd,eta);
if( lengthOfTransit < 3 ) {
Providers.getMessageProvider().error()
.msgKey("For ocean transportation mode, must allow 3 or more days for transit time").build();
}
}
}
function daysInBetween( date1, date2 ) {
//Get 1 day in milliseconds
var one_day=1000*60*60*24;
//alter dates to ensure they are in correct format to be parsed by Mozilla Engine
//Format is YYYY-MM-DDTHH:mm:ss.sssZ
//Dates from GTN system are in this format : YYYY-MM-DDTHH:mm:ssZ
date1 = date1.replace("Z",".000Z");
date2 = date2.replace("Z",".000Z");
date1 = new Date(date1);
date2 = new Date(date2);
// Convert both dates to milliseconds
var date1_ms = date1.getTime();
var date2_ms = date2.getTime();
// Calculate the difference in milliseconds
var difference_ms = date2_ms - date1_ms;
// Convert back to days and return
return Math.round(difference_ms/one_day);
}
The Result
When the estimated time of departure is less than 3 days apart from the estimated time of arrival, this type extension will produce the following screen when you attempt to save a shipping order. It's important to note that even though the onPreCreate extension runs after the onSave extension point, the Shipping Order is not persisted when an error is reported to the user. The error works as a hard stop; forcing the user to go back and fix the cause of the exception before proceeding.

We can raise or suppress system exceptions during the onValidate extension point of a Shipping Order. The primary use case is to allow customization for variance within system exceptions depending on factors related to an SO instance such as customer, port, time of year, etc. For example, a 3PL might want Customer A to be limited to 2 items per invoice with anything greater raising an exception, while all other customers are limited to 5 items per invoice with an exception raised when exceeding that number.
There is a major UI limitation in lowering and raising exceptions, unfortunately. The UI runs the relevant Shipment Order System Exception rules whenever a UI user clicks "Next" to proceed to the next tab. Type Extensions do not run while navigating through tabs. Type extensions can raise or suppress exceptions when the user goes to save the SO and the onValidate extension runs. For this reason, UI users can have hard custom exceptions set in Type Extensions that are more lenient in the system exceptions, but cannot have hard system exceptions that are more strict than the custom exceptions because the system exceptions will block UI users from going from tab-to-tab before the Type Extensions can run.
Set up
The extension setup looks like:

Before we talk about suppressing or raising exceptions, it's important to note where to set up these system exceptions. These exceptions can be set up through an admin account in your organization by going to Shipping Orders under Products and then clicking on Shipping Order System Exception Management. There, you will see all your system exceptions. Your exceptions can be hard , soft, or hidden. Hard exceptions do not allow the user to navigate past the current screen, while soft ones give the user a warning but still allows them to proceed. Hidden exceptions are not reported to the UI. A list of exceptions could look like the following for your org's Shipping Orders:
Suppressing An Exception
In the above screenshot, note the Rule ID column. That is the number we will need in order to suppress or raise a system exception.
Let's say that we want to suppress a soft exception (where Rule ID is 100) when our Shipping Order has a certain port as its destination. We will implement the following code in an onValidate extension to suppress a system exception:
//We will suppress this exception when dealing with a place of delivery
//from this city code
var city_code = "...";
function onValidateExtension(so){
//If the place of delivery matches the city_code, we will suppress
//the system exception with Rule Id 100
if( so.PlaceOfDelivery && so.PlaceOfDelivery.CityCode == city_code ){
Providers.getMessageProvider().suppressRuleId("100");
}
});
In this scenario, the system exception we are suppressing is a soft exception (so only a warning was given when navigating through the tab). When the exception fires, it will look like the following within the Comments History section of the Shipping Order view.
When we suppress the exception using the code above, it will still show up in the same section but you will see a couple of key differences. You will see that it will say Exception Updated in the "Logged Activity" column and it will note that the exception has been Closed in the "Description" column.
Raising An Exception
Type Extension scripts can only raise an automated system exception that has already been configured for a particular customer/shipper in Configuration. The custom script will name the Rule number (ruleId) to raise. Rule IDs vary from environment-to-environment and will change as admins change the System Exceptions configuration. Rule IDs are listed in the System Exception Configuration page.
When setting up your onValidate extension, the AppXpress developer can set the conditions under which to raise an exception by using the Providers.getMessageProvider().error() provider, like:
Providers.getMessageProvider().error()
.msgKey("CustomRaisedSOException.01")
.fieldPath("CargoReadyDate")
.ruleId("521")
.suppressible(true)
.build();
- msgKey() — This allows you to name your exception in order to differentiate it.
- fieldPath() — Name a field in the external format (v310) that the system exception is tracking , i.e. the exception attribute column of the exceptions table.
- ruleId() — The system exception configured for the customer. Note: this number is the instance of the system exception and thus be altered if the exception is deleted and recreated.
- suppressible() — this is optional. If included, it can be set to true and that will give the ability for this rule to be suppressed in the future.
Exceptions can be raised to put stricter restrictions on an attribute depending on different conditions. When a Type Extension with an exception-raising script is run, it will evaluate whether or not it should raise the exception each time it runs. So if the script is in an OnValidate moment, the exception will be evaluated every time the SO goes through the OnValidate moment (i.e. every time it is saved). If the exception had previously been raised and subsequently closed by a user, the exception system recognizes this and will not re-raise the exception. If a user implicitly resolves an exception by fixing the data in the SO and then saves again, the evaluation script will cease to raise the exception and the Exceptions system will implicitly lower the exception.
We will now demonstrate a use case for using the Shipping Orders onSave extension point.
The Business Context
In this example, we introduce a scenario in which type extensions can help increase efficiency across an organizations' operations. Let's say that we have certain Shipping Orders that have certain fields that are always going to be the same. It would be redundant and inefficient for the people who fill out these shipping orders to have to enter the same values into these fields over and over again. Luckily, type extensions can help us out by automatically filling out these fields for us, which can eliminate this unwanted redundant activity. This example is going to run an onSave extension that copies the values from a custom object template over to a shipping order. The following fields are deemed redundant (in this examples business context) : vessel, voyage, ETDPortOfLoading, and origin city. We want to enter these field values into a custom object and then each time you save a shipping order, the field values will populate the correlating shipping order fields.
The Setup
We want to run our script onSave of a shipping order and for this example we are the organization that is the consolidator on the shipping order.

The Code
//The global identifer of our custom object where we will
//retrieve the fields from
var CO_IDENTIFIER = "$TemplateS1";
//UID of Template custom object
//In practice, a type extension would most likely perform
//some sort of query based on logic to fetch a specific object
//However, in this example we will fetch a preset custom object
//by its unique id
var object_uid = "4524685";
//Function to run on save of the shipping order as specified by our type extension
//within the platform console
function onSaveFn(so,event,params){
var myCustomObject = Providers.getPersistenceProvider()
.createFetchRequest(CO_IDENTIFIER,310)
.addParameter("id", object_uid).execute();
if(myCustomObject){
//map fields of your custom object to fields of SO
so.Vessel = myCustomObject.vessel;
so.Voyage = myCustomObject.voyage;
//use helper function to ensure Infor Nexus JS compiler understands date object
so.ETDPortOfLoadDate = gtn_format_date(myCustomObject.soDate);
//OriginCity is an object - if it does not exist on SO, create it
if (!so.OriginCity) {
so.OriginCity = new Object();
so.OriginCity.CityCode = new Object();
}
so.OriginCity.CityCode.value = myCustomObject.origin_city_code;
so.OriginCity.CityCode.Qualifier = myCustomObject.origin_country_code;
}
//Always remember to save your changes!
Providers.getPersistenceProvider().save(so);
}
function gtn_format_date(date){
if(date){
var split = date.split("-");
return new Date(split[0],split[1],split[2]);
}
return null;
}
In our collection of scripts, there is an example of Creating Milestone Data when submitting a Shipping Order.
This models the logical workflow of a Shipping Order including the party roles who can edit and take various actions (state transitions).
State | Edit Roles (org roles of logged in user) | Action | To State | Action Roles (org roles of logged in user) | Notes |
---|---|---|---|---|---|
New | Object Creator | save | Draft | Object Creator | |
Draft | Supplier, 3PL Customer | submit | Submitted | Supplier, 3PL Customer | Generally, this could be either Supplier or Service Provider. However seems like any party who has access to the shipping order would be able to submit the shipping order |
delete | Canceled | Supplier, 3PL Customer | |||
Submitted | Supplier, 3PL Customer | approve | Approved | Supplier, 3PL Customer | |
decline | Declined | Supplier, 3PL Customer | |||
delete | Canceled | Supplier, 3PL Customer | |||
Approved | Supplier, 3PL Customer | submit | Submitted | Supplier, 3PL Customer | |
delete | Canceled | Supplier, 3PL Customer | |||
decline | Declined | Supplier, 3PL Customer | |||
Declined | Supplier, 3PL Customer | submit | Submitted | Supplier, 3PL Customer | |
delete | Canceled | Supplier, 3PL Customer | |||
approve | Approved | Supplier, 3PL Customer | |||
Canceled | [Read Only] |
The following logic is used to determine if a specified query request will be executed or not - if a query cannot be executed due to being unbound, you will receive a 413 - Request Entity too long HTTP response
- Query determined specific. (Precise Search Query Path Type with Equals or IN operator only.)
- Query determined possibly specific. (Constraining Search Query Path Type where current user is the customer or Constraining Search Query Path Type with customer specified.*)
- Query is properly constrained - queries involving dates must be bound or have a date range less than 90. Below is a list of invalid
queries that would receive a 413 error response
- Unbounded Date Range conditions
- LastUpdatedDate > '2017-08-01'
- LastUpdatedDate < '2017-08-01'
- LastUpdatedDate != '2017-08-01'
- LastUpdatedDate NOT IN ('2017-08-01', '2017-08-02')
- Date Range more than 90 days
- LastUpdatedDate In @(Last 92 day)
- LastUpdatedDate BETWEEN '2017-05-01' and '2017-08-04'
- LastUpdatedDate BETWEEN '2017-05-01 12:00:00' and '2017-08-04 12:00:00' (with time part)
- LastUpdatedDate >= '2017-05-01' and LastUpdatedDate <= '2017-08-04'
- LastUpdatedDate >= '2017-05-01 12:00:00' and LastUpdatedDate <= '2017-08-04 12:00:00' (with time part)
- Unbounded Date Range conditions
Query By | Query Path Type |
---|---|
id | Precise |
Status | Precise |
ShippingOrderNumber | Precise |
CommercialInvoiceNumber | Precise |
PurchaseOrderNumber | Precise |
FCR_Number | Precise |
CargoReadyDateTime | Constraining |
LastUpdatedDate | Constraining |
CargoCutoffDateTime | Constraining |
So_Created_Event_Date | Constraining |
So_Submitted_Event_Date | Constraining |
So_Approval_By_Customer_Event_Date | Constraining |
So_Decline_Event_Date | Constraining |
So_Acknowledge_Event_Date | Constraining |
So_Cancelled_Event_Date | Constraining |
* Specifying Customer Organization criteria - If the Rest API user is not a customer user then CustomerOrgId criteria (CustomerOrgId=XXXX - where XXXX is the Unified Organization ID) must be specified in the OQL. If the Rest API user is a customer user then the current user org will be defaulted and any CustomerOrgId param specified will be ignored. For list of possible customers, see User - Fetch List of Customers.
If you are getting errors when querying, here is a description of some common errors while querying.