Skip to content

Instantly share code, notes, and snippets.

@wmathes
Created August 21, 2017 07:45
Show Gist options
  • Save wmathes/fb6be6b880043db73c2c664a3586e67c to your computer and use it in GitHub Desktop.
Save wmathes/fb6be6b880043db73c2c664a3586e67c to your computer and use it in GitHub Desktop.
Shipping route changes

Dependencies

1) HarmonizedSystem (HS) Codes

It's used in almost all systems, we should have it as a php 5.3 package. The AasTariff-class from Shop is already quite good:

  • it should return a class instance though (HsCode-instance, immutable and lazily constructed)
  • add getDefault()-method.
  • Change fromMaterial() to return 'null' instead

2) AsendiaClient

Request bundling

The Asendia Client needs to learn a few new tricks. As we have learned: We can bundle up to 100 requests at a time.

We should definitely make use of it, as it will reduce our request times substantially.

  • AsendiaClient will not offer direct methods for making requests anymore.
  • Instead all requests will be instantiated and either passed into AsendiaClient::call() as an array or via ->call() on the object itself.
  • Afterwards calling getResponse() on any request will return the associated response for that request.

Supported Requests

We will need to implement at least the following commands:

  • ParcelDataRequest/Response
  • CustomsDataOutboundRequest/Response
  • DispatchListRequest/Response
  • DeleteParcelRequest/Response

Routing

1) Managing routes

We'll have lots of routes, each of them represented by a class. Every route that behaves differently will be its own class.

We should do this for remaining old routes as well as for the new ones.

For instance there would be 6 different classes for routes to Belgium and other countries with special treatment

  • Belgium LV THIN
  • Belgium LV THICK
  • Belgium HV THIN
  • Belgium HV THICK
  • Belgium HVT THIN
  • Belgium HVT THICK

And a total of 6 classes for other EU countries:

  • LV_EU_OTHER_THIN
  • LV_EU_OTHER_THICK
  • HV_EU_OTHER_THIN
  • HV_EU_OTHER_THICK
  • HVT_EU_OTHER_THIN
  • HVT_EU_OTHER_THICK

And a total of 2 classes for the rest of the world:

  • LV_OTHER_THIN
  • LV_OTHER_THICK

etc.

Every class will be pretty much static and won't offer that much logic. They will all implement the same interface though to be compatible at all times. They will react to different kind of hooks throughout the process.

Each class can be tested quickly.

2) Determine the route to be used.

When a delivery note has no route selected yet, it (or a part of it) will be passed into a SHIPPING_ROUTE_RESOLVER-service implementing RouteResolverInterface.

interface RouteResolverInterface {
    resolve(string $countryCode, float $euroValue, string thickness = THICKNESS_THIN): RouteInterface;
    get(string $routeName): ?RouteInterface;
}

It will ALWAYS return a class implementing RouteInterface, which can be uniquely identitified via getName(). This is can be tested easily as well.

This will also allow us to add special rules when switching to the new system. For example we could resolve the new route only for up to 50 parcels every day for each route. Reducing the negative effects in case of failure.

/*
 * Note: Instances of this class are pretty much immutable!
 */
interface RouteInterface {
    // Essentials
    getName(): string; // can be derived from ::class
    isCompatible(string $countryCode, float $euroValue, string thickness = THICKNESS_THIN): bool;
    
    // TBD
    requiresCN22(): bool;
    requiresPrintedInvoice(): bool;
    requiresWeight(): bool;
    requiresAsendiaSync(): bool;
    getPackageName(): name;
    getSortingIcons(): string[];
    getSortingBin(): string;
    getRouteData(): array; // ???
    // ...
}

The resulting route would then be assigned to the delivery note.

3) Working with the route

The Route object will required again and again throughout the shipping process. So whenever required the original route class can be retrieved via its originally retrieved identifier from getName(), by calling get() on the shipping route resolver service.

The route should contain all required flags for the systems to work and may also be used for stateless logic. Routes not using certain features should be generally compatible with them as defined via the interface.

Example: Not all deliveries require a CN22 label. So they should return false in requiresCn22Label() to skip the process, but they should not fail.

