123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- package model
- import (
- "time"
- )
- // Delivery is the actual transportation of the cargo, as opposed to the
- // customer requirement (RouteSpecification) and the plan (Itinerary).
- type Delivery struct {
- Itinerary Itinerary
- RouteSpecification RouteSpecification
- RoutingStatus RoutingStatus
- TransportStatus TransportStatus
- NextExpectedActivity HandlingActivity
- LastEvent HandlingEvent
- LastKnownLocation UNLocode
- CurrentVoyage VoyageNumber
- ETA time.Time
- IsMisdirected bool
- IsUnloadedAtDestination bool
- }
- // UpdateOnRouting creates a new delivery snapshot to reflect changes in
- // routing, i.e. when the route specification or the itinerary has changed but
- // no additional handling of the cargo has been performed.
- func (d Delivery) UpdateOnRouting(rs RouteSpecification, itinerary Itinerary) Delivery {
- return newDelivery(d.LastEvent, itinerary, rs)
- }
- // IsOnTrack checks if the delivery is on track.
- func (d Delivery) IsOnTrack() bool {
- return d.RoutingStatus == Routed && !d.IsMisdirected
- }
- // DeriveDeliveryFrom creates a new delivery snapshot based on the complete
- // handling history of a cargo, as well as its route specification and
- // itinerary.
- func DeriveDeliveryFrom(rs RouteSpecification, itinerary Itinerary, history HandlingHistory) Delivery {
- lastEvent, _ := history.MostRecentlyCompletedEvent()
- return newDelivery(lastEvent, itinerary, rs)
- }
- // newDelivery creates a up-to-date delivery based on an handling event,
- // itinerary and a route specification.
- func newDelivery(lastEvent HandlingEvent, itinerary Itinerary, rs RouteSpecification) Delivery {
- var (
- routingStatus = calculateRoutingStatus(itinerary, rs)
- transportStatus = calculateTransportStatus(lastEvent)
- lastKnownLocation = calculateLastKnownLocation(lastEvent)
- isMisdirected = calculateMisdirectedStatus(lastEvent, itinerary)
- isUnloadedAtDestination = calculateUnloadedAtDestination(lastEvent, rs)
- currentVoyage = calculateCurrentVoyage(transportStatus, lastEvent)
- )
- d := Delivery{
- LastEvent: lastEvent,
- Itinerary: itinerary,
- RouteSpecification: rs,
- RoutingStatus: routingStatus,
- TransportStatus: transportStatus,
- LastKnownLocation: lastKnownLocation,
- IsMisdirected: isMisdirected,
- IsUnloadedAtDestination: isUnloadedAtDestination,
- CurrentVoyage: currentVoyage,
- }
- d.NextExpectedActivity = calculateNextExpectedActivity(d)
- d.ETA = calculateETA(d)
- return d
- }
- // Below are internal functions used when creating a new delivery.
- func calculateRoutingStatus(itinerary Itinerary, rs RouteSpecification) RoutingStatus {
- if itinerary.Legs == nil {
- return NotRouted
- }
- if rs.IsSatisfiedBy(itinerary) {
- return Routed
- }
- return Misrouted
- }
- func calculateMisdirectedStatus(event HandlingEvent, itinerary Itinerary) bool {
- if event.Activity.Type == NotHandled {
- return false
- }
- return !itinerary.IsExpected(event)
- }
- func calculateUnloadedAtDestination(event HandlingEvent, rs RouteSpecification) bool {
- if event.Activity.Type == NotHandled {
- return false
- }
- return event.Activity.Type == Unload && rs.Destination == event.Activity.Location
- }
- func calculateTransportStatus(event HandlingEvent) TransportStatus {
- switch event.Activity.Type {
- case NotHandled:
- return NotReceived
- case Load:
- return OnboardCarrier
- case Unload:
- return InPort
- case Receive:
- return InPort
- case Customs:
- return InPort
- case Claim:
- return Claimed
- }
- return Unknown
- }
- func calculateLastKnownLocation(event HandlingEvent) UNLocode {
- return event.Activity.Location
- }
- func calculateNextExpectedActivity(d Delivery) HandlingActivity {
- if !d.IsOnTrack() {
- return HandlingActivity{}
- }
- switch d.LastEvent.Activity.Type {
- case NotHandled:
- return HandlingActivity{Type: Receive, Location: d.RouteSpecification.Origin}
- case Receive:
- l := d.Itinerary.Legs[0]
- return HandlingActivity{Type: Load, Location: l.LoadLocation, VoyageNumber: l.VoyageNumber}
- case Load:
- for _, l := range d.Itinerary.Legs {
- if l.LoadLocation == d.LastEvent.Activity.Location {
- return HandlingActivity{Type: Unload, Location: l.UnloadLocation, VoyageNumber: l.VoyageNumber}
- }
- }
- case Unload:
- for i, l := range d.Itinerary.Legs {
- if l.UnloadLocation == d.LastEvent.Activity.Location {
- if i < len(d.Itinerary.Legs)-1 {
- return HandlingActivity{Type: Load, Location: d.Itinerary.Legs[i+1].LoadLocation, VoyageNumber: d.Itinerary.Legs[i+1].VoyageNumber}
- }
- return HandlingActivity{Type: Claim, Location: l.UnloadLocation}
- }
- }
- }
- return HandlingActivity{}
- }
- func calculateCurrentVoyage(transportStatus TransportStatus, event HandlingEvent) VoyageNumber {
- if transportStatus == OnboardCarrier && event.Activity.Type != NotHandled {
- return event.Activity.VoyageNumber
- }
- return VoyageNumber("")
- }
- func calculateETA(d Delivery) time.Time {
- if !d.IsOnTrack() {
- return time.Time{}
- }
- return d.Itinerary.FinalArrivalTime()
- }
|