1// Package cargo contains the heart of the domain model.
2package cargo
3
4import (
5	"errors"
6	"strings"
7	"time"
8
9	"github.com/pborman/uuid"
10
11	"github.com/go-kit/kit/examples/shipping/location"
12)
13
14// TrackingID uniquely identifies a particular cargo.
15type TrackingID string
16
17// Cargo is the central class in the domain model.
18type Cargo struct {
19	TrackingID         TrackingID
20	Origin             location.UNLocode
21	RouteSpecification RouteSpecification
22	Itinerary          Itinerary
23	Delivery           Delivery
24}
25
26// SpecifyNewRoute specifies a new route for this cargo.
27func (c *Cargo) SpecifyNewRoute(rs RouteSpecification) {
28	c.RouteSpecification = rs
29	c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary)
30}
31
32// AssignToRoute attaches a new itinerary to this cargo.
33func (c *Cargo) AssignToRoute(itinerary Itinerary) {
34	c.Itinerary = itinerary
35	c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary)
36}
37
38// DeriveDeliveryProgress updates all aspects of the cargo aggregate status
39// based on the current route specification, itinerary and handling of the cargo.
40func (c *Cargo) DeriveDeliveryProgress(history HandlingHistory) {
41	c.Delivery = DeriveDeliveryFrom(c.RouteSpecification, c.Itinerary, history)
42}
43
44// New creates a new, unrouted cargo.
45func New(id TrackingID, rs RouteSpecification) *Cargo {
46	itinerary := Itinerary{}
47	history := HandlingHistory{make([]HandlingEvent, 0)}
48
49	return &Cargo{
50		TrackingID:         id,
51		Origin:             rs.Origin,
52		RouteSpecification: rs,
53		Delivery:           DeriveDeliveryFrom(rs, itinerary, history),
54	}
55}
56
57// Repository provides access a cargo store.
58type Repository interface {
59	Store(cargo *Cargo) error
60	Find(id TrackingID) (*Cargo, error)
61	FindAll() []*Cargo
62}
63
64// ErrUnknown is used when a cargo could not be found.
65var ErrUnknown = errors.New("unknown cargo")
66
67// NextTrackingID generates a new tracking ID.
68// TODO: Move to infrastructure(?)
69func NextTrackingID() TrackingID {
70	return TrackingID(strings.Split(strings.ToUpper(uuid.New()), "-")[0])
71}
72
73// RouteSpecification Contains information about a route: its origin,
74// destination and arrival deadline.
75type RouteSpecification struct {
76	Origin          location.UNLocode
77	Destination     location.UNLocode
78	ArrivalDeadline time.Time
79}
80
81// IsSatisfiedBy checks whether provided itinerary satisfies this
82// specification.
83func (s RouteSpecification) IsSatisfiedBy(itinerary Itinerary) bool {
84	return itinerary.Legs != nil &&
85		s.Origin == itinerary.InitialDepartureLocation() &&
86		s.Destination == itinerary.FinalArrivalLocation()
87}
88
89// RoutingStatus describes status of cargo routing.
90type RoutingStatus int
91
92// Valid routing statuses.
93const (
94	NotRouted RoutingStatus = iota
95	Misrouted
96	Routed
97)
98
99func (s RoutingStatus) String() string {
100	switch s {
101	case NotRouted:
102		return "Not routed"
103	case Misrouted:
104		return "Misrouted"
105	case Routed:
106		return "Routed"
107	}
108	return ""
109}
110
111// TransportStatus describes status of cargo transportation.
112type TransportStatus int
113
114// Valid transport statuses.
115const (
116	NotReceived TransportStatus = iota
117	InPort
118	OnboardCarrier
119	Claimed
120	Unknown
121)
122
123func (s TransportStatus) String() string {
124	switch s {
125	case NotReceived:
126		return "Not received"
127	case InPort:
128		return "In port"
129	case OnboardCarrier:
130		return "Onboard carrier"
131	case Claimed:
132		return "Claimed"
133	case Unknown:
134		return "Unknown"
135	}
136	return ""
137}
138