1 // Package tracking provides the use-case of tracking a cargo. Used by views
2 // facing the end-user.
11 "github.com/go-kit/kit/examples/shipping/cargo"
14 // ErrInvalidArgument is returned when one or more arguments are invalid.
15 var ErrInvalidArgument = errors.New("invalid argument")
17 // Service is the interface that provides the basic Track method.
18 type Service interface {
19 // Track returns a cargo matching a tracking ID.
20 Track(id string) (Cargo, error)
24 cargos cargo.Repository
25 handlingEvents cargo.HandlingEventRepository
28 func (s *service) Track(id string) (Cargo, error) {
30 return Cargo{}, ErrInvalidArgument
32 c, err := s.cargos.Find(cargo.TrackingID(id))
36 return assemble(c, s.handlingEvents), nil
39 // NewService returns a new instance of the default Service.
40 func NewService(cargos cargo.Repository, events cargo.HandlingEventRepository) Service {
43 handlingEvents: events,
47 // Cargo is a read model for tracking views.
49 TrackingID string `json:"tracking_id"`
50 StatusText string `json:"status_text"`
51 Origin string `json:"origin"`
52 Destination string `json:"destination"`
53 ETA time.Time `json:"eta"`
54 NextExpectedActivity string `json:"next_expected_activity"`
55 ArrivalDeadline time.Time `json:"arrival_deadline"`
56 Events []Event `json:"events"`
59 // Leg is a read model for booking views.
61 VoyageNumber string `json:"voyage_number"`
62 From string `json:"from"`
64 LoadTime time.Time `json:"load_time"`
65 UnloadTime time.Time `json:"unload_time"`
68 // Event is a read model for tracking views.
70 Description string `json:"description"`
71 Expected bool `json:"expected"`
74 func assemble(c *cargo.Cargo, events cargo.HandlingEventRepository) Cargo {
76 TrackingID: string(c.TrackingID),
77 Origin: string(c.Origin),
78 Destination: string(c.RouteSpecification.Destination),
80 NextExpectedActivity: nextExpectedActivity(c),
81 ArrivalDeadline: c.RouteSpecification.ArrivalDeadline,
82 StatusText: assembleStatusText(c),
83 Events: assembleEvents(c, events),
87 func assembleLegs(c cargo.Cargo) []Leg {
89 for _, l := range c.Itinerary.Legs {
90 legs = append(legs, Leg{
91 VoyageNumber: string(l.VoyageNumber),
92 From: string(l.LoadLocation),
93 To: string(l.UnloadLocation),
95 UnloadTime: l.UnloadTime,
101 func nextExpectedActivity(c *cargo.Cargo) string {
102 a := c.Delivery.NextExpectedActivity
103 prefix := "Next expected activity is to"
107 return fmt.Sprintf("%s %s cargo onto voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location)
109 return fmt.Sprintf("%s %s cargo off of voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location)
110 case cargo.NotHandled:
111 return "There are currently no expected activities for this cargo."
114 return fmt.Sprintf("%s %s cargo in %s.", prefix, strings.ToLower(a.Type.String()), a.Location)
117 func assembleStatusText(c *cargo.Cargo) string {
118 switch c.Delivery.TransportStatus {
119 case cargo.NotReceived:
120 return "Not received"
122 return fmt.Sprintf("In port %s", c.Delivery.LastKnownLocation)
123 case cargo.OnboardCarrier:
124 return fmt.Sprintf("Onboard voyage %s", c.Delivery.CurrentVoyage)
132 func assembleEvents(c *cargo.Cargo, handlingEvents cargo.HandlingEventRepository) []Event {
133 h := handlingEvents.QueryHandlingHistory(c.TrackingID)
136 for _, e := range h.HandlingEvents {
137 var description string
139 switch e.Activity.Type {
140 case cargo.NotHandled:
141 description = "Cargo has not yet been received."
143 description = fmt.Sprintf("Received in %s, at %s", e.Activity.Location, time.Now().Format(time.RFC3339))
145 description = fmt.Sprintf("Loaded onto voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339))
147 description = fmt.Sprintf("Unloaded off voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339))
149 description = fmt.Sprintf("Claimed in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339))
151 description = fmt.Sprintf("Cleared customs in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339))
153 description = "[Unknown status]"
156 events = append(events, Event{
157 Description: description,
158 Expected: c.Itinerary.IsExpected(e),