@fleetbase/fleetops-engine 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/.gitattributes +14 -0
  2. package/.php-cs-fixer.cache +1 -0
  3. package/.php-cs-fixer.php +29 -0
  4. package/LICENSE.md +16 -4
  5. package/README.md +103 -13
  6. package/composer.json +85 -0
  7. package/extension.json +10 -0
  8. package/package.json +3 -3
  9. package/phpstan.neon.dist +8 -0
  10. package/phpunit.xml.dist +16 -0
  11. package/server/config/api.php +765 -0
  12. package/server/config/cache.stores.php +18 -0
  13. package/server/config/dompdf.php +242 -0
  14. package/server/config/fleetops.php +19 -0
  15. package/server/config/geocoder.php +108 -0
  16. package/server/data/globe.json +261 -0
  17. package/server/migrations/2023_04_27_053456_create_contacts_table.php +49 -0
  18. package/server/migrations/2023_04_27_053456_create_drivers_table.php +62 -0
  19. package/server/migrations/2023_04_27_053456_create_entities_table.php +67 -0
  20. package/server/migrations/2023_04_27_053456_create_fleet_drivers_table.php +39 -0
  21. package/server/migrations/2023_04_27_053456_create_fleets_table.php +47 -0
  22. package/server/migrations/2023_04_27_053456_create_fuel_reports_table.php +50 -0
  23. package/server/migrations/2023_04_27_053456_create_integrated_vendors_table.php +44 -0
  24. package/server/migrations/2023_04_27_053456_create_issues_table.php +53 -0
  25. package/server/migrations/2023_04_27_053456_create_orders_table.php +70 -0
  26. package/server/migrations/2023_04_27_053456_create_payloads_table.php +51 -0
  27. package/server/migrations/2023_04_27_053456_create_places_table.php +62 -0
  28. package/server/migrations/2023_04_27_053456_create_positions_table.php +46 -0
  29. package/server/migrations/2023_04_27_053456_create_proofs_table.php +44 -0
  30. package/server/migrations/2023_04_27_053456_create_purchase_rates_table.php +46 -0
  31. package/server/migrations/2023_04_27_053456_create_routes_table.php +40 -0
  32. package/server/migrations/2023_04_27_053456_create_service_areas_table.php +47 -0
  33. package/server/migrations/2023_04_27_053456_create_service_quote_items_table.php +42 -0
  34. package/server/migrations/2023_04_27_053456_create_service_quotes_table.php +47 -0
  35. package/server/migrations/2023_04_27_053456_create_service_rate_fees_table.php +45 -0
  36. package/server/migrations/2023_04_27_053456_create_service_rate_parcel_fees_table.php +47 -0
  37. package/server/migrations/2023_04_27_053456_create_service_rates_table.php +61 -0
  38. package/server/migrations/2023_04_27_053456_create_tracking_numbers_table.php +46 -0
  39. package/server/migrations/2023_04_27_053456_create_tracking_statuses_table.php +54 -0
  40. package/server/migrations/2023_04_27_053456_create_vehicles_table.php +84 -0
  41. package/server/migrations/2023_04_27_053456_create_vendors_table.php +56 -0
  42. package/server/migrations/2023_04_27_053456_create_waypoints_table.php +45 -0
  43. package/server/migrations/2023_04_27_053456_create_zones_table.php +46 -0
  44. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_companies_table.php +32 -0
  45. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_contacts_table.php +34 -0
  46. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_drivers_table.php +40 -0
  47. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_entities_table.php +40 -0
  48. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_fleet_drivers_table.php +34 -0
  49. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_fleets_table.php +36 -0
  50. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_fuel_reports_table.php +36 -0
  51. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_integrated_vendors_table.php +34 -0
  52. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_issues_table.php +38 -0
  53. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_orders_table.php +48 -0
  54. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_payloads_table.php +40 -0
  55. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_places_table.php +32 -0
  56. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_positions_table.php +36 -0
  57. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_proofs_table.php +34 -0
  58. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_purchase_rates_table.php +38 -0
  59. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_routes_table.php +34 -0
  60. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_areas_table.php +34 -0
  61. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_quote_items_table.php +32 -0
  62. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_quotes_table.php +38 -0
  63. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_rate_fees_table.php +32 -0
  64. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_rate_parcel_fees_table.php +32 -0
  65. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_rates_table.php +36 -0
  66. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_tracking_numbers_table.php +34 -0
  67. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_tracking_statuses_table.php +34 -0
  68. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_vehicles_table.php +36 -0
  69. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_vendors_table.php +40 -0
  70. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_waypoints_table.php +38 -0
  71. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_zones_table.php +34 -0
  72. package/server/migrations/2023_09_14_074512_update_vehicles_table.php +100 -0
  73. package/server/migrations/2023_09_14_074619_create_vehicle_devices_table.php +44 -0
  74. package/server/migrations/2023_09_26_071205_add_online_column_to_vehicles_table.php +32 -0
  75. package/server/migrations/2023_09_26_083957_create_fleet_vehicles_table.php +45 -0
  76. package/server/migrations/2023_09_27_023757_add_column_to_fleets_table.php +46 -0
  77. package/server/migrations/2023_10_05_074811_update_vehicle.php +42 -0
  78. package/server/resources/views/labels/default.php +165 -0
  79. package/server/src/Auth/Schemas/FleetOps.php +75 -0
  80. package/server/src/Casts/MultiPolygon.php +50 -0
  81. package/server/src/Casts/Point.php +109 -0
  82. package/server/src/Casts/Polygon.php +46 -0
  83. package/server/src/Console/Commands/DispatchAdhocOrders.php +137 -0
  84. package/server/src/Console/Commands/DispatchOrders.php +71 -0
  85. package/server/src/Console/Commands/TrackOrderDistanceAndTime.php +62 -0
  86. package/server/src/Events/DriverLocationChanged.php +174 -0
  87. package/server/src/Events/DriverSimulatedLocationChanged.php +183 -0
  88. package/server/src/Events/EntityDriverAssigned.php +36 -0
  89. package/server/src/Events/OrderCanceled.php +15 -0
  90. package/server/src/Events/OrderCompleted.php +15 -0
  91. package/server/src/Events/OrderDispatchFailed.php +42 -0
  92. package/server/src/Events/OrderDispatched.php +15 -0
  93. package/server/src/Events/OrderDriverAssigned.php +15 -0
  94. package/server/src/Events/OrderReady.php +15 -0
  95. package/server/src/Events/OrderStarted.php +15 -0
  96. package/server/src/Exceptions/IntegratedVendorException.php +18 -0
  97. package/server/src/Expansions/UserExpansion.php +63 -0
  98. package/server/src/Exports/ContactExport.php +56 -0
  99. package/server/src/Exports/DriverExport.php +61 -0
  100. package/server/src/Exports/FleetExport.php +53 -0
  101. package/server/src/Exports/PlaceExport.php +55 -0
  102. package/server/src/Exports/ServiceAreaExport.php +58 -0
  103. package/server/src/Exports/VehicleExport.php +57 -0
  104. package/server/src/Exports/VendorExport.php +57 -0
  105. package/server/src/Http/Controllers/Api/v1/ContactController.php +133 -0
  106. package/server/src/Http/Controllers/Api/v1/DriverController.php +691 -0
  107. package/server/src/Http/Controllers/Api/v1/EntityController.php +230 -0
  108. package/server/src/Http/Controllers/Api/v1/FleetController.php +146 -0
  109. package/server/src/Http/Controllers/Api/v1/LabelController.php +63 -0
  110. package/server/src/Http/Controllers/Api/v1/OrderController.php +1114 -0
  111. package/server/src/Http/Controllers/Api/v1/PayloadController.php +195 -0
  112. package/server/src/Http/Controllers/Api/v1/PlaceController.php +333 -0
  113. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +199 -0
  114. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +133 -0
  115. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +381 -0
  116. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +223 -0
  117. package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +146 -0
  118. package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +138 -0
  119. package/server/src/Http/Controllers/Api/v1/VehicleController.php +191 -0
  120. package/server/src/Http/Controllers/Api/v1/VendorController.php +159 -0
  121. package/server/src/Http/Controllers/Api/v1/ZoneController.php +146 -0
  122. package/server/src/Http/Controllers/FleetOpsController.php +13 -0
  123. package/server/src/Http/Controllers/Internal/v1/ContactController.php +69 -0
  124. package/server/src/Http/Controllers/Internal/v1/DriverController.php +261 -0
  125. package/server/src/Http/Controllers/Internal/v1/EntityController.php +15 -0
  126. package/server/src/Http/Controllers/Internal/v1/FleetController.php +152 -0
  127. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +80 -0
  128. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +15 -0
  129. package/server/src/Http/Controllers/Internal/v1/GeocoderController.php +96 -0
  130. package/server/src/Http/Controllers/Internal/v1/IntegratedVendorController.php +63 -0
  131. package/server/src/Http/Controllers/Internal/v1/IssueController.php +15 -0
  132. package/server/src/Http/Controllers/Internal/v1/LiveController.php +127 -0
  133. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +75 -0
  134. package/server/src/Http/Controllers/Internal/v1/MorphController.php +77 -0
  135. package/server/src/Http/Controllers/Internal/v1/OrderConfigController.php +269 -0
  136. package/server/src/Http/Controllers/Internal/v1/OrderController.php +564 -0
  137. package/server/src/Http/Controllers/Internal/v1/PayloadController.php +15 -0
  138. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +163 -0
  139. package/server/src/Http/Controllers/Internal/v1/ProofController.php +139 -0
  140. package/server/src/Http/Controllers/Internal/v1/PurchaseRateController.php +15 -0
  141. package/server/src/Http/Controllers/Internal/v1/RouteController.php +15 -0
  142. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +32 -0
  143. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +345 -0
  144. package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +47 -0
  145. package/server/src/Http/Controllers/Internal/v1/SettingController.php +30 -0
  146. package/server/src/Http/Controllers/Internal/v1/TrackingNumberController.php +15 -0
  147. package/server/src/Http/Controllers/Internal/v1/TrackingStatusController.php +15 -0
  148. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +65 -0
  149. package/server/src/Http/Controllers/Internal/v1/VehicleDeviceController.php +15 -0
  150. package/server/src/Http/Controllers/Internal/v1/VendorController.php +120 -0
  151. package/server/src/Http/Controllers/Internal/v1/ZoneController.php +15 -0
  152. package/server/src/Http/Filter/ContactFilter.php +86 -0
  153. package/server/src/Http/Filter/DriverFilter.php +148 -0
  154. package/server/src/Http/Filter/FleetFilter.php +108 -0
  155. package/server/src/Http/Filter/IntegratedVendorFilter.php +13 -0
  156. package/server/src/Http/Filter/OrderFilter.php +242 -0
  157. package/server/src/Http/Filter/PlaceFilter.php +111 -0
  158. package/server/src/Http/Filter/ServiceAreaFilter.php +13 -0
  159. package/server/src/Http/Filter/ServiceQuoteFilter.php +13 -0
  160. package/server/src/Http/Filter/ServiceRateFilter.php +33 -0
  161. package/server/src/Http/Filter/TrackingStatusFilter.php +43 -0
  162. package/server/src/Http/Filter/VehicleFilter.php +91 -0
  163. package/server/src/Http/Filter/VendorFilter.php +83 -0
  164. package/server/src/Http/Filter/ZoneFilter.php +13 -0
  165. package/server/src/Http/Requests/CancelOrderRequest.php +31 -0
  166. package/server/src/Http/Requests/CreateContactRequest.php +33 -0
  167. package/server/src/Http/Requests/CreateDriverRequest.php +55 -0
  168. package/server/src/Http/Requests/CreateEntityRequest.php +43 -0
  169. package/server/src/Http/Requests/CreateFleetRequest.php +32 -0
  170. package/server/src/Http/Requests/CreateOrderRequest.php +66 -0
  171. package/server/src/Http/Requests/CreatePayloadRequest.php +37 -0
  172. package/server/src/Http/Requests/CreatePlaceRequest.php +38 -0
  173. package/server/src/Http/Requests/CreatePurchaseRateRequest.php +33 -0
  174. package/server/src/Http/Requests/CreateServiceAreaRequest.php +35 -0
  175. package/server/src/Http/Requests/CreateServiceQuoteRequest.php +29 -0
  176. package/server/src/Http/Requests/CreateServiceRateRequest.php +56 -0
  177. package/server/src/Http/Requests/CreateTrackingNumberRequest.php +34 -0
  178. package/server/src/Http/Requests/CreateTrackingStatusRequest.php +42 -0
  179. package/server/src/Http/Requests/CreateVehicleRequest.php +32 -0
  180. package/server/src/Http/Requests/CreateVendorRequest.php +35 -0
  181. package/server/src/Http/Requests/CreateZoneRequest.php +35 -0
  182. package/server/src/Http/Requests/DecodeTrackingNumberQR.php +30 -0
  183. package/server/src/Http/Requests/DriverSimulationRequest.php +46 -0
  184. package/server/src/Http/Requests/Internal/AssignOrderRequest.php +31 -0
  185. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +42 -0
  186. package/server/src/Http/Requests/Internal/FleetActionRequest.php +32 -0
  187. package/server/src/Http/Requests/Internal/UpdateDriverRequest.php +7 -0
  188. package/server/src/Http/Requests/QueryServiceQuotesRequest.php +41 -0
  189. package/server/src/Http/Requests/UpdateContactRequest.php +20 -0
  190. package/server/src/Http/Requests/UpdateDriverRequest.php +7 -0
  191. package/server/src/Http/Requests/UpdateEntityRequest.php +7 -0
  192. package/server/src/Http/Requests/UpdateFleetRequest.php +7 -0
  193. package/server/src/Http/Requests/UpdateOrderRequest.php +7 -0
  194. package/server/src/Http/Requests/UpdatePayloadRequest.php +7 -0
  195. package/server/src/Http/Requests/UpdatePlaceRequest.php +7 -0
  196. package/server/src/Http/Requests/UpdatePurchaseRateRequest.php +7 -0
  197. package/server/src/Http/Requests/UpdateServiceAreaRequest.php +29 -0
  198. package/server/src/Http/Requests/UpdateServiceQuoteRequest.php +29 -0
  199. package/server/src/Http/Requests/UpdateServiceRateRequest.php +29 -0
  200. package/server/src/Http/Requests/UpdateTrackingNumberRequest.php +7 -0
  201. package/server/src/Http/Requests/UpdateTrackingStatusRequest.php +32 -0
  202. package/server/src/Http/Requests/UpdateVehicleRequest.php +7 -0
  203. package/server/src/Http/Requests/UpdateVendorRequest.php +7 -0
  204. package/server/src/Http/Requests/UpdateZoneRequest.php +7 -0
  205. package/server/src/Http/Resources/Internal/v1/IntegratedVendorFacilitator.php +37 -0
  206. package/server/src/Http/Resources/Internal/v1/Vehicle.php +33 -0
  207. package/server/src/Http/Resources/Internal/v1/Waypoint.php +34 -0
  208. package/server/src/Http/Resources/v1/Contact.php +62 -0
  209. package/server/src/Http/Resources/v1/CurrentJob.php +36 -0
  210. package/server/src/Http/Resources/v1/DeletedResource.php +61 -0
  211. package/server/src/Http/Resources/v1/Driver.php +89 -0
  212. package/server/src/Http/Resources/v1/Entity.php +107 -0
  213. package/server/src/Http/Resources/v1/Fleet.php +60 -0
  214. package/server/src/Http/Resources/v1/FuelReport.php +67 -0
  215. package/server/src/Http/Resources/v1/Order.php +131 -0
  216. package/server/src/Http/Resources/v1/Payload.php +94 -0
  217. package/server/src/Http/Resources/v1/Place.php +84 -0
  218. package/server/src/Http/Resources/v1/Proof.php +36 -0
  219. package/server/src/Http/Resources/v1/PurchaseRate.php +77 -0
  220. package/server/src/Http/Resources/v1/ServiceArea.php +55 -0
  221. package/server/src/Http/Resources/v1/ServiceQuote.php +57 -0
  222. package/server/src/Http/Resources/v1/ServiceRate.php +94 -0
  223. package/server/src/Http/Resources/v1/ServiceRateFee.php +58 -0
  224. package/server/src/Http/Resources/v1/ServiceRateParcelFee.php +48 -0
  225. package/server/src/Http/Resources/v1/TrackingNumber.php +62 -0
  226. package/server/src/Http/Resources/v1/TrackingStatus.php +84 -0
  227. package/server/src/Http/Resources/v1/Vehicle.php +75 -0
  228. package/server/src/Http/Resources/v1/VehicleDevice.php +44 -0
  229. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +72 -0
  230. package/server/src/Http/Resources/v1/Vendor.php +72 -0
  231. package/server/src/Http/Resources/v1/Zone.php +60 -0
  232. package/server/src/Imports/OrdersImport.php +18 -0
  233. package/server/src/Integrations/Lalamove/Lalamove.php +678 -0
  234. package/server/src/Integrations/Lalamove/LalamoveDeliveryStop.php +97 -0
  235. package/server/src/Integrations/Lalamove/LalamoveMarket.php +123 -0
  236. package/server/src/Integrations/Lalamove/LalamoveServiceType.php +161 -0
  237. package/server/src/Jobs/SimulateDrivingRoute.php +81 -0
  238. package/server/src/Jobs/SimulateWaypointReached.php +79 -0
  239. package/server/src/Listeners/HandleOrderCanceled.php +46 -0
  240. package/server/src/Listeners/HandleOrderCreated.php +23 -0
  241. package/server/src/Listeners/HandleOrderDispatchFailed.php +35 -0
  242. package/server/src/Listeners/HandleOrderDispatched.php +108 -0
  243. package/server/src/Listeners/HandleOrderDriverAssigned.php +42 -0
  244. package/server/src/Listeners/HandleOrderReady.php +27 -0
  245. package/server/src/Listeners/HandleUserRemovedFromCompany.php +31 -0
  246. package/server/src/Models/Contact.php +239 -0
  247. package/server/src/Models/Driver.php +543 -0
  248. package/server/src/Models/Entity.php +416 -0
  249. package/server/src/Models/Fleet.php +203 -0
  250. package/server/src/Models/FleetDriver.php +73 -0
  251. package/server/src/Models/FleetVehicle.php +73 -0
  252. package/server/src/Models/FuelReport.php +125 -0
  253. package/server/src/Models/IntegratedVendor.php +219 -0
  254. package/server/src/Models/Issue.php +111 -0
  255. package/server/src/Models/Order.php +1132 -0
  256. package/server/src/Models/Payload.php +699 -0
  257. package/server/src/Models/Place.php +656 -0
  258. package/server/src/Models/Position.php +119 -0
  259. package/server/src/Models/Proof.php +94 -0
  260. package/server/src/Models/PurchaseRate.php +208 -0
  261. package/server/src/Models/Route.php +128 -0
  262. package/server/src/Models/ServiceArea.php +161 -0
  263. package/server/src/Models/ServiceQuote.php +207 -0
  264. package/server/src/Models/ServiceQuoteItem.php +46 -0
  265. package/server/src/Models/ServiceRate.php +953 -0
  266. package/server/src/Models/ServiceRateFee.php +96 -0
  267. package/server/src/Models/ServiceRateParcelFee.php +116 -0
  268. package/server/src/Models/TrackingNumber.php +310 -0
  269. package/server/src/Models/TrackingStatus.php +177 -0
  270. package/server/src/Models/Vehicle.php +452 -0
  271. package/server/src/Models/VehicleDevice.php +79 -0
  272. package/server/src/Models/Vendor.php +244 -0
  273. package/server/src/Models/Waypoint.php +226 -0
  274. package/server/src/Models/Zone.php +95 -0
  275. package/server/src/Notifications/OrderAssigned.php +140 -0
  276. package/server/src/Notifications/OrderCanceled.php +129 -0
  277. package/server/src/Notifications/OrderDispatchFailed.php +81 -0
  278. package/server/src/Notifications/OrderDispatched.php +173 -0
  279. package/server/src/Notifications/OrderPing.php +162 -0
  280. package/server/src/Notifications/OrderSplit.php +56 -0
  281. package/server/src/Observers/DriverObserver.php +34 -0
  282. package/server/src/Observers/OrderObserver.php +34 -0
  283. package/server/src/Observers/PayloadObserver.php +30 -0
  284. package/server/src/Observers/PlaceObserver.php +26 -0
  285. package/server/src/Observers/PurchaseRateObserver.php +61 -0
  286. package/server/src/Observers/ServiceAreaObserver.php +35 -0
  287. package/server/src/Observers/ServiceRateObserver.php +78 -0
  288. package/server/src/Observers/TrackingNumberObserver.php +44 -0
  289. package/server/src/Observers/UserObserver.php +20 -0
  290. package/server/src/Observers/VehicleObserver.php +66 -0
  291. package/server/src/Providers/EventServiceProvider.php +30 -0
  292. package/server/src/Providers/FleetOpsServiceProvider.php +94 -0
  293. package/server/src/Rules/ResolvablePoint.php +37 -0
  294. package/server/src/Scopes/DriverScope.php +21 -0
  295. package/server/src/Support/Algo.php +55 -0
  296. package/server/src/Support/DistanceMatrix.php +27 -0
  297. package/server/src/Support/Encoding/Polyline.php +168 -0
  298. package/server/src/Support/Flow.php +622 -0
  299. package/server/src/Support/Geocoding.php +216 -0
  300. package/server/src/Support/IntegratedVendors.php +287 -0
  301. package/server/src/Support/Metrics.php +305 -0
  302. package/server/src/Support/OSRM.php +191 -0
  303. package/server/src/Support/ParsePhone.php +21 -0
  304. package/server/src/Support/Utils.php +1145 -0
  305. package/server/src/Traits/HasTrackingNumber.php +185 -0
  306. package/server/src/routes.php +381 -0
  307. package/server/tests/Feature.php +5 -0
