@fleetbase/fleetops-engine 0.0.1

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 (446) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +32 -0
  3. package/addon/components/cell/driver-name.hbs +13 -0
  4. package/addon/components/cell/driver-name.js +30 -0
  5. package/addon/components/custom-field-value.hbs +11 -0
  6. package/addon/components/custom-field-value.js +41 -0
  7. package/addon/components/custom-field.hbs +42 -0
  8. package/addon/components/custom-field.js +46 -0
  9. package/addon/components/display-place.hbs +52 -0
  10. package/addon/components/display-place.js +11 -0
  11. package/addon/components/driver-card-mini.hbs +12 -0
  12. package/addon/components/driver-card-mini.js +25 -0
  13. package/addon/components/driver-card.hbs +27 -0
  14. package/addon/components/driver-card.js +14 -0
  15. package/addon/components/event-timeline/event.hbs +5 -0
  16. package/addon/components/event-timeline.hbs +7 -0
  17. package/addon/components/event-timeline.js +423 -0
  18. package/addon/components/file-icon.js +1 -0
  19. package/addon/components/fleet-driver-listing.hbs +73 -0
  20. package/addon/components/fleet-driver-listing.js +117 -0
  21. package/addon/components/full-calendar/draggable.js +1 -0
  22. package/addon/components/full-calendar.js +1 -0
  23. package/addon/components/full-size.js +1 -0
  24. package/addon/components/global-search.hbs +84 -0
  25. package/addon/components/global-search.js +52 -0
  26. package/addon/components/horizontal-route.hbs +33 -0
  27. package/addon/components/horizontal-route.js +48 -0
  28. package/addon/components/integrated-order-details.hbs +117 -0
  29. package/addon/components/integrated-order-details.js +10 -0
  30. package/addon/components/leaflet-draw-control.js +147 -0
  31. package/addon/components/leaflet-rotated-marker.js +24 -0
  32. package/addon/components/live-map.hbs +63 -0
  33. package/addon/components/live-map.js +856 -0
  34. package/addon/components/map-container/toolbar/zones-panel.hbs +41 -0
  35. package/addon/components/map-container/toolbar/zones-panel.js +106 -0
  36. package/addon/components/map-container/toolbar-container.hbs +1 -0
  37. package/addon/components/map-container/toolbar-container.js +3 -0
  38. package/addon/components/map-container/toolbar.hbs +51 -0
  39. package/addon/components/map-container/toolbar.js +34 -0
  40. package/addon/components/map-container/topbar.hbs +1 -0
  41. package/addon/components/map-container/topbar.js +3 -0
  42. package/addon/components/map-container/view-switch.hbs +1 -0
  43. package/addon/components/map-container/view-switch.js +3 -0
  44. package/addon/components/map-container.hbs +1 -0
  45. package/addon/components/map-container.js +15 -0
  46. package/addon/components/modals/assign-driver.hbs +24 -0
  47. package/addon/components/modals/assign-driver.js +3 -0
  48. package/addon/components/modals/clone-config-form.hbs +6 -0
  49. package/addon/components/modals/contact-details.hbs +44 -0
  50. package/addon/components/modals/contact-form.hbs +56 -0
  51. package/addon/components/modals/driver-assign-order.hbs +67 -0
  52. package/addon/components/modals/driver-assign-vehicle.hbs +20 -0
  53. package/addon/components/modals/driver-assign-vendor.hbs +20 -0
  54. package/addon/components/modals/driver-details.hbs +85 -0
  55. package/addon/components/modals/driver-form.hbs +83 -0
  56. package/addon/components/modals/edit-meta-form.hbs +20 -0
  57. package/addon/components/modals/entity-form.hbs +57 -0
  58. package/addon/components/modals/entity-meta-field-prompt.hbs +6 -0
  59. package/addon/components/modals/fleet-details.hbs +31 -0
  60. package/addon/components/modals/fleet-form.hbs +26 -0
  61. package/addon/components/modals/fuel-report-details.hbs +34 -0
  62. package/addon/components/modals/fuel-report-form.hbs +26 -0
  63. package/addon/components/modals/group-details.hbs +42 -0
  64. package/addon/components/modals/group-form.hbs +54 -0
  65. package/addon/components/modals/install-prompt.hbs +21 -0
  66. package/addon/components/modals/map-field-form.hbs +9 -0
  67. package/addon/components/modals/map-field-group-form.hbs +6 -0
  68. package/addon/components/modals/map-layer-form.hbs +29 -0
  69. package/addon/components/modals/meta-field-form.hbs +8 -0
  70. package/addon/components/modals/meta-field-group-form.hbs +5 -0
  71. package/addon/components/modals/new-order-config.hbs +11 -0
  72. package/addon/components/modals/option-prompt.hbs +17 -0
  73. package/addon/components/modals/order-assign-driver.hbs +25 -0
  74. package/addon/components/modals/order-config-new-status.hbs +5 -0
  75. package/addon/components/modals/order-event.hbs +18 -0
  76. package/addon/components/modals/order-form.hbs +63 -0
  77. package/addon/components/modals/order-import.hbs +84 -0
  78. package/addon/components/modals/order-label.hbs +7 -0
  79. package/addon/components/modals/order-meta.hbs +12 -0
  80. package/addon/components/modals/order-new-activity.hbs +62 -0
  81. package/addon/components/modals/order-route-form.hbs +111 -0
  82. package/addon/components/modals/place-assign-vendor.hbs +20 -0
  83. package/addon/components/modals/place-details.hbs +105 -0
  84. package/addon/components/modals/place-form.hbs +30 -0
  85. package/addon/components/modals/point-map.hbs +16 -0
  86. package/addon/components/modals/policy-form.hbs +22 -0
  87. package/addon/components/modals/resend-verification-email.hbs +25 -0
  88. package/addon/components/modals/role-form.hbs +16 -0
  89. package/addon/components/modals/select-payment-method.hbs +12 -0
  90. package/addon/components/modals/service-area-form.hbs +31 -0
  91. package/addon/components/modals/set-password.hbs +15 -0
  92. package/addon/components/modals/uninstall-prompt.hbs +21 -0
  93. package/addon/components/modals/user-form.hbs +56 -0
  94. package/addon/components/modals/vehicle-details.hbs +227 -0
  95. package/addon/components/modals/vehicle-form.hbs +282 -0
  96. package/addon/components/modals/vendor-details.hbs +77 -0
  97. package/addon/components/modals/vendor-form.hbs +168 -0
  98. package/addon/components/modals/view-service-area.hbs +12 -0
  99. package/addon/components/modals/view-zone.hbs +12 -0
  100. package/addon/components/modals/zone-form.hbs +16 -0
  101. package/addon/components/operations-pane/pane.hbs +1 -0
  102. package/addon/components/order/header.hbs +48 -0
  103. package/addon/components/order/header.js +26 -0
  104. package/addon/components/order/place-card.hbs +51 -0
  105. package/addon/components/order/place-card.js +33 -0
  106. package/addon/components/order/schedule-card.hbs +35 -0
  107. package/addon/components/order/schedule-card.js +17 -0
  108. package/addon/components/order/sub-header/item.hbs +16 -0
  109. package/addon/components/order/sub-header.hbs +3 -0
  110. package/addon/components/order-config/activity-flow-editor.hbs +108 -0
  111. package/addon/components/order-config/activity-flow-editor.js +374 -0
  112. package/addon/components/order-config/details-editor.hbs +32 -0
  113. package/addon/components/order-config/details-editor.js +16 -0
  114. package/addon/components/order-config/entities-editor.hbs +84 -0
  115. package/addon/components/order-config/entities-editor.js +123 -0
  116. package/addon/components/order-config/fields-editor.hbs +122 -0
  117. package/addon/components/order-config/fields-editor.js +233 -0
  118. package/addon/components/order-config/selector.hbs +1 -0
  119. package/addon/components/order-config/selector.js +3 -0
  120. package/addon/components/order-config.hbs +66 -0
  121. package/addon/components/order-config.js +296 -0
  122. package/addon/components/order-list-overlay/driver-panel-title.hbs +6 -0
  123. package/addon/components/order-list-overlay/order.hbs +22 -0
  124. package/addon/components/order-list-overlay/order.js +3 -0
  125. package/addon/components/order-list-overlay.hbs +120 -0
  126. package/addon/components/order-list-overlay.js +227 -0
  127. package/addon/components/route-list.hbs +47 -0
  128. package/addon/components/route-list.js +11 -0
  129. package/addon/components/settings-window.hbs +15 -0
  130. package/addon/components/settings-window.js +62 -0
  131. package/addon/components/split-grid.js +1 -0
  132. package/addon/components/widget/fleet-ops-quickstart.hbs +40 -0
  133. package/addon/components/widget/fleet-ops-quickstart.js +58 -0
  134. package/addon/components/widget/issues.hbs +1 -0
  135. package/addon/components/widget/key-metrics.hbs +121 -0
  136. package/addon/components/widget/key-metrics.js +46 -0
  137. package/addon/components/widget/live-order-map.hbs +59 -0
  138. package/addon/components/widget/live-order-map.js +11 -0
  139. package/addon/components/widget/recent-orders.hbs +35 -0
  140. package/addon/components/widget/recent-orders.js +24 -0
  141. package/addon/components/widget/transaction-earnings.hbs +1 -0
  142. package/addon/components/widget/transactions.hbs +36 -0
  143. package/addon/components/widget/transactions.js +29 -0
  144. package/addon/components/widget/weekly-orders.hbs +1 -0
  145. package/addon/components/widget/weekly-orders.js +3 -0
  146. package/addon/controllers/application.js +40 -0
  147. package/addon/controllers/management/contacts/index.js +438 -0
  148. package/addon/controllers/management/drivers/index.js +868 -0
  149. package/addon/controllers/management/fleets/index.js +507 -0
  150. package/addon/controllers/management/fuel-reports/index.js +428 -0
  151. package/addon/controllers/management/issues/index.js +335 -0
  152. package/addon/controllers/management/places/index.js +571 -0
  153. package/addon/controllers/management/vehicles/index.js +616 -0
  154. package/addon/controllers/management/vendors/index.js +626 -0
  155. package/addon/controllers/management.js +5 -0
  156. package/addon/controllers/operations/orders/index/config/types.js +17 -0
  157. package/addon/controllers/operations/orders/index/config.js +13 -0
  158. package/addon/controllers/operations/orders/index/new.js +1213 -0
  159. package/addon/controllers/operations/orders/index/view.js +794 -0
  160. package/addon/controllers/operations/orders/index.js +674 -0
  161. package/addon/controllers/operations/scheduler/index.js +110 -0
  162. package/addon/controllers/operations/service-rates/index/edit.js +59 -0
  163. package/addon/controllers/operations/service-rates/index/new.js +369 -0
  164. package/addon/controllers/operations/service-rates/index.js +275 -0
  165. package/addon/controllers/operations/zones/index.js +470 -0
  166. package/addon/engine.js +19 -0
  167. package/addon/helpers/avatar-url.js +9 -0
  168. package/addon/helpers/extract-coordinates.js +6 -0
  169. package/addon/helpers/feature-group.js +14 -0
  170. package/addon/helpers/flow-raw-status.js +13 -0
  171. package/addon/helpers/format-point.js +13 -0
  172. package/addon/helpers/get-tip-amount.js +19 -0
  173. package/addon/helpers/is-active-route.js +1 -0
  174. package/addon/helpers/is-facilitator-supported-place.js +6 -0
  175. package/addon/helpers/is-not-facilitator-supported-place.js +6 -0
  176. package/addon/helpers/is-waypoint-record.js +6 -0
  177. package/addon/helpers/point-coordinates.js +10 -0
  178. package/addon/helpers/point-to-coordinates.js +28 -0
  179. package/addon/helpers/service-name.js +6 -0
  180. package/addon/helpers/waypoint-label.js +1 -0
  181. package/addon/instance-initializers/register-leaflet-draw-control-layer.js +17 -0
  182. package/addon/instance-initializers/register-leaflet-rotated-marker.js +17 -0
  183. package/addon/modifiers/translate-x.js +5 -0
  184. package/addon/routes/application.js +25 -0
  185. package/addon/routes/comms/chat.js +3 -0
  186. package/addon/routes/comms/intercom.js +3 -0
  187. package/addon/routes/comms.js +3 -0
  188. package/addon/routes/home.js +20 -0
  189. package/addon/routes/management/contacts/index.js +24 -0
  190. package/addon/routes/management/contacts.js +3 -0
  191. package/addon/routes/management/drivers/index.js +33 -0
  192. package/addon/routes/management/drivers.js +3 -0
  193. package/addon/routes/management/fleets/index.js +27 -0
  194. package/addon/routes/management/fleets.js +3 -0
  195. package/addon/routes/management/fuel-reports/index.js +23 -0
  196. package/addon/routes/management/fuel-reports.js +3 -0
  197. package/addon/routes/management/index.js +3 -0
  198. package/addon/routes/management/issues/index.js +24 -0
  199. package/addon/routes/management/issues.js +3 -0
  200. package/addon/routes/management/places/index.js +18 -0
  201. package/addon/routes/management/places.js +3 -0
  202. package/addon/routes/management/vehicles/index.js +35 -0
  203. package/addon/routes/management/vehicles.js +3 -0
  204. package/addon/routes/management/vendors/index.js +34 -0
  205. package/addon/routes/management/vendors.js +3 -0
  206. package/addon/routes/management.js +3 -0
  207. package/addon/routes/operations/dispatch.js +3 -0
  208. package/addon/routes/operations/orders/index/config/types.js +17 -0
  209. package/addon/routes/operations/orders/index/config.js +3 -0
  210. package/addon/routes/operations/orders/index/new.js +16 -0
  211. package/addon/routes/operations/orders/index/view.js +37 -0
  212. package/addon/routes/operations/orders/index.js +35 -0
  213. package/addon/routes/operations/orders.js +4 -0
  214. package/addon/routes/operations/scheduler/index.js +10 -0
  215. package/addon/routes/operations/scheduler.js +3 -0
  216. package/addon/routes/operations/service-rates/index/edit.js +35 -0
  217. package/addon/routes/operations/service-rates/index/new.js +15 -0
  218. package/addon/routes/operations/service-rates/index.js +49 -0
  219. package/addon/routes/operations/service-rates.js +10 -0
  220. package/addon/routes/operations/zones/index.js +25 -0
  221. package/addon/routes/operations/zones.js +7 -0
  222. package/addon/routes/operations.js +3 -0
  223. package/addon/routes.js +55 -0
  224. package/addon/services/map-manager.js +3 -0
  225. package/addon/services/service-areas.js +385 -0
  226. package/addon/templates/application.hbs +27 -0
  227. package/addon/templates/comms/chat.hbs +1 -0
  228. package/addon/templates/comms/intercom.hbs +1 -0
  229. package/addon/templates/comms.hbs +1 -0
  230. package/addon/templates/home.hbs +1 -0
  231. package/addon/templates/management/contacts/index.hbs +23 -0
  232. package/addon/templates/management/contacts.hbs +1 -0
  233. package/addon/templates/management/drivers/index.hbs +35 -0
  234. package/addon/templates/management/drivers.hbs +1 -0
  235. package/addon/templates/management/fleets/index.hbs +22 -0
  236. package/addon/templates/management/fleets.hbs +1 -0
  237. package/addon/templates/management/fuel-reports/index.hbs +21 -0
  238. package/addon/templates/management/fuel-reports.hbs +1 -0
  239. package/addon/templates/management/index.hbs +53 -0
  240. package/addon/templates/management/issues/index.hbs +21 -0
  241. package/addon/templates/management/issues.hbs +1 -0
  242. package/addon/templates/management/places/index.hbs +34 -0
  243. package/addon/templates/management/places.hbs +1 -0
  244. package/addon/templates/management/vehicles/index.hbs +22 -0
  245. package/addon/templates/management/vehicles.hbs +1 -0
  246. package/addon/templates/management/vendors/index.hbs +22 -0
  247. package/addon/templates/management/vendors.hbs +1 -0
  248. package/addon/templates/management.hbs +1 -0
  249. package/addon/templates/operations/dispatch.hbs +1 -0
  250. package/addon/templates/operations/orders/index/config/types.hbs +26 -0
  251. package/addon/templates/operations/orders/index/config.hbs +24 -0
  252. package/addon/templates/operations/orders/index/new.hbs +506 -0
  253. package/addon/templates/operations/orders/index/view.hbs +428 -0
  254. package/addon/templates/operations/orders/index.hbs +48 -0
  255. package/addon/templates/operations/orders.hbs +1 -0
  256. package/addon/templates/operations/scheduler/index.hbs +30 -0
  257. package/addon/templates/operations/scheduler.hbs +1 -0
  258. package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
  259. package/addon/templates/operations/service-rates/index/new.hbs +308 -0
  260. package/addon/templates/operations/service-rates/index.hbs +23 -0
  261. package/addon/templates/operations/service-rates.hbs +1 -0
  262. package/addon/templates/operations/zones/index.hbs +72 -0
  263. package/addon/templates/operations/zones.hbs +1 -0
  264. package/addon/templates/operations.hbs +1 -0
  265. package/addon/utils/calculate-percentage.js +1 -0
  266. package/addon/utils/extract-coordinates.js +1 -0
  267. package/addon/utils/format-currency.js +1 -0
  268. package/addon/utils/geojson/geo-json.js +17 -0
  269. package/addon/utils/is-facilitator-supported-place.js +14 -0
  270. package/addon/utils/to-model.js +1 -0
  271. package/app/components/cell/driver-name.js +1 -0
  272. package/app/components/custom-field-value.js +1 -0
  273. package/app/components/custom-field.js +1 -0
  274. package/app/components/display-place.js +1 -0
  275. package/app/components/driver-card-mini.js +1 -0
  276. package/app/components/driver-card.js +1 -0
  277. package/app/components/event-timeline/event.js +1 -0
  278. package/app/components/event-timeline.js +1 -0
  279. package/app/components/file-icon.js +1 -0
  280. package/app/components/fleet-driver-listing.js +1 -0
  281. package/app/components/full-calendar/draggable.js +1 -0
  282. package/app/components/full-calendar.js +1 -0
  283. package/app/components/full-size.js +1 -0
  284. package/app/components/global-search.js +1 -0
  285. package/app/components/horizontal-route.js +1 -0
  286. package/app/components/integrated-order-details.js +1 -0
  287. package/app/components/leaflet-draw-control.js +1 -0
  288. package/app/components/leaflet-rotated-marker.js +1 -0
  289. package/app/components/live-map.js +1 -0
  290. package/app/components/map-container/toolbar/zones-panel.js +1 -0
  291. package/app/components/map-container/toolbar-container.js +1 -0
  292. package/app/components/map-container/toolbar.js +1 -0
  293. package/app/components/map-container/topbar.js +1 -0
  294. package/app/components/map-container/view-switch.js +1 -0
  295. package/app/components/map-container.js +1 -0
  296. package/app/components/modals/assign-driver.js +1 -0
  297. package/app/components/modals/clone-config-form.js +1 -0
  298. package/app/components/modals/contact-details.js +1 -0
  299. package/app/components/modals/contact-form.js +1 -0
  300. package/app/components/modals/driver-assign-order.js +1 -0
  301. package/app/components/modals/driver-assign-vehicle.js +1 -0
  302. package/app/components/modals/driver-assign-vendor.js +1 -0
  303. package/app/components/modals/driver-details.js +1 -0
  304. package/app/components/modals/driver-form.js +1 -0
  305. package/app/components/modals/edit-meta-form.js +1 -0
  306. package/app/components/modals/entity-form.js +1 -0
  307. package/app/components/modals/entity-meta-field-prompt.js +1 -0
  308. package/app/components/modals/fleet-details.js +1 -0
  309. package/app/components/modals/fleet-form.js +1 -0
  310. package/app/components/modals/fuel-report-details.js +1 -0
  311. package/app/components/modals/fuel-report-form.js +1 -0
  312. package/app/components/modals/group-details.js +1 -0
  313. package/app/components/modals/group-form.js +1 -0
  314. package/app/components/modals/install-prompt.js +1 -0
  315. package/app/components/modals/map-field-form.js +1 -0
  316. package/app/components/modals/map-field-group-form.js +1 -0
  317. package/app/components/modals/map-layer-form.js +1 -0
  318. package/app/components/modals/meta-field-form.js +1 -0
  319. package/app/components/modals/meta-field-group-form.js +1 -0
  320. package/app/components/modals/new-order-config.js +1 -0
  321. package/app/components/modals/option-prompt.js +1 -0
  322. package/app/components/modals/order-assign-driver.js +1 -0
  323. package/app/components/modals/order-config-new-status.js +1 -0
  324. package/app/components/modals/order-event.js +1 -0
  325. package/app/components/modals/order-form.js +1 -0
  326. package/app/components/modals/order-import.js +1 -0
  327. package/app/components/modals/order-label.js +1 -0
  328. package/app/components/modals/order-meta.js +1 -0
  329. package/app/components/modals/order-new-activity.js +1 -0
  330. package/app/components/modals/order-route-form.js +1 -0
  331. package/app/components/modals/place-assign-vendor.js +1 -0
  332. package/app/components/modals/place-details.js +1 -0
  333. package/app/components/modals/place-form.js +1 -0
  334. package/app/components/modals/point-map.js +1 -0
  335. package/app/components/modals/policy-form.js +1 -0
  336. package/app/components/modals/resend-verification-email.js +1 -0
  337. package/app/components/modals/role-form.js +1 -0
  338. package/app/components/modals/select-payment-method.js +1 -0
  339. package/app/components/modals/service-area-form.js +1 -0
  340. package/app/components/modals/set-password.js +1 -0
  341. package/app/components/modals/uninstall-prompt.js +1 -0
  342. package/app/components/modals/user-form.js +1 -0
  343. package/app/components/modals/vehicle-details.js +1 -0
  344. package/app/components/modals/vehicle-form.js +1 -0
  345. package/app/components/modals/vendor-details.js +1 -0
  346. package/app/components/modals/vendor-form.js +1 -0
  347. package/app/components/modals/view-service-area.js +1 -0
  348. package/app/components/modals/view-zone.js +1 -0
  349. package/app/components/modals/zone-form.js +1 -0
  350. package/app/components/operations-pane/pane.js +1 -0
  351. package/app/components/order/header.js +1 -0
  352. package/app/components/order/place-card.js +1 -0
  353. package/app/components/order/schedule-card.js +1 -0
  354. package/app/components/order/sub-header/item.js +1 -0
  355. package/app/components/order/sub-header.js +1 -0
  356. package/app/components/order-config/activity-flow-editor.js +1 -0
  357. package/app/components/order-config/details-editor.js +1 -0
  358. package/app/components/order-config/entities-editor.js +1 -0
  359. package/app/components/order-config/fields-editor.js +1 -0
  360. package/app/components/order-config/selector.js +1 -0
  361. package/app/components/order-config.js +1 -0
  362. package/app/components/order-list-overlay/driver-panel-title.js +1 -0
  363. package/app/components/order-list-overlay/order.js +1 -0
  364. package/app/components/order-list-overlay.js +1 -0
  365. package/app/components/route-list.js +1 -0
  366. package/app/components/settings-window.js +1 -0
  367. package/app/components/split-grid.js +1 -0
  368. package/app/components/widget/issues.js +1 -0
  369. package/app/components/widget/key-metrics.js +1 -0
  370. package/app/components/widget/live-order-map.js +1 -0
  371. package/app/components/widget/recent-orders.js +1 -0
  372. package/app/components/widget/recommended-todo.js +1 -0
  373. package/app/components/widget/transaction-earnings.js +1 -0
  374. package/app/components/widget/transactions.js +1 -0
  375. package/app/components/widget/weekly-orders.js +1 -0
  376. package/app/controllers/application.js +1 -0
  377. package/app/controllers/management/contacts/index.js +1 -0
  378. package/app/controllers/management/drivers/index/details.js +1 -0
  379. package/app/controllers/management/drivers/index.js +1 -0
  380. package/app/controllers/management/fleets/index/details.js +1 -0
  381. package/app/controllers/management/fleets/index.js +1 -0
  382. package/app/controllers/management/fuel-reports/index.js +1 -0
  383. package/app/controllers/management/issues/index.js +1 -0
  384. package/app/controllers/management/places/index.js +1 -0
  385. package/app/controllers/management/vehicles/index.js +1 -0
  386. package/app/controllers/management/vendors/index/details/remarks.js +1 -0
  387. package/app/controllers/management/vendors/index/details.js +1 -0
  388. package/app/controllers/management/vendors/index.js +1 -0
  389. package/app/controllers/management.js +1 -0
  390. package/app/controllers/operations/orders/index/config/types.js +1 -0
  391. package/app/controllers/operations/orders/index/config.js +1 -0
  392. package/app/controllers/operations/orders/index/new.js +1 -0
  393. package/app/controllers/operations/orders/index/view.js +1 -0
  394. package/app/controllers/operations/scheduler/index.js +1 -0
  395. package/app/controllers/operations/service-rates/index/edit.js +1 -0
  396. package/app/controllers/operations/service-rates/index/new.js +1 -0
  397. package/app/controllers/operations/service-rates/index.js +1 -0
  398. package/app/controllers/operations/zones/index.js +1 -0
  399. package/app/helpers/avatar-url.js +1 -0
  400. package/app/helpers/extract-coordinates.js +1 -0
  401. package/app/helpers/feature-group.js +1 -0
  402. package/app/helpers/flow-raw-status.js +1 -0
  403. package/app/helpers/format-point.js +1 -0
  404. package/app/helpers/get-tip-amount.js +1 -0
  405. package/app/helpers/is-facilitator-supported-place.js +1 -0
  406. package/app/helpers/is-not-facilitator-supported-place.js +1 -0
  407. package/app/helpers/is-waypoint-record.js +1 -0
  408. package/app/helpers/point-coordinates.js +1 -0
  409. package/app/helpers/point-to-coordinates.js +1 -0
  410. package/app/helpers/service-name.js +1 -0
  411. package/app/helpers/waypoint-label.js +1 -0
  412. package/app/modifiers/translate-x.js +1 -0
  413. package/app/routes/home.js +1 -0
  414. package/app/routes/management/fleets/index/details.js +1 -0
  415. package/app/routes/operations/orders/index/config/types.js +1 -0
  416. package/app/routes/operations/orders/index/config.js +1 -0
  417. package/app/routes/operations/orders/index/new.js +1 -0
  418. package/app/routes/operations/orders/index/view.js +1 -0
  419. package/app/routes/operations/scheduler/index.js +1 -0
  420. package/app/routes/operations/scheduler.js +1 -0
  421. package/app/routes/operations/service-rates/index/edit.js +1 -0
  422. package/app/routes/operations/service-rates/index/new.js +1 -0
  423. package/app/services/map-manager.js +1 -0
  424. package/app/services/service-areas.js +1 -0
  425. package/app/templates/home.js +1 -0
  426. package/app/templates/management/fleets/index/details.js +1 -0
  427. package/app/templates/operations/orders/index/config/types.js +1 -0
  428. package/app/templates/operations/orders/index/config.js +1 -0
  429. package/app/templates/operations/orders/index/new.js +1 -0
  430. package/app/templates/operations/orders/index/view.js +1 -0
  431. package/app/templates/operations/scheduler/index.js +1 -0
  432. package/app/templates/operations/scheduler.js +1 -0
  433. package/app/templates/operations/service-rates/index/edit.js +1 -0
  434. package/app/templates/operations/service-rates/index/new.js +1 -0
  435. package/app/utils/calculate-percentage.js +1 -0
  436. package/app/utils/extract-coordinates.js +1 -0
  437. package/app/utils/format-currency.js +1 -0
  438. package/app/utils/geojson/geo-json.js +1 -0
  439. package/app/utils/is-facilitator-supported-place.js +1 -0
  440. package/app/utils/is-latitude.js +1 -0
  441. package/app/utils/to-model.js +1 -0
  442. package/assets/images/drawing-controls-spritesheet-inverted.png +0 -0
  443. package/config/environment.js +25 -0
  444. package/index.js +73 -0
  445. package/package.json +132 -0
  446. package/pnpm-lock.yaml +12613 -0
