| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 | // Package booking provides the use-case of booking a shipping. Used by views// facing an administrator.package bookingimport (	"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,	}}
 |