@@ -0,0 +1,1114 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
4
+
5
+ use Fleetbase\FleetOps\Events\OrderDispatchFailed;
6
+ use Fleetbase\FleetOps\Events\OrderReady;
7
+ use Fleetbase\FleetOps\Events\OrderStarted;
8
+ use Fleetbase\FleetOps\Http\Requests\CreateOrderRequest;
9
+ use Fleetbase\FleetOps\Http\Requests\UpdateOrderRequest;
10
+ use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
11
+ use Fleetbase\FleetOps\Http\Resources\v1\Order as OrderResource;
12
+ use Fleetbase\FleetOps\Http\Resources\v1\Proof as ProofResource;
13
+ use Fleetbase\FleetOps\Models\Driver;
14
+ use Fleetbase\FleetOps\Models\Entity;
15
+ use Fleetbase\FleetOps\Models\Order;
16
+ use Fleetbase\FleetOps\Models\Payload;
17
+ use Fleetbase\FleetOps\Models\Place;
18
+ use Fleetbase\FleetOps\Models\Proof;
19
+ use Fleetbase\FleetOps\Models\ServiceQuote;
20
+ use Fleetbase\FleetOps\Models\Waypoint;
21
+ use Fleetbase\FleetOps\Support\Flow;
22
+ use Fleetbase\FleetOps\Support\Utils;
23
+ use Fleetbase\Http\Controllers\Controller;
24
+ use Fleetbase\Models\Company;
25
+ use Fleetbase\Models\File;
26
+ use Illuminate\Http\Request;
27
+ use Illuminate\Support\Carbon;
28
+ use Illuminate\Support\Facades\Storage;
29
+ use Illuminate\Support\Str;
30
+
31
+ class OrderController extends Controller
32
+ {
33
+ /**
34
+ * Creates a new Fleetbase Order resource.
35
+ *
36
+ * @param \Fleetbase\Http\Requests\CreateOrderRequest $request
37
+ *
38
+ * @return \Fleetbase\Http\Resources\Order
39
+ */
40
+ public function create(CreateOrderRequest $request)
41
+ {
42
+ // get request input
43
+ $input = $request->only(['internal_id', 'payload', 'service_quote', 'purchase_rate', 'adhoc', 'adhoc_distance', 'pod_method', 'pod_required', 'scheduled_at', 'type', 'status', 'meta', 'notes']);
44
+
45
+ // make sure company is set
46
+ $input['company_uuid'] = session('company');
47
+
48
+ // resolve service quote if applicable
49
+ $serviceQuote = ServiceQuote::resolveFromRequest($request);
50
+ $integratedVendorOrder = null;
51
+
52
+ // if service quote is applied, resolve it
53
+ if ($serviceQuote instanceof ServiceQuote && $serviceQuote->fromIntegratedVendor()) {
54
+ // create order with integrated vendor, then resume fleetbase order creation
55
+ try {
56
+ $integratedVendorOrder = $serviceQuote->integratedVendor->api()->createOrderFromServiceQuote($serviceQuote, $request);
57
+ } catch (\Exception $e) {
58
+ return response()->error($e->getMessage());
59
+ }
60
+ }
61
+
62
+ // create payload
63
+ if ($request->has('payload') && $request->isArray('payload')) {
64
+ $payloadInput = $request->input('payload');
65
+ $payload = new Payload();
66
+
67
+ $payload->setPickup($payloadInput['pickup']);
68
+ $payload->setDropoff($payloadInput['dropoff']);
69
+ if ($request->has('payload.return')) {
70
+ $payload->setReturn($payloadInput['return']);
71
+ }
72
+ $payload->setWaypoints($payloadInput['waypoints'] ?? []);
73
+ $payload->setEntities($payloadInput['entities'] ?? []);
74
+ $payload->save();
75
+
76
+ $input['payload_uuid'] = $payload->uuid;
77
+ } elseif ($request->isString('payload')) {
78
+ $input['payload_uuid'] = Utils::getUuid('payloads', [
79
+ 'public_id' => $request->input('payload'),
80
+ 'company_uuid' => session('company'),
81
+ ]);
82
+ unset($input['payload']);
83
+ }
84
+
85
+ // create a payload if missing payload[] but has pickup/dropoff/etc
86
+ if ($request->missing('payload')) {
87
+ $payloadInput = $request->only(['pickup', 'dropoff', 'return', 'waypoints', 'entities']);
88
+ $payload = new Payload();
89
+
90
+ $payload->setPickup($payloadInput['pickup']);
91
+ $payload->setDropoff($payloadInput['dropoff']);
92
+ if ($request->has('return')) {
93
+ $payload->setReturn($payloadInput['return']);
94
+ }
95
+ $payload->setWaypoints($payloadInput['waypoints'] ?? []);
96
+ $payload->setEntities($payloadInput['entities'] ?? []);
97
+ $payload->save();
98
+
99
+ $input['payload_uuid'] = $payload->uuid;
100
+ }
101
+
102
+ // driver assignment
103
+ if ($request->has('driver') && $integratedVendorOrder === null) {
104
+ $input['driver_assigned_uuid'] = Utils::getUuid('drivers', [
105
+ 'public_id' => $request->input('driver'),
106
+ 'company_uuid' => session('company'),
107
+ ]);
108
+ }
109
+
110
+ // facilitator assignment
111
+ if ($request->has('facilitator') && $integratedVendorOrder === null) {
112
+ $facilitator = Utils::getUuid(
113
+ ['contacts', 'vendors', 'integrated_vendors'],
114
+ [
115
+ 'public_id' => $request->input('facilitator'),
116
+ 'company_uuid' => session('company'),
117
+ ]
118
+ );
119
+
120
+ if (is_array($facilitator)) {
121
+ $input['facilitator_uuid'] = Utils::get($facilitator, 'uuid');
122
+ $input['facilitator_type'] = Utils::getModelClassName(Utils::get($facilitator, 'table'));
123
+ }
124
+ } elseif ($integratedVendorOrder) {
125
+ $input['facilitator_uuid'] = $serviceQuote->integratedVendor->uuid;
126
+ $input['facilitator_type'] = Utils::getModelClassName('integrated_vendors');
127
+ }
128
+
129
+ // customer assignment
130
+ if ($request->has('customer')) {
131
+ $customer = Utils::getUuid(
132
+ ['contacts', 'vendors'],
133
+ [
134
+ 'public_id' => $request->input('customer'),
135
+ 'company_uuid' => session('company'),
136
+ ]
137
+ );
138
+
139
+ if (is_array($customer)) {
140
+ $input['customer_uuid'] = Utils::get($customer, 'uuid');
141
+ $input['customer_type'] = Utils::getModelClassName(Utils::get($customer, 'table'));
142
+ }
143
+ }
144
+
145
+ // if no type is set its default to default
146
+ if (!isset($input['type'])) {
147
+ $input['type'] = 'default';
148
+ }
149
+
150
+ // if adhoc set convert to sql ready boolean value 1 or 0
151
+ if (isset($input['adhoc']) && $integratedVendorOrder === null) {
152
+ $input['adhoc'] = Utils::isTrue($input['adhoc']) ? 1 : 0;
153
+ }
154
+
155
+ if (!isset($input['payload_uuid'])) {
156
+ return response()->error('Attempted to attach invalid payload to order.');
157
+ }
158
+
159
+ // create the order
160
+ $order = Order::create($input);
161
+
162
+ // notify driver if assigned
163
+ $order->notifyDriverAssigned();
164
+
165
+ // set driving distance and time
166
+ $order->setPreliminaryDistanceAndTime();
167
+
168
+ // if service quote attached purchase
169
+ $order->purchaseServiceQuote($serviceQuote);
170
+
171
+ // if it's integrated vendor order apply to meta
172
+ if ($integratedVendorOrder) {
173
+ $order->updateMeta([
174
+ 'integrated_vendor' => $serviceQuote->integratedVendor->public_id,
175
+ 'integrated_vendor_order' => $integratedVendorOrder,
176
+ ]);
177
+ }
178
+
179
+ // dispatch if flagged true
180
+ if ($request->boolean('dispatch') && $integratedVendorOrder === null) {
181
+ $order->dispatch();
182
+ }
183
+
184
+ // load required relations
185
+ $order->load(['trackingNumber', 'driverAssigned', 'purchaseRate', 'customer', 'facilitator']);
186
+
187
+ // Trigger order created event
188
+ event(new OrderReady($order));
189
+
190
+ // response the driver resource
191
+ return new OrderResource($order);
192
+ }
193
+
194
+ /**
195
+ * Updates a Fleetbase Order resource.
196
+ *
197
+ * @param string $id
198
+ * @param \Fleetbase\Http\Requests\UpdateOrderRequest $request
199
+ *
200
+ * @return \Fleetbase\Http\Resources\Order
201
+ */
202
+ public function update($id, UpdateOrderRequest $request)
203
+ {
204
+ // find for the order
205
+ try {
206
+ $order = Order::findRecordOrFail($id);
207
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
208
+ return response()->json(
209
+ [
210
+ 'error' => 'Order resource not found.',
211
+ ],
212
+ 404
213
+ );
214
+ }
215
+
216
+ // get request input
217
+ $input = $request->only(['internal_id', 'payload', 'adhoc', 'adhoc_distance', 'pod_method', 'pod_required', 'scheduled_at', 'meta', 'type', 'status', 'notes']);
218
+
219
+ // update payload if new input or change payload by id
220
+ if ($request->isArray('payload')) {
221
+ $payloadInput = $request->input('payload');
222
+ $payload = new Payload();
223
+
224
+ $payload->setPickup($payloadInput['pickup']);
225
+ $payload->setDropoff($payloadInput['dropoff']);
226
+ if ($request->has('payload.return')) {
227
+ $payload->setReturn($payloadInput['return']);
228
+ }
229
+ $payload->setWaypoints($payloadInput['waypoints'] ?? []);
230
+ $payload->setEntities($payloadInput['entities'] ?? []);
231
+ $payload->save();
232
+
233
+ $input['payload_uuid'] = $payload->uuid;
234
+ } elseif ($request->has('payload')) {
235
+ $input['payload_uuid'] = Utils::getUuid('payloads', [
236
+ 'public_id' => $request->input('payload'),
237
+ 'company_uuid' => session('company'),
238
+ ]);
239
+ unset($input['payload']);
240
+ }
241
+
242
+ // update payload properties if applicable
243
+ if ($request->has('pickup')) {
244
+ if ($order->payload) {
245
+ $order->payload->setPickup($request->input('pickup'));
246
+ }
247
+ }
248
+ if ($request->has('dropoff')) {
249
+ if ($order->payload) {
250
+ $order->payload->setDropoff($request->input('dropoff'));
251
+ }
252
+ }
253
+
254
+ if ($request->has('return')) {
255
+ if ($order->payload) {
256
+ $order->payload->setReturn($request->input('return'));
257
+ }
258
+ }
259
+
260
+ if ($request->has('waypoints')) {
261
+ if ($order->payload) {
262
+ $order->payload->setWaypoints($request->input('waypoints'));
263
+ }
264
+ }
265
+
266
+ if ($request->has('entities')) {
267
+ if ($order->payload) {
268
+ $order->payload->setEntities($request->input('entities'));
269
+ }
270
+ }
271
+
272
+ // driver assignment
273
+ if ($request->has('driver')) {
274
+ $input['driver_assigned_uuid'] = Utils::getUuid('drivers', [
275
+ 'public_id' => $request->input('driver'),
276
+ 'company_uuid' => session('company'),
277
+ ]);
278
+ }
279
+
280
+ // facilitator assignment
281
+ if ($request->has('facilitator')) {
282
+ $facilitator = Utils::getUuid(
283
+ ['contacts', 'vendors'],
284
+ [
285
+ 'public_id' => $request->input('facilitator'),
286
+ 'company_uuid' => session('company'),
287
+ ]
288
+ );
289
+
290
+ if (is_array($facilitator)) {
291
+ $input['facilitator_uuid'] = Utils::get($facilitator, 'uuid');
292
+ $input['facilitator_type'] = Utils::getModelClassName(Utils::get($facilitator, 'table'));
293
+ }
294
+ }
295
+
296
+ // customer assignment
297
+ if ($request->has('customer')) {
298
+ $customer = Utils::getUuid(
299
+ ['contacts', 'vendors'],
300
+ [
301
+ 'public_id' => $request->input('customer'),
302
+ 'company_uuid' => session('company'),
303
+ ]
304
+ );
305
+
306
+ if (is_array($customer)) {
307
+ $input['customer_uuid'] = Utils::get($customer, 'uuid');
308
+ $input['customer_type'] = Utils::getModelClassName(Utils::get($customer, 'table'));
309
+ }
310
+ }
311
+
312
+ // dispatch if flagged true
313
+ if ($request->boolean('dispatch')) {
314
+ $order->dispatch();
315
+ }
316
+
317
+ // update the order
318
+ $order->update($input);
319
+ $order->flushAttributesCache();
320
+
321
+ // response the order resource
322
+ return new OrderResource($order);
323
+ }
324
+
325
+ /**
326
+ * Query for Fleetbase Order resources.
327
+ *
328
+ * @return \Fleetbase\Http\Resources\OrderCollection
329
+ */
330
+ public function query(Request $request)
331
+ {
332
+ $results = Order::queryWithRequest($request, function (&$query, $request) {
333
+ if ($request->has('payload')) {
334
+ $query->whereHas('payload', function ($q) use ($request) {
335
+ $q->where('public_id', $request->input('payload'));
336
+ });
337
+ }
338
+
339
+ if ($request->has('pickup')) {
340
+ $query->whereHas('payload.pickup', function ($q) use ($request) {
341
+ $q->where('public_id', $request->input('pickup'));
342
+ });
343
+ }
344
+
345
+ if ($request->has('dropoff')) {
346
+ $query->whereHas('payload.dropoff', function ($q) use ($request) {
347
+ $q->where('public_id', $request->input('dropoff'));
348
+ });
349
+ }
350
+
351
+ if ($request->has('return')) {
352
+ $query->whereHas('payload.return', function ($q) use ($request) {
353
+ $q->where('public_id', $request->input('return'));
354
+ });
355
+ }
356
+
357
+ if ($request->has('facilitator')) {
358
+ $query->whereHas('facilitator', function ($q) use ($request) {
359
+ $q->where(function ($q) use ($request) {
360
+ $q->where('public_id', $request->input('facilitator'));
361
+ $q->orWhere('internal_id', $request->input('facilitator'));
362
+ });
363
+ });
364
+ }
365
+
366
+ if ($request->has('customer')) {
367
+ $query->whereHas('customer', function ($q) use ($request) {
368
+ $q->where(function ($q) use ($request) {
369
+ $q->where('public_id', $request->input('customer'));
370
+ $q->orWhere('internal_id', $request->input('customer'));
371
+ });
372
+ });
373
+ }
374
+
375
+ if ($request->has('entity')) {
376
+ $query->whereHas('payload.entities', function ($q) use ($request) {
377
+ $q->where(function ($q) use ($request) {
378
+ $q->where('public_id', $request->input('entity'));
379
+ $q->orWhere('internal_id', $request->input('entity'));
380
+ });
381
+ });
382
+ }
383
+
384
+ if ($request->has('entity_status')) {
385
+ $query->whereHas('payload.entities.trackingNumber.status', function ($q) use ($request) {
386
+ if ($request->isArray('entity_status')) {
387
+ $q->whereIn('code', $request->input('entity_status'));
388
+ } else {
389
+ $q->where('code', $request->input('entity_status'));
390
+ }
391
+ });
392
+ }
393
+
394
+ if ($request->has('on')) {
395
+ $on = Carbon::fromString($request->input('on'));
396
+
397
+ $query->where(function ($q) use ($on) {
398
+ $q->whereDate('created_at', $on);
399
+ $q->orWhereDate('scheduled_at', $on);
400
+ });
401
+ }
402
+
403
+ if ($request->boolean('pod_required')) {
404
+ $query->where('pod_required', 1);
405
+ }
406
+
407
+ if ($request->boolean('dispatched')) {
408
+ $query->where('dispatched', 1);
409
+ }
410
+
411
+ if ($request->has('nearby')) {
412
+ $nearby = $request->input('nearby');
413
+ $distance = 6000; // default in meters
414
+ $company = Company::currentSession();
415
+ $addedNearbyQuery = false;
416
+
417
+ if ($company) {
418
+ $distance = $company->getOption('fleetops.adhoc_distance', 6000);
419
+ }
420
+
421
+ // if wants to find nearby place or coordinates
422
+ if (Utils::isCoordinates($nearby)) {
423
+ $location = Utils::getPointFromMixed($nearby);
424
+
425
+ $query->whereHas('payload', function ($q) use ($location, $distance) {
426
+ $q->whereHas('pickup', function ($q) use ($location, $distance) {
427
+ $q->distanceSphere('location', $location, $distance);
428
+ $q->distanceSphereValue('location', $location);
429
+ })->orWhereHas('waypoints', function ($q) use ($location, $distance) {
430
+ $q->distanceSphere('location', $location, $distance);
431
+ $q->distanceSphereValue('location', $location);
432
+ });
433
+ });
434
+
435
+ // Update so additional nearby queries are not added
436
+ $addedNearbyQuery = true;
437
+ }
438
+
439
+ // request wants to find orders nearby a driver ?
440
+ if ($addedNearbyQuery === false && is_string($nearby) && Str::startsWith($nearby, 'driver_')) {
441
+ $driver = Driver::where('public_id', $nearby)->first();
442
+
443
+ if ($driver) {
444
+ $query->whereHas('payload', function ($q) use ($driver, $distance) {
445
+ $q->whereHas('pickup', function ($q) use ($driver, $distance) {
446
+ $q->distanceSphere('location', $driver->location, $distance);
447
+ $q->distanceSphereValue('location', $driver->location);
448
+ })->orWhereHas('waypoints', function ($q) use ($driver, $distance) {
449
+ $q->distanceSphere('location', $driver->location, $distance);
450
+ $q->distanceSphereValue('location', $driver->location);
451
+ });
452
+ });
453
+
454
+ // Update so additional nearby queries are not added
455
+ $addedNearbyQuery = true;
456
+ }
457
+ }
458
+
459
+ // if is a string like address string
460
+ if ($addedNearbyQuery === false && is_string($nearby)) {
461
+ $nearby = Place::createFromMixed($nearby, [], false);
462
+
463
+ if ($nearby instanceof Place) {
464
+ $query->whereHas('payload', function ($q) use ($nearby, $distance) {
465
+ $q->whereHas('pickup', function ($q) use ($nearby, $distance) {
466
+ $q->distanceSphere('location', $nearby->location, $distance);
467
+ $q->distanceSphereValue('location', $nearby->location);
468
+ })->orWhereHas('waypoints', function ($q) use ($nearby, $distance) {
469
+ $q->distanceSphere('location', $nearby->location, $distance);
470
+ $q->distanceSphereValue('location', $nearby->location);
471
+ });
472
+ });
473
+
474
+ // Update so additional nearby queries are not added
475
+ $addedNearbyQuery = true;
476
+ }
477
+ }
478
+ }
479
+ });
480
+
481
+ return OrderResource::collection($results);
482
+ }
483
+
484
+ /**
485
+ * Finds a single Fleetbase Order resources.
486
+ *
487
+ * @return \Fleetbase\Http\Resources\OrderCollection
488
+ */
489
+ public function find($id, Request $request)
490
+ {
491
+ // find for the order
492
+ try {
493
+ $order = Order::findRecordOrFail($id);
494
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
495
+ return response()->json(
496
+ [
497
+ 'error' => 'Order resource not found.',
498
+ ],
499
+ 404
500
+ );
501
+ }
502
+
503
+ // response the order resource
504
+ return new OrderResource($order);
505
+ }
506
+
507
+ /**
508
+ * Deletes a Fleetbase Order resources.
509
+ *
510
+ * @return \Fleetbase\Http\Resources\OrderCollection
511
+ */
512
+ public function delete($id, Request $request)
513
+ {
514
+ // find for the driver
515
+ try {
516
+ $order = Order::findRecordOrFail($id);
517
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
518
+ return response()->json(
519
+ [
520
+ 'error' => 'Order resource not found.',
521
+ ],
522
+ 404
523
+ );
524
+ }
525
+
526
+ // delete the order
527
+ $order->delete();
528
+
529
+ // response the order resource
530
+ return new DeletedResource($order);
531
+ }
532
+
533
+ /**
534
+ * Returns current distance and time matrix for an order.
535
+ *
536
+ * @return \Illuminate\Http\Response $response
537
+ */
538
+ public function getDistanceMatrix(string $id)
539
+ {
540
+ // find the order
541
+ try {
542
+ $order = Order::findRecordOrFail($id);
543
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
544
+ return response()->json(
545
+ [
546
+ 'error' => 'Order resource not found.',
547
+ ],
548
+ 404
549
+ );
550
+ }
551
+
552
+ $order->load(['payload', 'payload.waypoints', 'payload.pickup', 'payload.dropoff']);
553
+
554
+ $origin = $order->payload->pickup ?? $order->payload->waypoints->first();
555
+ $destination = $order->payload->dropoff ?? $order->payload->waypoints->firstWhere('current_waypoint_uuid', $order->current_waypoint_uuid);
556
+
557
+ $matrix = Utils::getDrivingDistanceAndTime($origin, $destination);
558
+
559
+ $order->update(['distance' => $matrix->distance, 'time' => $matrix->time]);
560
+
561
+ // response distance and time matrix
562
+ return response()->json($matrix);
563
+ }
564
+
565
+ /**
566
+ * Dispatches an order.
567
+ *
568
+ * @return \Fleetbase\Http\Resources\v1\Order
569
+ */
570
+ public function dispatchOrder(string $id)
571
+ {
572
+ try {
573
+ $order = Order::findRecordOrFail($id);
574
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
575
+ return response()->json(
576
+ [
577
+ 'error' => 'Order resource not found.',
578
+ ],
579
+ 404
580
+ );
581
+ }
582
+
583
+ if (!$order->hasDriverAssigned && !$order->adhoc) {
584
+ return response()->error('No driver assigned to dispatch!');
585
+ }
586
+
587
+ if ($order->dispatched) {
588
+ return response()->error('Order has already been dispatched!');
589
+ }
590
+
591
+ $order->dispatch();
592
+
593
+ return new OrderResource($order);
594
+ }
595
+
596
+ /**
597
+ * Request to start order, this assumes order is dispatched.
598
+ * Unless there is a param to skip dispatch throw a order not dispatched error.
599
+ *
600
+ * @return \Fleetbase\Http\Resources\v1\Order
601
+ */
602
+ public function startOrder(string $id, Request $request)
603
+ {
604
+ $skipDispatch = $request->or(['skip_dispatch', 'skipDispatch'], false);
605
+ $assignAdhocDriver = $request->input('assign');
606
+
607
+ try {
608
+ $order = Order::findRecordOrFail($id, ['payload.waypoints'], []);
609
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
610
+ return response()->json(
611
+ [
612
+ 'error' => 'Order resource not found.',
613
+ ],
614
+ 404
615
+ );
616
+ }
617
+
618
+ if ($order->started) {
619
+ return response()->error('Order has already started.');
620
+ }
621
+
622
+ // if the order is adhoc and the parameter of `assign` is set with a valid driver id, assign the driver and continue
623
+ if ($order->adhoc && $assignAdhocDriver && Str::startsWith($assignAdhocDriver, 'driver_')) {
624
+ $order->assignDriver($assignAdhocDriver, true);
625
+ }
626
+
627
+ /** @var \Fleetbase\Models\Driver */
628
+ $driver = Driver::where('uuid', $order->driver_assigned_uuid)->withoutGlobalScopes()->first();
629
+
630
+ /** @var \Fleetbase\Models\Payload */
631
+ $payload = Payload::where('uuid', $order->payload_uuid)->withoutGlobalScopes()->with(['waypoints', 'waypointMarkers', 'entities'])->first();
632
+
633
+ if ($order->adhoc && !$driver) {
634
+ return response()->error('You must send driver to accept adhoc order.');
635
+ }
636
+
637
+ if (!$driver) {
638
+ return response()->error('No driver assigned to order.');
639
+ }
640
+
641
+ // get the next order activity
642
+ $flow = $activity = Flow::getNextActivity($order);
643
+
644
+ // order is not dispatched if next activity code is dispatch or order is not flagged as dispatched
645
+ $isNotDispatched = $activity['code'] === 'dispatched' || $order->isNotDispatched;
646
+
647
+ // if order is not dispatched yet $activity['code'] === 'dispatched' || $order->dispatched === true
648
+ // and not skipping throw order not dispatched error
649
+ if ($isNotDispatched && !$skipDispatch) {
650
+ return response()->error('Order has not been dispatched yet and cannot be started.');
651
+ }
652
+
653
+ // if we're going to skip the dispatch get the next activity status and flow and continue
654
+ if ($isNotDispatched && $skipDispatch) {
655
+ $flow = $activity = Flow::getAfterNextActivity($order);
656
+ }
657
+
658
+ // set order to started
659
+ $order->started = true;
660
+ $order->started_at = now();
661
+ $order->save();
662
+
663
+ // trigger start event
664
+ event(new OrderStarted($order));
665
+
666
+ // set order as drivers current order
667
+ $driver->current_job_uuid = $order->uuid;
668
+ $driver->save();
669
+
670
+ /** @var \Grimzy\LaravelMysqlSpatial\Types\Point */
671
+ $location = $order->getLastLocation();
672
+
673
+ // set first destination for payload
674
+ $payload->setFirstWaypoint($activity, $location);
675
+ $order->setRelation('payload', $payload);
676
+
677
+ // update order activity
678
+ $updateActivityRequest = new Request(['activity' => $flow]);
679
+
680
+ // update activity
681
+ return $this->updateActivity($order, $updateActivityRequest);
682
+ }
683
+
684
+ /**
685
+ * Update an order activity.
686
+ *
687
+ * @param \Fleetbase\Models\Order|string $order
688
+ *
689
+ * @return \Fleetbase\Http\Resources\v1\Order
690
+ */
691
+ public function updateActivity($order, Request $request)
692
+ {
693
+ $skipDispatch = $request->or(['skip_dispatch', 'skipDispatch'], false);
694
+ $proof = $request->input('proof', null);
695
+
696
+ /** @var \Fleetbase\FleetOps\Models\Order $order */
697
+ $order = ($order instanceof Order) ? $order : Order::withoutGlobalScopes()
698
+ ->where('public_id', $order)
699
+ ->whereNull('deleted_at')
700
+ ->with(['driverAssigned', 'payload.entities', 'payload.currentWaypoint', 'payload.waypoints'])
701
+ ->first();
702
+
703
+ if (!$order) {
704
+ return response()->error('No resource not found.');
705
+ }
706
+
707
+ // if orser is created trigger started flag
708
+ if ($order->status === 'created') {
709
+ $order->started = true;
710
+ $order->started_at = now();
711
+ }
712
+
713
+ $activity = $request->input('activity', Flow::getNextActivity($order));
714
+
715
+ // if we're going to skip the dispatch get the next activity status and flow and continue
716
+ if (is_array($activity) && $activity['code'] === 'dispatched' && $skipDispatch) {
717
+ $activity = Flow::getAfterNextActivity($order);
718
+ }
719
+
720
+ // handle pickup/dropoff order activity update as normal
721
+ if (is_array($activity) && $activity['code'] === 'dispatched') {
722
+ // make sure driver is assigned if not trigger failed dispatch
723
+ if (!$order->hasDriverAssigned && !$order->adhoc) {
724
+ event(new OrderDispatchFailed($order, 'No driver assigned for order to dispatch to.'));
725
+
726
+ return response()->error('No driver assigned for order to dispatch to.');
727
+ }
728
+
729
+ $order->dispatch();
730
+
731
+ return new OrderResource($order);
732
+ }
733
+
734
+ /** @var \Grimzy\LaravelMysqlSpatial\Types\Point */
735
+ $location = $order->getLastLocation();
736
+
737
+ // if is multi drop order and no current destination set it
738
+ if ($order->payload->isMultipleDropOrder && !$order->payload->current_waypoint_uuid) {
739
+ $order->payload->setFirstWaypoint($activity, $location);
740
+ }
741
+
742
+ if (is_array($activity) && $activity['code'] === 'completed' && $order->payload->isMultipleDropOrder) {
743
+ // confirm every waypoint is completed
744
+ $isCompleted = $order->payload->waypointMarkers->every(function ($waypoint) {
745
+ return $waypoint->status_code === 'COMPLETED';
746
+ });
747
+
748
+ // only update activity for waypoint
749
+ if (!$isCompleted) {
750
+ $order->payload->updateWaypointActivity($activity, $location, $proof);
751
+ $order->payload->setNextWaypointDestination();
752
+ $order->payload->refresh();
753
+
754
+ // recheck if order is completed
755
+ $isFullyCompleted = $order->payload->waypointMarkers->every(function ($waypoint) {
756
+ return $waypoint->status_code === 'COMPLETED';
757
+ });
758
+
759
+ if (!$isFullyCompleted) {
760
+ return new OrderResource($order);
761
+ }
762
+ }
763
+ }
764
+
765
+ if (is_array($activity) && $activity['code'] === 'completed' && $order->driverAssigned) {
766
+ // unset from driver current job
767
+ $order->driverAssigned->unassignCurrentOrder();
768
+ $order->notifyCompleted();
769
+ }
770
+
771
+ $order->setStatus($activity['code']);
772
+ $order->insertActivity($activity['status'], $activity['details'] ?? '', $location, $activity['code'], $proof);
773
+
774
+ // also update for each order entities if not multiple drop order
775
+ // all entities will share the same activity status as is one drop order
776
+ if (!$order->payload->isMultipleDropOrder) {
777
+ foreach ($order->payload->entities as $entity) {
778
+ $entity->insertActivity($activity['status'], $activity['details'] ?? '', $location, $activity['code'], $proof);
779
+ }
780
+ } else {
781
+ $order->payload->updateWaypointActivity($activity, $location);
782
+ }
783
+
784
+ return new OrderResource($order);
785
+ }
786
+
787
+ /**
788
+ * Retrieve the next activity for the order flow.
789
+ *
790
+ * @return \Illuminate\Http\Response
791
+ */
792
+ public function getNextActivity(string $id, Request $request)
793
+ {
794
+ $waypointId = $request->input('waypoint');
795
+
796
+ try {
797
+ $order = Order::findRecordOrFail($id, ['payload']);
798
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
799
+ return response()->json(
800
+ [
801
+ 'error' => 'Order resource not found.',
802
+ ],
803
+ 404
804
+ );
805
+ }
806
+
807
+ $isMultipleDropOrder = $order->payload->isMultipleDropOrder;
808
+
809
+ if ($waypointId && $isMultipleDropOrder) {
810
+ $waypoint = Waypoint::where('payload_uuid', $order->payload_uuid)->where(function ($q) use ($waypointId) {
811
+ $q->whereHas('place', function ($q) use ($waypointId) {
812
+ $q->where('public_id', $waypointId);
813
+ });
814
+ $q->orWhere('public_id', $waypointId);
815
+ })->withoutGlobalScopes()->first();
816
+
817
+ $activity = Flow::getOrderWaypointFlow($order, $waypoint);
818
+
819
+ return response()->json($activity);
820
+ }
821
+
822
+ $activity = Flow::getOrderFlow($order);
823
+
824
+ return response()->json($activity);
825
+ }
826
+
827
+ /**
828
+ * Confirms and completes an order.
829
+ *
830
+ * @return \Fleetbase\Http\Resources\v1\Order
831
+ */
832
+ public function completeOrder(string $id)
833
+ {
834
+ try {
835
+ $order = Order::findRecordOrFail($id);
836
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
837
+ return response()->json(
838
+ [
839
+ 'error' => 'Order resource not found.',
840
+ ],
841
+ 404
842
+ );
843
+ }
844
+
845
+ // confirm every waypoint is completed
846
+ $isCompleted = $order->payload->waypointMarkers->every(function ($waypoint) {
847
+ return $waypoint->status_code === 'COMPLETED';
848
+ });
849
+
850
+ // if not completed respond with error
851
+ if (!$isCompleted) {
852
+ return response()->error('Not all waypoints completed for order.');
853
+ }
854
+
855
+ $activity = [
856
+ 'status' => 'Order completed',
857
+ 'details' => 'Driver has completed order for all waypoints',
858
+ 'code' => 'completed',
859
+ ];
860
+
861
+ if ($order->driverAssigned) {
862
+ // unset from driver current job
863
+ $order->driverAssigned->unassignCurrentOrder();
864
+ }
865
+
866
+ $order->notifyCompleted();
867
+
868
+ /** @var \Grimzy\LaravelMysqlSpatial\Types\Point */
869
+ $location = $order->getLastLocation();
870
+
871
+ $order->setStatus($activity['code']);
872
+ $order->insertActivity($activity['status'], $activity['details'] ?? '', $location, $activity['code']);
873
+
874
+ return new OrderResource($order);
875
+ }
876
+
877
+ /**
878
+ * Updates a order to canceled and updates order activity.
879
+ *
880
+ * @return \Fleetbase\Http\Resources\v1\Order
881
+ */
882
+ public function cancelOrder(string $id)
883
+ {
884
+ try {
885
+ $order = Order::findRecordOrFail($id);
886
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
887
+ return response()->json(
888
+ [
889
+ 'error' => 'Order resource not found.',
890
+ ],
891
+ 404
892
+ );
893
+ }
894
+
895
+ $order->cancel();
896
+
897
+ return new OrderResource($order);
898
+ }
899
+
900
+ /**
901
+ * Updates the order payload destination with a valid place.
902
+ *
903
+ * @return \Fleetbase\Http\Resources\v1\Order
904
+ */
905
+ public function setDestination(string $id, string $placeId)
906
+ {
907
+ try {
908
+ $order = Order::findRecordOrFail($id);
909
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
910
+ return response()->json(
911
+ [
912
+ 'error' => 'Order resource not found.',
913
+ ],
914
+ 404
915
+ );
916
+ }
917
+
918
+ $place = $order->payload->waypoints->firstWhere('public_id', $placeId);
919
+
920
+ if (!$place) {
921
+ return response()->error('Place resource is not a valid destination.');
922
+ }
923
+
924
+ $order->payload->update(['current_waypoint_uuid' => $place->uuid]);
925
+ $order->payload->refresh();
926
+
927
+ return new OrderResource($order);
928
+ }
929
+
930
+ /**
931
+ * Sends request for route optimization and re-sorts waypoints.
932
+ *
933
+ * @return \Fleetbase\Http\Resources\v1\Order
934
+ */
935
+ public function optimize(string $id)
936
+ {
937
+ try {
938
+ $order = Order::findRecordOrFail($id);
939
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
940
+ return response()->json(
941
+ [
942
+ 'error' => 'Order resource not found.',
943
+ ],
944
+ 404
945
+ );
946
+ }
947
+
948
+ // do this code
949
+
950
+ return new OrderResource($order);
951
+ }
952
+
953
+ /**
954
+ * Verify & Capture QR Code Scan.
955
+ *
956
+ * @return void
957
+ */
958
+ public function captureQrScan(string $id, string $subjectId = null, Request $request)
959
+ {
960
+ $code = $request->input('code');
961
+ $data = $request->input('data', []);
962
+ $rawData = $request->input('raw_data', []);
963
+ $type = $subjectId ? strtok($subjectId, '_') : null;
964
+
965
+ try {
966
+ $order = Order::findRecordOrFail($id);
967
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
968
+ return response()->json(
969
+ [
970
+ 'error' => 'Order resource not found.',
971
+ ],
972
+ 404
973
+ );
974
+ }
975
+
976
+ if (!$code) {
977
+ return response()->error('No QR code data to capture.');
978
+ }
979
+
980
+ $subject = $type === null ? $order : null;
981
+
982
+ switch ($type) {
983
+ case 'place':
984
+ case 'waypoint':
985
+ $subject = Waypoint::where('payload_uuid', $order->payload_uuid)->where(function ($q) use ($code) {
986
+ $q->whereHas('place', function ($q) use ($code) {
987
+ $q->where('uuid', $code);
988
+ });
989
+ $q->orWhere('uuid', $code);
990
+ })->withoutGlobalScopes()->first();
991
+ break;
992
+
993
+ case 'entity':
994
+ $subject = Entity::where('uuid', $code)->withoutGlobalScopes()->first();
995
+ break;
996
+
997
+ default:
998
+ break;
999
+ }
1000
+
1001
+ if (!$subject) {
1002
+ return response()->error('Unable to capture QR code data.');
1003
+ }
1004
+
1005
+ // validate
1006
+ if ($subject && $code === $subject->uuid) {
1007
+ // create verification proof
1008
+ $proof = Proof::create([
1009
+ 'company_uuid' => session('company'),
1010
+ 'order_uuid' => $order->uuid,
1011
+ 'subject_uuid' => $subject->uuid,
1012
+ 'subject_type' => Utils::getModelClassName($subject),
1013
+ 'remarks' => 'Verified by QR Code Scan',
1014
+ 'raw_data' => $rawData,
1015
+ 'data' => $data,
1016
+ ]);
1017
+
1018
+ return new ProofResource($proof);
1019
+ }
1020
+
1021
+ return response()->error('Unable to validate QR code data.');
1022
+ }
1023
+
1024
+ /**
1025
+ * Validate a QR code.
1026
+ *
1027
+ * @return void
1028
+ */
1029
+ public function captureSignature(string $id, string $subjectId = null, Request $request)
1030
+ {
1031
+ $disk = $request->input('disk', config('filesystems.default'));
1032
+ $bucket = $request->input('bucket', config('filesystems.disks.' . $disk . '.bucket', config('filesystems.disks.s3.bucket')));
1033
+ $signature = $request->input('signature');
1034
+ $data = $request->input('data', []);
1035
+ $type = $subjectId ? strtok($subjectId, '_') : null;
1036
+
1037
+ try {
1038
+ $order = Order::findRecordOrFail($id);
1039
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
1040
+ return response()->json(
1041
+ [
1042
+ 'error' => 'Order resource not found.',
1043
+ ],
1044
+ 404
1045
+ );
1046
+ }
1047
+
1048
+ if (!$signature) {
1049
+ return response()->error('No signature data to capture.');
1050
+ }
1051
+
1052
+ $subject = $type === null ? $order : null;
1053
+
1054
+ switch ($type) {
1055
+ case 'place':
1056
+ case 'waypoint':
1057
+ $subject = Waypoint::where('payload_uuid', $order->payload_uuid)->where(function ($q) use ($subjectId) {
1058
+ $q->whereHas('place', function ($q) use ($subjectId) {
1059
+ $q->where('public_id', $subjectId);
1060
+ });
1061
+ $q->orWhere('public_id', $subjectId);
1062
+ })->withoutGlobalScopes()->first();
1063
+ break;
1064
+
1065
+ case 'entity':
1066
+ $subject = Entity::where('public_id', $subjectId)->withoutGlobalScopes()->first();
1067
+ break;
1068
+
1069
+ default:
1070
+ break;
1071
+ }
1072
+
1073
+ if (!$subject) {
1074
+ return response()->error('Unable to capture signature data.');
1075
+ }
1076
+
1077
+ // create proof instance
1078
+ $proof = Proof::create([
1079
+ 'company_uuid' => session('company'),
1080
+ 'order_uuid' => $order->uuid,
1081
+ 'subject_uuid' => $subject->uuid,
1082
+ 'subject_type' => Utils::getModelClassName($subject),
1083
+ 'remarks' => 'Verified by Signature',
1084
+ 'raw_data' => $signature,
1085
+ 'data' => $data,
1086
+ ]);
1087
+
1088
+ // set the signature storage path
1089
+ $path = 'uploads/' . session('company') . '/signatures/' . $proof->public_id . '.png';
1090
+
1091
+ // upload signature
1092
+ Storage::disk($disk)->put($path, base64_decode($signature));
1093
+
1094
+ // create file record for upload
1095
+ $file = File::create([
1096
+ 'company_uuid' => session('company'),
1097
+ 'uploader_uuid' => session('user'),
1098
+ 'name' => basename($path),
1099
+ 'original_filename' => basename($path),
1100
+ 'extension' => 'png',
1101
+ 'content_type' => 'image/png',
1102
+ 'path' => $path,
1103
+ 'bucket' => $bucket,
1104
+ 'type' => 'signature',
1105
+ 'size' => Utils::getBase64ImageSize($signature),
1106
+ ])->setKey($proof);
1107
+
1108
+ // set file to proof
1109
+ $proof->file_uuid = $file->uuid;
1110
+ $proof->save();
1111
+
1112
+ return new ProofResource($proof);
1113
+ }
1114
+ }