123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- // Package tracking provides the use-case of tracking a shipping. Used by views
- // facing the end-user.
- package tracking
- import (
- "errors"
- "fmt"
- "strings"
- "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 the basic Track method.
- type Service interface {
- // Track returns a cargo matching a tracking ID.
- Track(id string) (Cargo, error)
- }
- type service struct {
- cargos shipping.CargoRepository
- handlingEvents shipping.HandlingEventRepository
- }
- func (s *service) Track(id string) (Cargo, error) {
- if id == "" {
- return Cargo{}, ErrInvalidArgument
- }
- c, err := s.cargos.Find(shipping.TrackingID(id))
- if err != nil {
- return Cargo{}, err
- }
- return assemble(c, s.handlingEvents), nil
- }
- // NewService returns a new instance of the default Service.
- func NewService(cargos shipping.CargoRepository, events shipping.HandlingEventRepository) Service {
- return &service{
- cargos: cargos,
- handlingEvents: events,
- }
- }
- // Cargo is a read model for tracking views.
- type Cargo struct {
- TrackingID string `json:"tracking_id"`
- StatusText string `json:"status_text"`
- Origin string `json:"origin"`
- Destination string `json:"destination"`
- ETA time.Time `json:"eta"`
- NextExpectedActivity string `json:"next_expected_activity"`
- ArrivalDeadline time.Time `json:"arrival_deadline"`
- Events []Event `json:"events"`
- }
- // Leg is a read model for booking views.
- type Leg struct {
- VoyageNumber string `json:"voyage_number"`
- From string `json:"from"`
- To string `json:"to"`
- LoadTime time.Time `json:"load_time"`
- UnloadTime time.Time `json:"unload_time"`
- }
- // Event is a read model for tracking views.
- type Event struct {
- Description string `json:"description"`
- Expected bool `json:"expected"`
- }
- func assemble(c *shipping.Cargo, events shipping.HandlingEventRepository) Cargo {
- return Cargo{
- TrackingID: string(c.TrackingID),
- Origin: string(c.Origin),
- Destination: string(c.RouteSpecification.Destination),
- ETA: c.Delivery.ETA,
- NextExpectedActivity: nextExpectedActivity(c),
- ArrivalDeadline: c.RouteSpecification.ArrivalDeadline,
- StatusText: assembleStatusText(c),
- Events: assembleEvents(c, events),
- }
- }
- func assembleLegs(c shipping.Cargo) []Leg {
- var legs []Leg
- for _, l := range c.Itinerary.Legs {
- legs = append(legs, Leg{
- VoyageNumber: string(l.VoyageNumber),
- From: string(l.LoadLocation),
- To: string(l.UnloadLocation),
- LoadTime: l.LoadTime,
- UnloadTime: l.UnloadTime,
- })
- }
- return legs
- }
- func nextExpectedActivity(c *shipping.Cargo) string {
- a := c.Delivery.NextExpectedActivity
- prefix := "Next expected activity is to"
- switch a.Type {
- case shipping.Load:
- return fmt.Sprintf("%s %s cargo onto voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location)
- case shipping.Unload:
- return fmt.Sprintf("%s %s cargo off of voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location)
- case shipping.NotHandled:
- return "There are currently no expected activities for this shipping."
- }
- return fmt.Sprintf("%s %s cargo in %s.", prefix, strings.ToLower(a.Type.String()), a.Location)
- }
- func assembleStatusText(c *shipping.Cargo) string {
- switch c.Delivery.TransportStatus {
- case shipping.NotReceived:
- return "Not received"
- case shipping.InPort:
- return fmt.Sprintf("In port %s", c.Delivery.LastKnownLocation)
- case shipping.OnboardCarrier:
- return fmt.Sprintf("Onboard voyage %s", c.Delivery.CurrentVoyage)
- case shipping.Claimed:
- return "Claimed"
- default:
- return "Unknown"
- }
- }
- func assembleEvents(c *shipping.Cargo, handlingEvents shipping.HandlingEventRepository) []Event {
- h := handlingEvents.QueryHandlingHistory(c.TrackingID)
- var events []Event
- for _, e := range h.HandlingEvents {
- var description string
- switch e.Activity.Type {
- case shipping.NotHandled:
- description = "Cargo has not yet been received."
- case shipping.Receive:
- description = fmt.Sprintf("Received in %s, at %s", e.Activity.Location, time.Now().Format(time.RFC3339))
- case shipping.Load:
- description = fmt.Sprintf("Loaded onto voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339))
- case shipping.Unload:
- description = fmt.Sprintf("Unloaded off voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339))
- case shipping.Claim:
- description = fmt.Sprintf("Claimed in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339))
- case shipping.Customs:
- description = fmt.Sprintf("Cleared customs in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339))
- default:
- description = "[Unknown status]"
- }
- events = append(events, Event{
- Description: description,
- Expected: c.Itinerary.IsExpected(e),
- })
- }
- return events
- }
|