delivery.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package model
  2. import (
  3. "time"
  4. )
  5. // Delivery is the actual transportation of the cargo, as opposed to the
  6. // customer requirement (RouteSpecification) and the plan (Itinerary).
  7. type Delivery struct {
  8. Itinerary Itinerary
  9. RouteSpecification RouteSpecification
  10. RoutingStatus RoutingStatus
  11. TransportStatus TransportStatus
  12. NextExpectedActivity HandlingActivity
  13. LastEvent HandlingEvent
  14. LastKnownLocation UNLocode
  15. CurrentVoyage VoyageNumber
  16. ETA time.Time
  17. IsMisdirected bool
  18. IsUnloadedAtDestination bool
  19. }
  20. // UpdateOnRouting creates a new delivery snapshot to reflect changes in
  21. // routing, i.e. when the route specification or the itinerary has changed but
  22. // no additional handling of the cargo has been performed.
  23. func (d Delivery) UpdateOnRouting(rs RouteSpecification, itinerary Itinerary) Delivery {
  24. return newDelivery(d.LastEvent, itinerary, rs)
  25. }
  26. // IsOnTrack checks if the delivery is on track.
  27. func (d Delivery) IsOnTrack() bool {
  28. return d.RoutingStatus == Routed && !d.IsMisdirected
  29. }
  30. // DeriveDeliveryFrom creates a new delivery snapshot based on the complete
  31. // handling history of a cargo, as well as its route specification and
  32. // itinerary.
  33. func DeriveDeliveryFrom(rs RouteSpecification, itinerary Itinerary, history HandlingHistory) Delivery {
  34. lastEvent, _ := history.MostRecentlyCompletedEvent()
  35. return newDelivery(lastEvent, itinerary, rs)
  36. }
  37. // newDelivery creates a up-to-date delivery based on an handling event,
  38. // itinerary and a route specification.
  39. func newDelivery(lastEvent HandlingEvent, itinerary Itinerary, rs RouteSpecification) Delivery {
  40. var (
  41. routingStatus = calculateRoutingStatus(itinerary, rs)
  42. transportStatus = calculateTransportStatus(lastEvent)
  43. lastKnownLocation = calculateLastKnownLocation(lastEvent)
  44. isMisdirected = calculateMisdirectedStatus(lastEvent, itinerary)
  45. isUnloadedAtDestination = calculateUnloadedAtDestination(lastEvent, rs)
  46. currentVoyage = calculateCurrentVoyage(transportStatus, lastEvent)
  47. )
  48. d := Delivery{
  49. LastEvent: lastEvent,
  50. Itinerary: itinerary,
  51. RouteSpecification: rs,
  52. RoutingStatus: routingStatus,
  53. TransportStatus: transportStatus,
  54. LastKnownLocation: lastKnownLocation,
  55. IsMisdirected: isMisdirected,
  56. IsUnloadedAtDestination: isUnloadedAtDestination,
  57. CurrentVoyage: currentVoyage,
  58. }
  59. d.NextExpectedActivity = calculateNextExpectedActivity(d)
  60. d.ETA = calculateETA(d)
  61. return d
  62. }
  63. // Below are internal functions used when creating a new delivery.
  64. func calculateRoutingStatus(itinerary Itinerary, rs RouteSpecification) RoutingStatus {
  65. if itinerary.Legs == nil {
  66. return NotRouted
  67. }
  68. if rs.IsSatisfiedBy(itinerary) {
  69. return Routed
  70. }
  71. return Misrouted
  72. }
  73. func calculateMisdirectedStatus(event HandlingEvent, itinerary Itinerary) bool {
  74. if event.Activity.Type == NotHandled {
  75. return false
  76. }
  77. return !itinerary.IsExpected(event)
  78. }
  79. func calculateUnloadedAtDestination(event HandlingEvent, rs RouteSpecification) bool {
  80. if event.Activity.Type == NotHandled {
  81. return false
  82. }
  83. return event.Activity.Type == Unload && rs.Destination == event.Activity.Location
  84. }
  85. func calculateTransportStatus(event HandlingEvent) TransportStatus {
  86. switch event.Activity.Type {
  87. case NotHandled:
  88. return NotReceived
  89. case Load:
  90. return OnboardCarrier
  91. case Unload:
  92. return InPort
  93. case Receive:
  94. return InPort
  95. case Customs:
  96. return InPort
  97. case Claim:
  98. return Claimed
  99. }
  100. return Unknown
  101. }
  102. func calculateLastKnownLocation(event HandlingEvent) UNLocode {
  103. return event.Activity.Location
  104. }
  105. func calculateNextExpectedActivity(d Delivery) HandlingActivity {
  106. if !d.IsOnTrack() {
  107. return HandlingActivity{}
  108. }
  109. switch d.LastEvent.Activity.Type {
  110. case NotHandled:
  111. return HandlingActivity{Type: Receive, Location: d.RouteSpecification.Origin}
  112. case Receive:
  113. l := d.Itinerary.Legs[0]
  114. return HandlingActivity{Type: Load, Location: l.LoadLocation, VoyageNumber: l.VoyageNumber}
  115. case Load:
  116. for _, l := range d.Itinerary.Legs {
  117. if l.LoadLocation == d.LastEvent.Activity.Location {
  118. return HandlingActivity{Type: Unload, Location: l.UnloadLocation, VoyageNumber: l.VoyageNumber}
  119. }
  120. }
  121. case Unload:
  122. for i, l := range d.Itinerary.Legs {
  123. if l.UnloadLocation == d.LastEvent.Activity.Location {
  124. if i < len(d.Itinerary.Legs)-1 {
  125. return HandlingActivity{Type: Load, Location: d.Itinerary.Legs[i+1].LoadLocation, VoyageNumber: d.Itinerary.Legs[i+1].VoyageNumber}
  126. }
  127. return HandlingActivity{Type: Claim, Location: l.UnloadLocation}
  128. }
  129. }
  130. }
  131. return HandlingActivity{}
  132. }
  133. func calculateCurrentVoyage(transportStatus TransportStatus, event HandlingEvent) VoyageNumber {
  134. if transportStatus == OnboardCarrier && event.Activity.Type != NotHandled {
  135. return event.Activity.VoyageNumber
  136. }
  137. return VoyageNumber("")
  138. }
  139. func calculateETA(d Delivery) time.Time {
  140. if !d.IsOnTrack() {
  141. return time.Time{}
  142. }
  143. return d.Itinerary.FinalArrivalTime()
  144. }