This plugin reads the 'connectSpec' ShippedItemInstance V4 JSON and map the properties to the ADM classes, and the related ContextItems:
- Catalog.Products; including the product type and product composition
- Catalog.Brands
- Catalog.Manufacturers
- Catalog.Crops
- Catalog.PackagedProductInstance == > should this be deprecated, and moved to Product?
- Catalog.PackagedProduct ==> should this be deprecated and moved to Products
The above questions relate to the newly developed ADAPT Standard which does not include the PackagedProductInstance and PackagedProduct. Also the PackagedProductInstance should have all the data elements that PackagedProductInstance plus lot identifiers, and quantitative measurements. In
In the process of recommendations for the ADAPT standard should include need for a Measurement class (density is only one measurement) with name, value, unit code and a first-class UID attribute for selection from the pre-populated product list. The view is that Product should really be named ProductInstance, as we are always dealing with real instances of a product that appear in a Catalog, and these instances are packaged. Is the package itself worthy of being modeled as an instance, as while as an example a seed bag may have a tag sewn to it, it is only describing the seed instance inside the bag?
Key properties from the JSON mapped to Product class include:
- Display name (concatenatation of shortened description and Lot Id); this is due to the inability for displays to also show the Brand, Product, LotId and ShipmentId in the product list
- Product Description
- Product TypeCode -- equivalent to ADAPT ProductType enum as per https://github.com/ADAPT/Standard/blob/main/adapt-data-type-definitions.json) -- our initial use case is SEED, but the ShippedItemInstance model has support for substance arrays for fertilizer and crop protection.
- Crop Type based on an agency code list such as USDA (many) or AgGateway; crop type will be of the form related to corn, soybeans, wheat, etc.
The rest of these elements must be mapped to ContextItems which many will be nested:
- GTIN (Global Trade Item Number, as managed by GS1)
- UID (RFID, barcode, etc) -- Used for automatic identification and row selection from a pre-populated product list -- The barcode can identify a bag, seed tender box, or a tank
- Lot Id with typeCode ::= Seed Lot ID or Batch if Crop Protection or Commerical fertilizer
- ShipmentID as generated by the Retailer ERP system
- Retailer GLN (Global Location Number as managed by GS1 and licenced in blocks by AgGateway)
- Carrier Standard Carrier Alpha Code (SCAC) code as managed by the National Motor Freight Traffic Association (NMFTA)
- Shipment Unit (semi-trailer)
- Quantitative Measurement (e.g., seed size)
- Item Composition to handle seed treatment, and nested CAS and EPA registration numbers
It should be noted that the method to populate ProductComposition currently requires the creation of a Product and referencing its id. That unfortunately does NOT work as we do NOT want these substance raw materials to show up in the product list in the display. The Product Composition model in ADAPT needs to be reviewed as a result, as it works for product mixes in the field only.
From the ADM, either the ISOXML.zip (TASKDATA.XML and LINKLIST.XML) or the ADM.zip output can be generated.
A conversation around the weight and gross weight of the package product, such as that of individual bags and a tender box need discussion. ADAPT standard does not provide this, and it needs to be determined if there is value such as automation that could benefit from this
//-----------------------
//PackagedProductInstance
//-----------------------
// The PackagedProductInstance represents a single, unique product shipment line and maps 1:1 to the ShippedItemInstance
// The shipment line is typically a single seed lot or crop protection batch
//
//
PackagedProductInstance packagedProductInstance = new PackagedProductInstance();
//Description and quantity are set on the related class properties
var quantity = (double)shippedItemInstance.Packaging.Quantity?.Content;
packagedProductInstance.ProductQuantity = CreateRepresentationValue(quantity, shippedItemInstance.Quantity.UnitCode);
// this is also in packaged product; why duplicated?
//
var perPackageWeight = (double)shippedItemInstance.Item.Packaging.PerPackageQuantity?.Content;
var perPackageWeightUOM = shippedItemInstance.Item.Packaging.PerPackageQuantity?.UnitCode;
// if this is placed in the tender box the packaging is outside of item
if (shippedItemInstance.Packaging?.TypeCode == "SeedBox"
&& shippedItemInstance.Packaging.Quantity?.Content is not null
&& shippedItemInstance.Packaging.Quantity?.TypeCode == "GrossWeight") {
var seedBoxUID = shippedItemInstance.Packaging?.Id;
var tenderBoxGrossWeight = shippedItemInstance.Packaging.Quantity?.Content;
var tenderBoxWeightUOM = shippedItemInstance.Packaging.Quantity?.UnitCode;
packagedProductInstance.GrossWeight =
CreateRepresentationValue((double)tenderBoxGrossWeight,tenderBoxWeightUOM);
}
else {
// calculate total pounds as gross weight e.g., from example 55 LB/BG * 45 BG
var grossWeightCalculated = perPackageWeight * quantity;
packagedProductInstance.GrossWeight =
CreateRepresentationValue((double)grossWeightCalculated,shippedItemInstance.Packaging.Quantity.UnitCode);
}
packagedProductInstance.Description = shippedItemInstance.Item?.Description;
//The remaining data is somewhat specific to the ShippedItemInstance and is persisted as ContextItems
//The ContextItem data generally is intended to be passed out of the ApplicationDataModel and passed back in unaltered,
//in order that the data may return, e.g., on a logged planting operation and reconcile that planting operation
//back to this ShippedItemInstance.
packagedProductInstance.ContextItems.AddRange(CreatePackagedProductInstanceContextItems(shippedItemInstance));
//-----------------------
// PackagedProduct
//-----------------------
// Packaged product is defined a referenced product within a specific packaging or container
//
// Multiple ShippedItemInstances may map to the same PackagedProduct -- this is not true
//
PackagedProduct packagedProduct = GetPackagedProduct(shippedItemInstance);
//
if (packagedProduct != null)
{
packagedProductInstance.PackagedProductId = packagedProduct.Id.ReferenceId;
}
else
{
Errors.Add(new Error(null, "Mapper.MapShippedItemInstance", $"Couldn't create PackagedProduct for PackageProductInstance {packagedProductInstance.Id.ReferenceId}", null));
}
//Add the PackagedProductInstance to the Catalog. The PackagedProduct is added in the subroutine above.
Catalog.PackagedProductInstances.Add(packagedProductInstance);