// Package booking provides the use-case of booking a shipping. Used by views // facing an administrator. package booking import ( "errors" "time" shipping "github.com/longjoy/micro-go-course/section19/cargo/model" ) // ErrInvalidArgument is returned when one or more arguments are invalid. var ErrInvalidArgument = errors.New("invalid argument") // Service is the interface that provides booking methods. type Service interface { // BookNewCargo registers a new cargo in the tracking system, not yet // routed. BookNewCargo(origin shipping.UNLocode, destination shipping.UNLocode, deadline time.Time) (shipping.TrackingID, error) // LoadCargo returns a read model of a shipping. LoadCargo(id shipping.TrackingID) (Cargo, error) // RequestPossibleRoutesForCargo requests a list of itineraries describing // possible routes for this shipping. RequestPossibleRoutesForCargo(id shipping.TrackingID) []shipping.Itinerary // AssignCargoToRoute assigns a cargo to the route specified by the // itinerary. AssignCargoToRoute(id shipping.TrackingID, itinerary shipping.Itinerary) (bool, error) // ChangeDestination changes the destination of a shipping. ChangeDestination(id shipping.TrackingID, destination shipping.UNLocode) (bool, error) // Cargos returns a list of all cargos that have been booked. Cargos() []Cargo // Locations returns a list of registered locations. Locations() []Location } type service struct { cargos shipping.CargoRepository locations shipping.LocationRepository handlingEvents shipping.HandlingEventRepository routingService shipping.RoutingService } func (s *service) AssignCargoToRoute(id shipping.TrackingID, itinerary shipping.Itinerary) (bool, error) { if id == "" || len(itinerary.Legs) == 0 { return false, ErrInvalidArgument } c, err := s.cargos.Find(id) if err != nil { return false, err } c.AssignToRoute(itinerary) return s.cargos.Store(c) } func (s *service) BookNewCargo(origin, destination shipping.UNLocode, deadline time.Time) (shipping.TrackingID, error) { if origin == "" || destination == "" || deadline.IsZero() { return "", ErrInvalidArgument } id := shipping.NextTrackingID() rs := shipping.RouteSpecification{ Origin: origin, Destination: destination, ArrivalDeadline: deadline, } c := shipping.NewCargo(id, rs) if _, err := s.cargos.Store(c); err != nil { return "", err } return c.TrackingID, nil } func (s *service) LoadCargo(id shipping.TrackingID) (Cargo, error) { if id == "" { return Cargo{}, ErrInvalidArgument } c, err := s.cargos.Find(id) if err != nil { return Cargo{}, err } return assemble(c, s.handlingEvents), nil } func (s *service) ChangeDestination(id shipping.TrackingID, destination shipping.UNLocode) (bool, error) { if id == "" || destination == "" { return false, ErrInvalidArgument } c, err := s.cargos.Find(id) if err != nil { return false, err } l, err := s.locations.Find(destination) if err != nil { return false, err } c.SpecifyNewRoute(shipping.RouteSpecification{ Origin: c.Origin, Destination: l.UNLocode, ArrivalDeadline: c.RouteSpecification.ArrivalDeadline, }) if _, err := s.cargos.Store(c); err != nil { return false, err } return true, nil } func (s *service) RequestPossibleRoutesForCargo(id shipping.TrackingID) []shipping.Itinerary { if id == "" { return nil } c, err := s.cargos.Find(id) if err != nil { return []shipping.Itinerary{} } return s.routingService.FetchRoutesForSpecification(c.RouteSpecification) } func (s *service) Cargos() []Cargo { var result []Cargo for _, c := range s.cargos.FindAll() { result = append(result, assemble(c, s.handlingEvents)) } return result } func (s *service) Locations() []Location { var result []Location for _, v := range s.locations.FindAll() { result = append(result, Location{ UNLocode: string(v.UNLocode), Name: v.Name, }) } return result } // NewService creates a booking service with necessary dependencies. func NewService(cargos shipping.CargoRepository, locations shipping.LocationRepository, events shipping.HandlingEventRepository) Service { return &service{ cargos: cargos, locations: locations, handlingEvents: events, } } // Location is a read model for booking views. type Location struct { UNLocode string `json:"locode"` Name string `json:"name"` } // Cargo is a read model for booking views. type Cargo struct { ArrivalDeadline time.Time `json:"arrival_deadline"` Destination string `json:"destination"` Legs []shipping.Leg `json:"legs,omitempty"` Misrouted bool `json:"misrouted"` Origin string `json:"origin"` Routed bool `json:"routed"` TrackingID string `json:"tracking_id"` } func assemble(c *shipping.Cargo, events shipping.HandlingEventRepository) Cargo { return Cargo{ TrackingID: string(c.TrackingID), Origin: string(c.Origin), Destination: string(c.RouteSpecification.Destination), Misrouted: c.Delivery.RoutingStatus == shipping.Misrouted, Routed: !c.Itinerary.IsEmpty(), ArrivalDeadline: c.RouteSpecification.ArrivalDeadline, Legs: c.Itinerary.Legs, } }