This repository has been archived by the owner on Oct 13, 2021. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 273
/
delivery.go
171 lines (144 loc) · 4.99 KB
/
delivery.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package shipping
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()
}