3.1) Syncing data

For the new SwissPost route we need to print a lable containing their barcode, which we'll receive via the AsendiaClient. The request however is too slow, forcing us to take a different approach:

The DeliveryNotes table will be expanded to include the sync state with the external service.

Getting a unique barcode/id

When transfering DeliveryNotes from the CollectionCenter to the QA, all routes requiring asendiaSyncOnce will issue a ParcelDataRequest. The response will contain a unique barcode, which will be saved. It will be used in later requests with the Service and also rendered onto the label in the QA.

The initial data being sent is a "best guess"-estimate of the parcel data and the exact data sent, will be saved in an additional table.

Update parcel data (UpdateAsendiaParcelDataTask)

If the data changes during QA (for instance the thickness, a partially/fully missing item, etc.), it will be marked as unsynced/dirty again. Same is true for a complete cancellation.

A cronjob will regularly pick up all unsynced parcels and update/delete these via the AsendiaClient.

Also: For some reason i don't understand (yet) we have to send the customs data for each parcel individually AFTER creating it and BEFORE finalizing the package.

This is a separate request, but could possibly be handled by the same cronjob (maybe as a 2nd task)

Packages

Apart from our "old" LV packages, we may have different processed/checks when closing packages.

Grouping

Grouping of items is generally handled via the Routes as they can specify a package group and sorting symbols.

We'll have A LOT more groups to sort though for the new route:

  • 4 final package types (A,B,C,D)

Every pile will be separated within the Package and has its own weight.

Package Type A

<22eu, LV_EU_, LV_EU_OTHER, LV_USA, LV_CANADA, LV_OTHER

  • 28 piles: 14 special EU countries * 2 formats (thick & thin)
  • 2 piles: 14 other EU countries * 2 formats (thick & thin)
  • 6 piles: USA/Canada/Other * 2 formats

Package Type B

  • 2 piles: Switzerland * 2 formats

Package Type C

22eu-200eu, HV_EU

  • 26 piles: 13 special EU countries * 2 formats (thick & thin)
  • 2 piles: 15 other EU countries * 2 formats (thick & thin)

Package Type D

200eu, HVT_EU_ HVT_EU_OTHER

  • 26 piles: 13 special EU countries * 2 formats (thick & thin)
  • 2 piles: 15 other EU countries * 2 formats (thick & thin)

This totals in 94 piles if i'm not mistaken. OUCH!

Package Type A is pretty much what we have already. B, C and D will be part of the new route.

Finishing ackages with synced parcels

Finishing a package is generally IMPOSSIBLE if there are related packages marked as unsynced. When closing a package we need to be absolutely sure, that we only mark synced items as dispatched, as it is impossible to update them afterwards in case of an error!

The DispatchListRequest offers many different interactions, but will only accept up to 100 IDs at the same time. I hope this is wrong, but it might be intentional...

We'll have to find out how to handle this properly as the alternatives are foggy at best. (Like dispatch all within a time range..., which may include unsynced items...)

Documents

AsendiaParcelLabel

The new route will require labels with a very specific format:

  • 148mm wide
  • the top 40mm are equally split into Sender (left) and Postage data (right).
  • the lower area is for the Recipient's data and has a margin of at least 15mm (right, bottom and left, bottom)

We can print minor remarks and icons into the lower area's bottom margin:

  • right bottom will have our sorting codes
  • left bottom will have our Order number (and maybe a tiny barcode)

The Stickers we currently use are 100mm wide. To keep the old route working we intend to reuse these for the foreseeable future. The solution is to print the parcel label on two stickers (right-aligned). They will be attached next to each other by the worker.

This will leave an area of roughly 5cm unused on the left side of the first label. We will use this area to render our CN22 label if required.

In the future we may change the label format, but for now this should be ok.

Invoices

For the new route we need to add a printed invoice for each parcel. This is different from the printed delivery note we add for some routes as it will contain more details informations and will have a slightly different layout. They will also show toll and tax information, which again requires the route to be determined as countries have different regulations for this.

These will added in the CollectionCenter after the route has been determined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment