| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 | package modelimport (	"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()}
 |