@@ -0,0 +1,1213 @@
1
+ import Controller, { inject as controller } from '@ember/controller';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { action, computed, setProperties, set, get } from '@ember/object';
5
+ import { not, equal, alias } from '@ember/object/computed';
6
+ import { isArray } from '@ember/array';
7
+ import { dasherize } from '@ember/string';
8
+ import { later } from '@ember/runloop';
9
+ import { OSRMv1, Control as RoutingControl } from '@fleetbase/leaflet-routing-machine';
10
+ import polyline from '@fleetbase/ember-core/utils/polyline';
11
+ import findClosestWaypoint from '@fleetbase/ember-core/utils/find-closest-waypoint';
12
+ import isNotEmpty from '@fleetbase/ember-core/utils/is-not-empty';
13
+ import getRoutingHost from '@fleetbase/ember-core/utils/get-routing-host';
14
+ import groupBy from '@fleetbase/ember-core/utils/macros/group-by';
15
+ import extractCoordinates from '@fleetbase/ember-core/utils/extract-coordinates';
16
+
17
+ L.Bounds.prototype.intersects = function (bounds) {
18
+ var min = this.min,
19
+ max = this.max,
20
+ min2 = bounds.min,
21
+ max2 = bounds.max,
22
+ xIntersects = max2.x >= min.x && min2.x <= max.x,
23
+ yIntersects = max2.y >= min.y && min2.y <= max.y;
24
+
25
+ return xIntersects && yIntersects;
26
+ };
27
+
28
+ export default class OperationsOrdersIndexNewController extends Controller {
29
+ @controller('management.places.index') placesController;
30
+ @controller('management.contacts.index') contactsController;
31
+ @controller('management.vendors.index') vendorsController;
32
+ @controller('operations.orders.index') ordersController;
33
+
34
+ @service modalsManager;
35
+ @service notifications;
36
+ @service loader;
37
+ @service currentUser;
38
+ @service hostRouter;
39
+ @service fileQueue;
40
+ @service fetch;
41
+ @service store;
42
+
43
+ @tracked order = this.store.createRecord('order', { meta: [] });
44
+ @tracked payload = this.store.createRecord('payload');
45
+ @tracked driversQuery = {};
46
+ @tracked meta = [];
47
+ @tracked entities = [];
48
+ @tracked waypoints = [];
49
+ @tracked types = [];
50
+ @tracked serviceRates = [];
51
+ @tracked selectedServiceRate;
52
+ @tracked selectedServiceQuote;
53
+ @tracked isCreatingOrder = false;
54
+ @tracked isMultipleDropoffOrder = false;
55
+ @tracked isViewingRoutePreview = false;
56
+ @tracked isOptimizingRoute = false;
57
+ @tracked optimizedRouteMarkers = [];
58
+ @tracked optimizedRoutePolyline;
59
+ @tracked isFetchingQuotes = false;
60
+ @tracked servicable = false;
61
+ @tracked scheduledDate;
62
+ @tracked scheduledTime;
63
+ @tracked leafletRoute;
64
+ @tracked leafletOptimizedRoute;
65
+ @tracked currentLeafletRoute;
66
+ @tracked leafletLayers = [];
67
+ @tracked routeProfile = 'driving';
68
+ @tracked routeProfileOptions = ['driving', 'bycicle', 'walking'];
69
+ @tracked podOptions = ['scan', 'signature'];
70
+ @tracked isViewingRoutePreview = false;
71
+ @tracked isCsvImportedOrder = false;
72
+ @tracked routePreviewArray = [];
73
+ @tracked previewRouteControl;
74
+ @tracked isSubscriptionValid = true;
75
+ @tracked isUsingIntegratedVendor = false;
76
+ @tracked integratedVendorServiceType;
77
+ @tracked invalidReason;
78
+ @tracked metadataButtons = [
79
+ {
80
+ type: 'default',
81
+ text: 'Edit metadata',
82
+ icon: 'edit',
83
+ onClick: this.editMetaData,
84
+ },
85
+ ];
86
+
87
+ @not('isServicable') isNotServicable;
88
+ @alias('currentUser.latitude') userLatitude;
89
+ @alias('currentUser.longitude') userLongitude;
90
+ @alias('ordersController.leafletMap') leafletMap;
91
+ @equal('isCsvImportedOrder', false) isNotCsvImportedOrder;
92
+ @groupBy('typeConfig.meta.fields', 'group') groupedMetaFields;
93
+
94
+ @computed('entities.length', 'isMultipleDropoffOrder', 'isFetchingQuotes', 'isSubscriptionValid', 'order.type', 'payload.{dropoff,pickup}', 'waypoints.length')
95
+ get isValid() {
96
+ const { isMultipleDropoffOrder, isSubscriptionValid, isFetchingQuotes } = this;
97
+ const isOrderTypeSet = isNotEmpty(this.order?.type);
98
+ const isWaypointsSet = this.waypoints?.length > 1;
99
+ const isPickupSet = isNotEmpty(this.payload?.pickup);
100
+ const isDropoffSet = isNotEmpty(this.payload?.dropoff);
101
+ // const isPayloadSet = this.entities?.length > 0;
102
+
103
+ if (isFetchingQuotes) {
104
+ return false;
105
+ }
106
+
107
+ if (!isSubscriptionValid) {
108
+ return false;
109
+ }
110
+
111
+ if (isMultipleDropoffOrder) {
112
+ return isOrderTypeSet && isWaypointsSet;
113
+ }
114
+
115
+ return isOrderTypeSet && isPickupSet && isDropoffSet;
116
+ }
117
+
118
+ @computed('payload.{dropoff,pickup,waypoints}', 'waypoints.[]')
119
+ get payloadCoordinates() {
120
+ let waypoints = [];
121
+ let coordinates = [];
122
+
123
+ waypoints.pushObjects([this.payload.pickup, ...this.waypoints.map((waypoint) => waypoint.place), this.payload.dropoff]);
124
+ waypoints.forEach((place) => {
125
+ if (place && place.get('longitude') && place.get('latitude')) {
126
+ if (place.hasInvalidCoordinates) {
127
+ return;
128
+ }
129
+
130
+ coordinates.pushObject([place.get('longitude'), place.get('latitude')]);
131
+ }
132
+ });
133
+
134
+ return coordinates;
135
+ }
136
+
137
+ @computed('payloadCoordinates.length', 'waypoints.[]') get isServicable() {
138
+ return this.payloadCoordinates.length >= 2;
139
+ }
140
+
141
+ @computed('routePreviewArray.[]') get routePreviewCoordinates() {
142
+ // return this.routePreviewArray.filter((place) => place.get('hasValidCoordinates')).map((place) => place.get('latlng'));
143
+ return (
144
+ this.routePreviewArray
145
+ // .filter((place) => place.get('hasValidCoordinates'))
146
+ .map((place) => place.get('extractedLatLng'))
147
+ );
148
+ }
149
+
150
+ @computed('order.type', 'types') get typeConfig() {
151
+ return this.types.find((type) => type.key === this.order.type);
152
+ }
153
+
154
+ @computed('order.type', 'types') get typeConfigIndex() {
155
+ return this.types.findIndex((type) => type.key === this.order.type);
156
+ }
157
+
158
+ @computed('entities.[]', 'waypoints.[]') get entitiesByImportId() {
159
+ const groups = [];
160
+
161
+ // create groups
162
+ this.waypoints.forEach((waypoint) => {
163
+ const importId = waypoint.place._import_id ?? null;
164
+
165
+ if (importId) {
166
+ const entities = this.entities.filter((entity) => entity._import_id === importId);
167
+
168
+ // if (entities.length === 0) {
169
+ // return;
170
+ // }
171
+
172
+ const group = {
173
+ importId,
174
+ waypoint,
175
+ entities,
176
+ };
177
+
178
+ groups.pushObject(group);
179
+ }
180
+ });
181
+
182
+ return groups;
183
+ }
184
+
185
+ @action dropdownAction(actionName, dd, ...params) {
186
+ if (typeof dd?.actions?.close === 'function') {
187
+ dd.actions.close();
188
+ }
189
+
190
+ if (typeof this[actionName] === 'function') {
191
+ this[actionName](...params);
192
+ }
193
+ }
194
+
195
+ @action createOrder() {
196
+ if (!this.isValid) {
197
+ return;
198
+ }
199
+
200
+ this.previewRoute(false);
201
+ this.loader.showLoader('body', 'Creating Order...');
202
+
203
+ const { order, groupedMetaFields, payload, entities, waypoints, isMultipleDropoffOrder } = this;
204
+ const route = this.leafletOptimizedRoute ? this.getOptimizedRoute() : this.getRoute();
205
+
206
+ // set service quote if applicable
207
+ if (this.selectedServiceQuote) {
208
+ order.service_quote_uuid = this.selectedServiceQuote;
209
+ }
210
+
211
+ try {
212
+ order
213
+ .serializeMeta()
214
+ .serializeMetaFromGroupedFields(groupedMetaFields)
215
+ .setPayload(payload)
216
+ .setRoute(route)
217
+ .get('payload')
218
+ .setWaypoints(waypoints, isMultipleDropoffOrder)
219
+ .setEntities(entities);
220
+ } catch (error) {
221
+ this.notifications.serverError(error);
222
+ this.loader.removeLoader();
223
+ return;
224
+ }
225
+
226
+ this.isCreatingOrder = true;
227
+
228
+ try {
229
+ return order
230
+ .save()
231
+ .then((order) => {
232
+ return this.transitionToRoute('operations.orders.index.view', order).then(() => {
233
+ this.notifications.success(`New Order ${order.public_id} Created`);
234
+ this.loader.removeLoader();
235
+ this.resetForm();
236
+ later(
237
+ this,
238
+ () => {
239
+ this.hostRouter.refresh();
240
+ },
241
+ 100
242
+ );
243
+ });
244
+ })
245
+ .catch((error) => {
246
+ this.isCreatingOrder = false;
247
+ this.notifications.serverError(error);
248
+ this.loader.removeLoader();
249
+ });
250
+ } catch (error) {
251
+ this.notifications.error(error.message);
252
+ this.loader.removeLoader();
253
+ }
254
+ }
255
+
256
+ @action importOrder() {
257
+ const checkQueue = () => {
258
+ const uploadQueue = this.modalsManager.getOption('uploadQueue');
259
+
260
+ if (uploadQueue.length) {
261
+ this.modalsManager.setOption('acceptButtonDisabled', false);
262
+ } else {
263
+ this.modalsManager.setOption('acceptButtonDisabled', true);
264
+ }
265
+ };
266
+
267
+ this.modalsManager.show('modals/order-import', {
268
+ title: 'Import order(s) with spreadsheets',
269
+ acceptButtonText: 'Start Upload',
270
+ acceptButtonScheme: 'magic',
271
+ acceptButtonIcon: 'upload',
272
+ acceptButtonDisabled: true,
273
+ isProcessing: false,
274
+ uploadQueue: [],
275
+ fileQueueColumns: [
276
+ { name: 'Type', valuePath: 'extension', key: 'type' },
277
+ { name: 'File Name', valuePath: 'name', key: 'fileName' },
278
+ { name: 'File Size', valuePath: 'size', key: 'fileSize' },
279
+ {
280
+ name: 'Upload Date',
281
+ valuePath: 'blob.lastModifiedDate',
282
+ key: 'uploadDate',
283
+ },
284
+ { name: '', valuePath: '', key: 'delete' },
285
+ ],
286
+ acceptedFileTypes: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'text/csv'],
287
+ queueFile: (file) => {
288
+ const uploadQueue = this.modalsManager.getOption('uploadQueue');
289
+
290
+ uploadQueue.pushObject(file);
291
+ checkQueue();
292
+ },
293
+ removeFile: (file) => {
294
+ const { queue } = file;
295
+ const uploadQueue = this.modalsManager.getOption('uploadQueue');
296
+
297
+ uploadQueue.removeObject(file);
298
+ queue.remove(file);
299
+ checkQueue();
300
+ },
301
+ confirm: async (modal) => {
302
+ const uploadQueue = this.modalsManager.getOption('uploadQueue');
303
+ const uploadedFiles = [];
304
+ const uploadTask = (file) => {
305
+ return new Promise((resolve) => {
306
+ this.fetch.uploadFile.perform(
307
+ file,
308
+ {
309
+ path: `uploads/order-imports/${this.currentUser.companyId}`,
310
+ type: `order_import`,
311
+ },
312
+ (uploadedFile) => {
313
+ uploadedFiles.pushObject(uploadedFile);
314
+
315
+ resolve(uploadedFile);
316
+ }
317
+ );
318
+ });
319
+ };
320
+
321
+ if (!uploadQueue.length) {
322
+ return this.notifications.warning('No files in queue to upload!');
323
+ }
324
+
325
+ modal.startLoading();
326
+ modal.setOption('acceptButtonText', 'Uploading...');
327
+
328
+ for (let i = 0; i < uploadQueue.length; i++) {
329
+ const file = uploadQueue.objectAt(i);
330
+
331
+ await uploadTask(file);
332
+ }
333
+
334
+ this.modalsManager.setOption('acceptButtonText', 'Processing...');
335
+ this.modalsManager.setOption('isProcessing', true);
336
+
337
+ const files = uploadedFiles.map((file) => file.id);
338
+
339
+ let results;
340
+
341
+ try {
342
+ results = await this.fetch.post('orders/process-imports', { files });
343
+ } catch (error) {
344
+ return this.notifications.serverError(error);
345
+ }
346
+
347
+ // import places
348
+ if (isArray(results?.places)) {
349
+ this.isMultipleDropoffOrder = true;
350
+
351
+ // map into place models
352
+ const waypoints = results.places.map((_place) => {
353
+ const place = this.store.createRecord('place', _place);
354
+
355
+ return this.store.createRecord('waypoint', { place });
356
+ });
357
+
358
+ this.waypoints = waypoints;
359
+ }
360
+
361
+ // import entities
362
+ if (isArray(results?.entities)) {
363
+ // map into entity models
364
+ const entities = results.entities.map((entity) => {
365
+ return this.store.createRecord('entity', entity);
366
+ });
367
+
368
+ this.entities = entities;
369
+ }
370
+
371
+ this.notifications.success('Import completed.');
372
+ this.isCsvImportedOrder = true;
373
+ this.previewDraftOrderRoute(this.payload, this.waypoints, this.isMultipleDropoffOrder);
374
+ modal.done();
375
+ },
376
+ decline: (modal) => {
377
+ this.modalsManager.setOption('uploadQueue', []);
378
+ this.fileQueue.flush();
379
+
380
+ modal.done();
381
+ },
382
+ });
383
+ }
384
+
385
+ @action async toggleAdhoc(on) {
386
+ const defaultDistanceInMeters = 5000;
387
+
388
+ if (on) {
389
+ const company = this.store.peekRecord('company', this.currentUser.companyId);
390
+ this.order.adhoc_distance = company?.options?.fleetops?.adhoc_distance ?? defaultDistanceInMeters;
391
+ } else {
392
+ this.order.adhoc_distance = defaultDistanceInMeters;
393
+ }
394
+
395
+ this.order.adhoc = on;
396
+ }
397
+
398
+ @action async toggleProofOfDelivery(on) {
399
+ this.order.pod_required = on;
400
+
401
+ if (on) {
402
+ this.order.pod_method = 'scan';
403
+ } else {
404
+ this.order.pod_method = null;
405
+ }
406
+ }
407
+
408
+ @action async checkServiceRates(shouldCheck) {
409
+ this.servicable = shouldCheck;
410
+ const params = {
411
+ coordinates: this.getCoordinatesFromPayload().join(';'),
412
+ };
413
+ let serviceRates = [];
414
+
415
+ if (this.isUsingIntegratedVendor) {
416
+ params.facilitator = this.order.facilitator.public_id;
417
+ }
418
+
419
+ if (shouldCheck) {
420
+ try {
421
+ serviceRates = await this.fetch.get(`service-rates/for-route`, params);
422
+
423
+ serviceRates.unshiftObject({
424
+ service_name: 'Quote from all Service Rates',
425
+ id: 'all',
426
+ });
427
+ } catch (error) {
428
+ this.notifications.serverError(error);
429
+ }
430
+
431
+ this.serviceRates = serviceRates;
432
+ }
433
+ }
434
+
435
+ @action newPlace() {
436
+ this.placesController?.createPlace();
437
+ }
438
+
439
+ @action async getQuotes(service) {
440
+ this.isFetchingQuotes = true;
441
+
442
+ let payload = this.payload.serialize();
443
+ let route = this.getRoute();
444
+ let distance = get(route, 'payload.summary.totalDistance');
445
+ let time = get(route, 'payload.summary.totalTime');
446
+ let service_type = this.order.type;
447
+ let scheduled_at = this.order.scheduled_at;
448
+ let facilitator = this.order.facilitator?.get('public_id');
449
+ let is_route_optimized = this.order.get('is_route_optimized');
450
+ let { waypoints, entities } = this;
451
+ let places = [];
452
+
453
+ if (this.payloadCoordinates?.length < 2) {
454
+ this.isFetchingQuotes = false;
455
+ return;
456
+ }
457
+
458
+ if (this.isUsingIntegratedVendor && this.integratedVendorServiceType) {
459
+ service_type = this.integratedVendorServiceType;
460
+ }
461
+
462
+ // get place instances from WaypointModel
463
+ for (let i = 0; i < waypoints.length; i++) {
464
+ let place = await waypoints[i].place;
465
+
466
+ places.pushObject(place);
467
+ }
468
+
469
+ setProperties(payload, { waypoints: places, entities });
470
+
471
+ if (!payload.type && this.order.type) {
472
+ setProperties(payload, { type: this.order.type });
473
+ }
474
+
475
+ this.fetch
476
+ .post('service-quotes/preliminary', {
477
+ payload,
478
+ distance,
479
+ time,
480
+ service,
481
+ service_type,
482
+ facilitator,
483
+ scheduled_at,
484
+ is_route_optimized,
485
+ })
486
+ .then((serviceQuotes) => {
487
+ set(this, 'serviceQuotes', isArray(serviceQuotes) ? serviceQuotes : []);
488
+
489
+ if (this.serviceQuotes.length && this.isUsingIntegratedVendor) {
490
+ set(this, 'selectedServiceQuote', this.serviceQuotes.firstObject?.uuid);
491
+ }
492
+ })
493
+ .catch(() => {
494
+ this.notifications.warning('Failed to fetch service quotes for this order.');
495
+ })
496
+ .finally(() => {
497
+ this.isFetchingQuotes = false;
498
+ });
499
+ }
500
+
501
+ @action scheduleOrder(dateInstance) {
502
+ this.order.scheduled_at = dateInstance.toDate();
503
+ }
504
+
505
+ @action setupInterface() {
506
+ this.leafletMap?.liveMap?.hideDrivers();
507
+ this.leafletMap?.liveMap?.hideRoutes();
508
+
509
+ // track all layers added from this view
510
+ this.leafletMap?.on('layeradd', ({ layer }) => {
511
+ // disable dragging of layer
512
+ layer.dragging?.disable();
513
+
514
+ if (!this.leafletLayers?.includes(layer)) {
515
+ this.leafletLayers.pushObject(layer);
516
+ }
517
+ });
518
+
519
+ // switch to map mode
520
+ this.ordersController.setLayoutMode('map');
521
+ }
522
+
523
+ @action resetInterface() {
524
+ this.leafletMap?.liveMap?.showDrivers();
525
+ this.leafletMap?.liveMap?.showRoutes();
526
+ }
527
+
528
+ @action getRoute() {
529
+ const details = this.leafletRoute;
530
+ const route = this.store.createRecord('route', { details });
531
+
532
+ return route;
533
+ }
534
+
535
+ @action getOptimizedRoute() {
536
+ const details = this.leafletOptimizedRoute;
537
+ const route = this.store.createRecord('route', { details });
538
+
539
+ return route;
540
+ }
541
+
542
+ @action setOptimizedRoute(route, trip, waypoints) {
543
+ let summary = { totalDistance: trip.distance, totalTime: trip.duration };
544
+ let payload = {
545
+ optimized: true,
546
+ coordinates: route,
547
+ waypoints,
548
+ trip,
549
+ summary,
550
+ };
551
+
552
+ this.leafletOptimizedRoute = payload;
553
+ }
554
+
555
+ @action removeRoutingControlPreview() {
556
+ const leafletMap = this.leafletMap;
557
+ const previewRouteControl = this.previewRouteControl;
558
+
559
+ let removed = false;
560
+
561
+ if (leafletMap && previewRouteControl instanceof RoutingControl) {
562
+ try {
563
+ previewRouteControl.remove();
564
+ removed = true;
565
+ } catch (e) {
566
+ // silent
567
+ }
568
+
569
+ if (!removed) {
570
+ try {
571
+ leafletMap.removeControl(previewRouteControl);
572
+ } catch (e) {
573
+ // silent
574
+ }
575
+ }
576
+ }
577
+
578
+ if (!removed) {
579
+ this.forceRemoveRoutePreview();
580
+ }
581
+ }
582
+
583
+ @action forceRemoveRoutePreview() {
584
+ const { leafletMap } = this;
585
+
586
+ leafletMap.eachLayer((layer) => {
587
+ if (layer instanceof L.Polyline || layer instanceof L.Marker) {
588
+ try {
589
+ layer.remove();
590
+ } catch (error) {
591
+ // silent error just continue with order processing if any
592
+ }
593
+ }
594
+ });
595
+ }
596
+
597
+ @action removePreviewRouteLayers() {
598
+ const { currentLeafletRoute, leafletMap } = this;
599
+
600
+ if (currentLeafletRoute) {
601
+ // target is the route, and waypoints is the markers
602
+ const { target, waypoints } = currentLeafletRoute;
603
+
604
+ leafletMap.removeLayer(target);
605
+ waypoints?.forEach((waypoint) => {
606
+ try {
607
+ leafletMap.removeLayer(waypoint);
608
+ } catch (error) {
609
+ // silent error just continue with order processing if any
610
+ }
611
+ });
612
+ }
613
+ }
614
+
615
+ @action clearLayers() {
616
+ const { leafletLayers, leafletMap } = this;
617
+
618
+ leafletLayers?.forEach((layer) => {
619
+ try {
620
+ leafletMap.removeLayer(layer);
621
+ } catch (error) {
622
+ // silent error just continue with order processing if any
623
+ }
624
+ });
625
+ }
626
+
627
+ @action createPlaceArrayFromPayload(payload, waypoints, isMultipleDropoffOrder = false) {
628
+ const routePreviewArray = [];
629
+
630
+ if (isMultipleDropoffOrder) {
631
+ for (let i = 0; i < waypoints.length; i++) {
632
+ if (waypoints[i].place) {
633
+ routePreviewArray.pushObject(waypoints[i].place);
634
+ }
635
+ }
636
+ } else {
637
+ if (payload.pickup) {
638
+ routePreviewArray.pushObject(payload.pickup);
639
+ }
640
+
641
+ if (payload.dropoff) {
642
+ routePreviewArray.pushObject(payload.dropoff);
643
+ }
644
+ }
645
+
646
+ return routePreviewArray;
647
+ }
648
+
649
+ @action createCoordinatesFromRoutePlaceArray(array) {
650
+ return array.filter((place) => place.get('hasValidCoordinates')).map((place) => place.get('extractedLatLng'));
651
+ }
652
+
653
+ @action previewDraftOrderRoute(payload, waypoints, isMultipleDropoffOrder = false) {
654
+ const leafletMap = this.leafletMap;
655
+
656
+ // if existing route preview on the map - remove it
657
+ this.removeRoutingControlPreview();
658
+ this.removeOptimizedRoute();
659
+ this.clearLayers();
660
+
661
+ if (!this.isRoutePreviewAnimationActive) {
662
+ this.previewRoute(true);
663
+ }
664
+
665
+ this.isViewingRoutePreview = true;
666
+ this.routePreviewArray = this.createPlaceArrayFromPayload(payload, waypoints, isMultipleDropoffOrder);
667
+
668
+ const canPreviewRoute = this.routePreviewArray.length > 0;
669
+
670
+ if (canPreviewRoute) {
671
+ const routingHost = getRoutingHost(payload, waypoints);
672
+ const router = new OSRMv1({
673
+ serviceUrl: `${routingHost}/route/v1`,
674
+ profile: 'driving',
675
+ });
676
+
677
+ console.log('[this.routePreviewArray]', this.routePreviewArray);
678
+ console.log('[this.routePreviewCoordinates]', this.routePreviewCoordinates);
679
+
680
+ this.previewRouteControl = new RoutingControl({
681
+ waypoints: this.routePreviewCoordinates,
682
+ alternativeClassName: 'hidden',
683
+ addWaypoints: false,
684
+ router,
685
+ }).addTo(leafletMap);
686
+
687
+ this.previewRouteControl.on('routesfound', (event) => {
688
+ const { routes } = event;
689
+ const leafletRoute = routes.firstObject;
690
+ this.currentLeafletRoute = event;
691
+
692
+ this.setProperties({ leafletRoute });
693
+ });
694
+
695
+ if (this.routePreviewCoordinates.length === 1) {
696
+ leafletMap.flyTo(this.routePreviewCoordinates[0], 18);
697
+ leafletMap.once('moveend', function () {
698
+ leafletMap.panBy([200, 0]);
699
+ });
700
+ } else {
701
+ leafletMap.flyToBounds(this.routePreviewCoordinates, {
702
+ paddingBottomRight: [300, 0],
703
+ maxZoom: this.routePreviewCoordinates.length === 2 ? 15 : 14,
704
+ animate: true,
705
+ });
706
+ leafletMap.once('moveend', function () {
707
+ leafletMap.panBy([150, 0]);
708
+ });
709
+ }
710
+ } else {
711
+ this.notifications.warning('No route to preview.');
712
+ }
713
+ }
714
+
715
+ @action previewRoute(isViewingRoutePreview) {
716
+ this.isViewingRoutePreview = isViewingRoutePreview;
717
+ this.isRoutePreviewAnimationActive = isViewingRoutePreview;
718
+
719
+ if (isViewingRoutePreview === true) {
720
+ this.previewDraftOrderRoute(this.payload, this.waypoints, this.isMultipleDropoffOrder);
721
+ }
722
+
723
+ if (isViewingRoutePreview === false) {
724
+ this.removeRoutingControlPreview();
725
+ this.removeOptimizedRoute();
726
+ this.removePreviewRouteLayers();
727
+ this.clearLayers();
728
+ }
729
+ }
730
+
731
+ @action async optimizeRoute() {
732
+ this.isOptimizingRoute = true;
733
+
734
+ const leafletMap = this.leafletMap;
735
+ const coordinates = this.getCoordinatesFromPayload();
736
+ const routingHost = getRoutingHost(this.payload, this.waypoints);
737
+
738
+ const response = await this.fetch.routing(coordinates, { source: 'any', destination: 'any', annotations: true }, { host: routingHost }).catch(() => {
739
+ this.notifications.error('Route optimization failed, check route entry and try again.');
740
+ this.isOptimizingRoute = false;
741
+ });
742
+
743
+ this.isOptimizingRoute = false;
744
+
745
+ if (response && response.code === 'Ok') {
746
+ // remove current route display
747
+ this.removeRoutingControlPreview();
748
+ this.removeOptimizedRoute(leafletMap);
749
+
750
+ let trip = response.trips.firstObject;
751
+ let route = polyline.decode(trip.geometry);
752
+ let sortedWaypoints = [];
753
+ let optimizedRouteMarkers = [];
754
+
755
+ if (response.waypoints && isArray(response.waypoints)) {
756
+ const responseWaypoints = response.waypoints.sortBy('waypoint_index');
757
+
758
+ this.setOptimizedRoute(route, trip, responseWaypoints);
759
+
760
+ for (let i = 0; i < responseWaypoints.length; i++) {
761
+ const optimizedWaypoint = responseWaypoints.objectAt(i);
762
+ const optimizedWaypointLongitude = optimizedWaypoint.location.firstObject;
763
+ const optimizedWaypointLatitude = optimizedWaypoint.location.lastObject;
764
+ const waypointModel = findClosestWaypoint(optimizedWaypointLatitude, optimizedWaypointLongitude, this.waypoints);
765
+ // eslint-disable-next-line no-undef
766
+ // const optimizedWaypointMarker = new L.Marker(optimizedWaypoint.location.reverse()).addTo(leafletMap);
767
+ const optimizedWaypointMarker = new L.Marker(extractCoordinates(optimizedWaypoint.location)).addTo(leafletMap);
768
+
769
+ sortedWaypoints.pushObject(waypointModel);
770
+ optimizedRouteMarkers.pushObject(optimizedWaypointMarker);
771
+ }
772
+
773
+ this.waypoints = sortedWaypoints;
774
+ this.optimizedRouteMarkers = optimizedRouteMarkers;
775
+ }
776
+
777
+ // set order as route optimized
778
+ this.order.set('is_route_optimized', true);
779
+
780
+ // refetch quotes
781
+ if (this.isUsingIntegratedVendor) {
782
+ this.getQuotes();
783
+ }
784
+
785
+ // eslint-disable-next-line no-undef
786
+ let optimizedRoute = (this.optimizedRoutePolyline = new L.Polyline(route, { color: 'red' }).addTo(leafletMap));
787
+ // leafletMap.addLayer(optimizedRoute);
788
+ leafletMap.flyToBounds(optimizedRoute.getBounds(), {
789
+ paddingBottomRight: [0, 600],
790
+ animate: true,
791
+ maxZoom: 13,
792
+ });
793
+ } else {
794
+ this.notifications.error('Route optimization failed, check route entry and try again.');
795
+ this.isOptimizingRoute = false;
796
+ }
797
+ }
798
+
799
+ @action removeOptimizedRoute(_leafletMap = null) {
800
+ this.leafletOptimizedRoute = undefined;
801
+
802
+ const leafletMap = _leafletMap || this.leafletMap;
803
+
804
+ if (!leafletMap) {
805
+ return;
806
+ }
807
+
808
+ if (this.optimizedRoutePolyline) {
809
+ leafletMap.removeLayer(this.optimizedRoutePolyline);
810
+ }
811
+
812
+ for (let i = 0; i < this.optimizedRouteMarkers.length; i++) {
813
+ let marker = this.optimizedRouteMarkers.objectAt(i);
814
+
815
+ leafletMap.removeLayer(marker);
816
+ }
817
+ }
818
+
819
+ @action getCoordinatesFromPayload() {
820
+ this.notifyPropertyChange('payloadCoordinates');
821
+
822
+ return this.payloadCoordinates;
823
+ }
824
+
825
+ @action toggleMultiDropOrder(isMultipleDropoffOrder) {
826
+ this.isMultipleDropoffOrder = isMultipleDropoffOrder;
827
+
828
+ if (isMultipleDropoffOrder) {
829
+ this.addWaypoint();
830
+ } else {
831
+ this.clearWaypoints();
832
+ }
833
+ }
834
+
835
+ @action resetForm() {
836
+ const order = this.store.createRecord('order', { meta: [] });
837
+ const payload = this.store.createRecord('payload');
838
+ const driversQuery = {};
839
+ const meta = [];
840
+ const entities = [];
841
+ const waypoints = [];
842
+ const types = [];
843
+ const isCreatingOrder = false;
844
+ const isMultipleDropoffOrder = false;
845
+ const leafletRoute = undefined;
846
+ const serviceRates = [];
847
+ const selectedServiceRate = undefined;
848
+ const selectedServiceQuote = undefined;
849
+ const servicable = false;
850
+
851
+ this.removeRoutingControlPreview();
852
+ this.removeOptimizedRoute();
853
+ this.setProperties({
854
+ order,
855
+ payload,
856
+ driversQuery,
857
+ meta,
858
+ entities,
859
+ waypoints,
860
+ types,
861
+ isCreatingOrder,
862
+ isMultipleDropoffOrder,
863
+ leafletRoute,
864
+ serviceRates,
865
+ selectedServiceQuote,
866
+ selectedServiceRate,
867
+ servicable,
868
+ });
869
+ this.resetInterface();
870
+ }
871
+
872
+ @action setConfig(type) {
873
+ if (!type) {
874
+ return;
875
+ }
876
+
877
+ set(this, 'order.type', type);
878
+ this.meta = [];
879
+ this.applyCustomMetaFields(type);
880
+ }
881
+
882
+ @action setOrderFacilitator(model) {
883
+ this.order.set('facilitator', model);
884
+ this.order.set('driver', null);
885
+
886
+ this.isUsingIntegratedVendor = model.isIntegratedVendor;
887
+ this.servicable = model.isIntegratedVendor;
888
+
889
+ if (model.service_types?.length) {
890
+ this.integratedVendorServiceType = model.service_types.firstObject.key;
891
+ }
892
+
893
+ if (model.isIntegratedVendor) {
894
+ this.getQuotes();
895
+ }
896
+
897
+ if (model) {
898
+ this.driversQuery = { facilitator: model.id };
899
+ }
900
+ }
901
+
902
+ @action selectIntegratedServiceType(key) {
903
+ this.integratedVendorServiceType = key;
904
+
905
+ if (this.isUsingIntegratedVendor) {
906
+ this.getQuotes();
907
+ }
908
+ }
909
+
910
+ @action addCustomField() {
911
+ let label, value;
912
+
913
+ this.modalsManager.show('modals/meta-field-form', {
914
+ title: 'Add custom field to order',
915
+ acceptButtonIcon: 'check',
916
+ acceptButtonIconPrefix: 'fas',
917
+ acceptButtonText: 'Done',
918
+ declineButtonIcon: 'times',
919
+ declineButtonIconPrefix: 'fas',
920
+ label,
921
+ value,
922
+ confirm: (modal) => {
923
+ const label = modal.getOption('label');
924
+ const value = modal.getOption('value');
925
+
926
+ if (!label) {
927
+ return this.notifications.warning('Custom field must have a label');
928
+ }
929
+
930
+ if (!value) {
931
+ return this.notifications.warning('Custom field must have a value');
932
+ }
933
+
934
+ modal.startLoading();
935
+
936
+ this.order.meta.pushObject({
937
+ key: dasherize(label),
938
+ label,
939
+ value,
940
+ });
941
+
942
+ modal.done();
943
+ },
944
+ });
945
+ }
946
+
947
+ @action editCustomField(index) {
948
+ const metaField = this.order.meta.objectAt(index);
949
+ const { label, value } = metaField;
950
+
951
+ this.modalsManager.show('modals/meta-field-form', {
952
+ title: 'Edit custom field',
953
+ acceptButtonIcon: 'save',
954
+ acceptButtonText: 'Save Changes',
955
+ label,
956
+ value,
957
+ confirm: (modal) => {
958
+ const label = modal.getOption('label');
959
+ const value = modal.getOption('value');
960
+
961
+ if (!label) {
962
+ return this.notifications.warning('Custom field must have a label');
963
+ }
964
+
965
+ if (!value) {
966
+ return this.notifications.warning('Custom field must have a value');
967
+ }
968
+
969
+ modal.startLoading();
970
+
971
+ this.order.meta.replace(index, 1, [
972
+ {
973
+ key: dasherize(label),
974
+ label,
975
+ value,
976
+ },
977
+ ]);
978
+
979
+ modal.done();
980
+ },
981
+ });
982
+ }
983
+
984
+ @action editMetaData() {
985
+ let { meta } = this.order;
986
+
987
+ if (!isArray(meta)) {
988
+ meta = [];
989
+ }
990
+
991
+ this.modalsManager.show('modals/edit-meta-form', {
992
+ title: 'Edit Metadata',
993
+ hideDeclineButton: true,
994
+ acceptButtonIcon: 'check',
995
+ acceptButtonIconPrefix: 'fas',
996
+ acceptButtonText: 'Done',
997
+ meta,
998
+ addMetaField: (meta) => {
999
+ const label = 'New field';
1000
+ meta.pushObject({
1001
+ key: dasherize(label),
1002
+ label,
1003
+ value: null,
1004
+ });
1005
+ },
1006
+ removeMetaField: (meta, index) => {
1007
+ meta.removeAt(index);
1008
+ },
1009
+ confirm: (modal) => {
1010
+ const meta = modal.getOption('meta');
1011
+
1012
+ this.order.meta = meta;
1013
+
1014
+ modal.done();
1015
+ },
1016
+ });
1017
+ }
1018
+
1019
+ @action removeMeta(meta) {
1020
+ this.meta.removeObject(meta);
1021
+ }
1022
+
1023
+ @action setPayloadPlace(prop, place) {
1024
+ this.payload[prop] = place;
1025
+
1026
+ // this.previewRoute(true);
1027
+ this.previewDraftOrderRoute(this.payload, this.waypoints, this.isMultipleDropoffOrder);
1028
+
1029
+ if (this.isUsingIntegratedVendor) {
1030
+ this.getQuotes();
1031
+ }
1032
+ }
1033
+
1034
+ @action sortWaypoints({ sourceList, sourceIndex, targetList, targetIndex }) {
1035
+ if (sourceList === targetList && sourceIndex === targetIndex) {
1036
+ return;
1037
+ }
1038
+
1039
+ const item = sourceList.objectAt(sourceIndex);
1040
+
1041
+ sourceList.removeAt(sourceIndex);
1042
+ targetList.insertAt(targetIndex, item);
1043
+
1044
+ if (this.isViewingRoutePreview) {
1045
+ this.previewDraftOrderRoute(this.payload, this.waypoints, this.isMultipleDropoffOrder);
1046
+ }
1047
+ }
1048
+
1049
+ @action addWaypoint() {
1050
+ const waypoint = this.store.createRecord('waypoint');
1051
+
1052
+ this.waypoints.pushObject(waypoint);
1053
+ }
1054
+
1055
+ @action setWaypointPlace(index, place) {
1056
+ if (!this.waypoints[index]) {
1057
+ return;
1058
+ }
1059
+
1060
+ this.waypoints[index].place = place;
1061
+
1062
+ if (this.waypoints.length) {
1063
+ this.previewDraftOrderRoute(this.payload, this.waypoints, this.isMultipleDropoffOrder);
1064
+ }
1065
+
1066
+ if (this.isUsingIntegratedVendor) {
1067
+ this.getQuotes();
1068
+ }
1069
+ }
1070
+
1071
+ @action removeWaypoint(waypoint) {
1072
+ if (this.isMultipleDropoffOrder && this.waypoints.length === 1) {
1073
+ return;
1074
+ }
1075
+
1076
+ this.waypoints.removeObject(waypoint);
1077
+
1078
+ if (this.waypoints.length === 1) {
1079
+ this.previewRoute(false);
1080
+ } else {
1081
+ this.previewDraftOrderRoute(this.payload, this.waypoints, this.isMultipleDropoffOrder);
1082
+ }
1083
+ }
1084
+
1085
+ @action clearWaypoints() {
1086
+ this.waypoints.clear();
1087
+
1088
+ if (this.isViewingRoutePreview) {
1089
+ this.previewRoute(false);
1090
+ }
1091
+ }
1092
+
1093
+ @action setEntityDestionation(index, { target }) {
1094
+ const { value } = target;
1095
+
1096
+ this.entities[index].destination_uuid = value;
1097
+ }
1098
+
1099
+ @action addEntity(importId = null) {
1100
+ const entity = this.store.createRecord('entity', {
1101
+ _import_id: importId,
1102
+ });
1103
+
1104
+ this.entities.pushObject(entity);
1105
+ }
1106
+
1107
+ @action removeEntity(entity) {
1108
+ if (this.entities.length === 1) {
1109
+ return;
1110
+ }
1111
+
1112
+ if (!entity.get('isNew')) {
1113
+ return entity.destroyRecord();
1114
+ }
1115
+
1116
+ this.entities.removeObject(entity);
1117
+ }
1118
+
1119
+ @action editEntity(entity) {
1120
+ this.modalsManager.show('modals/entity-form', {
1121
+ title: 'Edit Item',
1122
+ acceptButtonText: 'Save Changes',
1123
+ entity,
1124
+ uploadNewPhoto: (file) => {
1125
+ if (entity.get('isNew')) {
1126
+ const { queue } = file;
1127
+ const fileUrl = URL.createObjectURL(file.blob);
1128
+
1129
+ this.modalsManager.setOption('pendingFileUpload', file);
1130
+ entity.set('photo_url', fileUrl);
1131
+ // entity.set('photo_upload', fileUrl);
1132
+ queue.remove(file);
1133
+ return;
1134
+ }
1135
+
1136
+ return this.fetch.uploadFile.perform(
1137
+ file,
1138
+ {
1139
+ path: `uploads/${this.currentUser.companyId}/entities/${entity.id}`,
1140
+ subject_uuid: entity.id,
1141
+ subject_type: `entity`,
1142
+ type: `entity_photo`,
1143
+ },
1144
+ (uploadedFile) => {
1145
+ entity.setProperties({
1146
+ photo_uuid: uploadedFile.id,
1147
+ photo_url: uploadedFile.s3url,
1148
+ photo: uploadedFile,
1149
+ });
1150
+ }
1151
+ );
1152
+ },
1153
+ confirm: async (modal) => {
1154
+ modal.startLoading();
1155
+
1156
+ const pendingFileUpload = modal.getOption('pendingFileUpload');
1157
+
1158
+ return entity.save().then(() => {
1159
+ if (pendingFileUpload) {
1160
+ return modal.invoke('uploadNewPhoto', pendingFileUpload);
1161
+ }
1162
+ });
1163
+ },
1164
+ });
1165
+ }
1166
+
1167
+ @action transitionBack() {
1168
+ return this.transitionToRoute('operations.orders.index');
1169
+ }
1170
+
1171
+ @action async newFacilitator() {
1172
+ const type = await this.modalsManager.userSelectOption('Select facilitator type', ['contact', 'vendor']);
1173
+
1174
+ if (type === 'vendor') {
1175
+ return this.vendorsController.createVendor();
1176
+ }
1177
+
1178
+ if (type === 'contact') {
1179
+ return this.contactsController.createContact();
1180
+ }
1181
+ }
1182
+
1183
+ @action async newCustomer() {
1184
+ const type = await this.modalsManager.userSelectOption('Select customer type', ['contact', 'vendor']);
1185
+
1186
+ if (type === 'vendor') {
1187
+ return this.vendorsController.createVendor();
1188
+ }
1189
+
1190
+ if (type === 'contact') {
1191
+ return this.contactsController.createContact();
1192
+ }
1193
+ }
1194
+
1195
+ @action applyCustomMetaFields(typeKey) {
1196
+ const type = this.types.find((type) => type.key === typeKey);
1197
+
1198
+ if (!type || !type.meta) {
1199
+ return;
1200
+ }
1201
+
1202
+ if (isArray(type.meta.fields)) {
1203
+ for (let i = 0; i < type.meta.fields.length; i++) {
1204
+ let field = type.meta.fields[i];
1205
+
1206
+ this.meta.pushObject({
1207
+ ...field,
1208
+ value: null,
1209
+ });
1210
+ }
1211
+ }
1212
+ }
1213
+ }