@fleetbase/fleetops-engine 0.6.49 → 0.6.51

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 (271) hide show
  1. package/README.md +185 -67
  2. package/addon/components/contact/panel-header.hbs +11 -48
  3. package/addon/components/customer/create-order-form.hbs +2 -2
  4. package/addon/components/customer/details.hbs +1 -2
  5. package/addon/components/customer/form.hbs +62 -50
  6. package/addon/components/customer/form.js +27 -1
  7. package/addon/components/customer/order-form.hbs +2 -2
  8. package/addon/components/customer/orders.hbs +50 -2
  9. package/addon/components/customer/orders.js +67 -5
  10. package/addon/components/display-place.hbs +2 -2
  11. package/addon/components/driver/form.hbs +1 -1
  12. package/addon/components/home/getting-started-guidance.hbs +113 -0
  13. package/addon/components/home/getting-started-guidance.js +107 -0
  14. package/addon/components/issue/details/comments.hbs +3 -0
  15. package/addon/components/issue/details/comments.js +3 -0
  16. package/addon/components/issue/details/correspondence.hbs +9 -0
  17. package/addon/components/issue/details/correspondence.js +83 -0
  18. package/addon/components/issue/details/documents.hbs +69 -0
  19. package/addon/components/issue/details/documents.js +85 -0
  20. package/addon/components/issue/details.hbs +220 -59
  21. package/addon/components/issue/details.js +245 -0
  22. package/addon/components/issue/form.hbs +19 -1
  23. package/addon/components/issue/panel-header.hbs +43 -0
  24. package/addon/components/issue/panel-header.js +22 -0
  25. package/addon/components/issue/timeline.hbs +40 -0
  26. package/addon/components/issue/timeline.js +45 -0
  27. package/addon/components/leaflet-tracking-marker.js +3 -0
  28. package/addon/components/map/drawer/position-listing.js +3 -0
  29. package/addon/components/map/google-live-map.js +6 -2
  30. package/addon/components/map/leaflet-live-map.hbs +48 -14
  31. package/addon/components/map/leaflet-live-map.js +201 -20
  32. package/addon/components/map/order-list-overlay.hbs +4 -4
  33. package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
  34. package/addon/components/modals/convert-customer-to-vendor.js +55 -0
  35. package/addon/components/modals/issue-assign.hbs +20 -0
  36. package/addon/components/modals/issue-assign.js +49 -0
  37. package/addon/components/modals/issue-close.hbs +30 -0
  38. package/addon/components/modals/issue-close.js +73 -0
  39. package/addon/components/modals/issue-status.hbs +20 -0
  40. package/addon/components/modals/issue-status.js +43 -0
  41. package/addon/components/modals/order-change-destination.hbs +46 -0
  42. package/addon/components/modals/order-change-destination.js +106 -0
  43. package/addon/components/modals/reset-customer-credentials.js +12 -6
  44. package/addon/components/modals/stop-activity.hbs +28 -0
  45. package/addon/components/modals/stop-activity.js +52 -0
  46. package/addon/components/modals/update-order-activity.hbs +78 -2
  47. package/addon/components/modals/update-order-activity.js +197 -5
  48. package/addon/components/orchestrator/resource-panel.hbs +0 -10
  49. package/addon/components/orchestrator/resource-panel.js +2 -4
  50. package/addon/components/order/activity-list.hbs +2 -2
  51. package/addon/components/order/activity-timeline.hbs +31 -29
  52. package/addon/components/order/details/activity.hbs +3 -3
  53. package/addon/components/order/details/activity.js +13 -2
  54. package/addon/components/order/details/documents.js +2 -0
  55. package/addon/components/order/details/proof.hbs +40 -38
  56. package/addon/components/order/details/proof.js +31 -4
  57. package/addon/components/order/details/purchase-rate.hbs +2 -2
  58. package/addon/components/order/details/route.hbs +11 -3
  59. package/addon/components/order/details/route.js +91 -14
  60. package/addon/components/order/details/tracking.hbs +101 -72
  61. package/addon/components/order/details/tracking.js +149 -2
  62. package/addon/components/order/details.hbs +5 -5
  63. package/addon/components/order/details.js +19 -1
  64. package/addon/components/order/form/details.hbs +3 -3
  65. package/addon/components/order/form/documents.js +11 -4
  66. package/addon/components/order/form/route.hbs +108 -104
  67. package/addon/components/order/form/route.js +37 -1
  68. package/addon/components/order/form/service-rate.hbs +2 -2
  69. package/addon/components/order/header.hbs +1 -1
  70. package/addon/components/order-config-manager/activity-flow.hbs +4 -1
  71. package/addon/components/order-config-manager/activity-flow.js +178 -12
  72. package/addon/components/place/address.js +1 -1
  73. package/addon/components/route-list.hbs +17 -29
  74. package/addon/components/route-list.js +42 -10
  75. package/addon/components/service-rate/details.hbs +42 -1
  76. package/addon/components/service-rate/form.hbs +119 -1
  77. package/addon/components/service-rate/form.js +59 -0
  78. package/addon/components/vehicle/form.js +1 -1
  79. package/addon/components/vehicle/pill.hbs +1 -1
  80. package/addon/components/vendor/personnel-manager.hbs +166 -0
  81. package/addon/components/vendor/personnel-manager.js +143 -0
  82. package/addon/components/widget/live-fleet.hbs +1 -0
  83. package/addon/components/widget/live-fleet.js +2 -7
  84. package/addon/components/widget/revenue-trend.hbs +1 -1
  85. package/addon/components/widget/revenue-trend.js +75 -2
  86. package/addon/controllers/management/contacts/customers/details.js +31 -13
  87. package/addon/controllers/management/contacts/customers.js +16 -5
  88. package/addon/controllers/management/contacts/index/details.js +31 -13
  89. package/addon/controllers/management/contacts/index.js +7 -0
  90. package/addon/controllers/management/issues/index/details.js +52 -6
  91. package/addon/controllers/management/vehicles/index/new.js +1 -1
  92. package/addon/controllers/management/vendors/index/details.js +4 -0
  93. package/addon/controllers/operations/orders/index/details.js +15 -7
  94. package/addon/controllers/operations/orders/index.js +15 -1
  95. package/addon/extension.js +19 -12
  96. package/addon/helpers/format-duration.js +1 -38
  97. package/addon/helpers/place-address-html.js +2 -2
  98. package/addon/initializers/load-leaflet-assets.js +5 -6
  99. package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
  100. package/addon/routes/management/issues/index/details.js +1 -1
  101. package/addon/routes/operations/service-rates/index.js +1 -1
  102. package/addon/routes.js +1 -0
  103. package/addon/services/contact-actions.js +231 -1
  104. package/addon/services/geofence.js +163 -19
  105. package/addon/services/getting-started.js +44 -0
  106. package/addon/services/issue-actions.js +71 -0
  107. package/addon/services/leaflet-contextmenu-manager.js +27 -0
  108. package/addon/services/leaflet-map-manager.js +2 -0
  109. package/addon/services/map-adapter/leaflet.js +19 -0
  110. package/addon/services/service-areas.js +3 -0
  111. package/addon/services/vehicle-actions.js +1 -1
  112. package/addon/styles/fleetops-engine.css +1127 -1
  113. package/addon/templates/management/contacts/customers/details.hbs +1 -1
  114. package/addon/templates/management/contacts/index/details.hbs +1 -1
  115. package/addon/templates/management/contacts.hbs +2 -2
  116. package/addon/templates/management/issues/index/details/index.hbs +3 -1
  117. package/addon/templates/management/issues/index/details.hbs +3 -2
  118. package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
  119. package/addon/templates/operations/orders/index/details/index.hbs +2 -2
  120. package/addon/templates/operations/orders/index.hbs +1 -1
  121. package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
  122. package/addon/templates/operations/service-rates/index/new.hbs +1 -0
  123. package/addon/templates/settings/payments/onboard.hbs +8 -8
  124. package/addon/utils/fleet-ops-options.js +2 -1
  125. package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
  126. package/addon/utils/leaflet-plugin-loader.js +219 -0
  127. package/addon/utils/normalize-order-config-flow.js +41 -0
  128. package/addon/utils/place-address-html.js +3 -2
  129. package/addon/utils/route-visualization.js +54 -7
  130. package/addon/utils/setup-customer-portal.js +4 -64
  131. package/app/components/home/getting-started-guidance.js +1 -0
  132. package/app/components/issue/details/comments.js +1 -0
  133. package/app/components/issue/details/correspondence.js +1 -0
  134. package/app/components/issue/details/documents.js +1 -0
  135. package/app/components/issue/panel-header.js +1 -0
  136. package/app/components/modals/convert-customer-to-vendor.js +1 -0
  137. package/app/components/modals/issue-assign.js +1 -0
  138. package/app/components/modals/issue-close.js +1 -0
  139. package/app/components/modals/issue-status.js +1 -0
  140. package/app/components/modals/order-change-destination.js +1 -0
  141. package/app/components/modals/stop-activity.js +1 -0
  142. package/app/components/vendor/personnel-manager.js +1 -0
  143. package/app/helpers/format-duration.js +1 -1
  144. package/app/services/getting-started.js +1 -0
  145. package/app/utils/leaflet-draw-namespace-guard.js +1 -0
  146. package/app/utils/leaflet-plugin-loader.js +1 -0
  147. package/composer.json +1 -1
  148. package/extension.json +1 -1
  149. package/package.json +5 -5
  150. package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
  151. package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
  152. package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
  153. package/server/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
  154. package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
  155. package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
  156. package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
  157. package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
  158. package/server/resources/views/labels/default.php +57 -49
  159. package/server/resources/views/labels/waypoint-label.php +27 -20
  160. package/server/resources/views/mail/customer-credentials.blade.php +16 -13
  161. package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
  162. package/server/seeders/Testing/FleetSeeder.php +22 -22
  163. package/server/seeders/Testing/OrdersSeeder.php +6 -22
  164. package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
  165. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
  166. package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
  167. package/server/src/Expansions/UserFilterExpansion.php +28 -0
  168. package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
  169. package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
  170. package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
  171. package/server/src/Http/Controllers/Api/v1/IssueController.php +2 -0
  172. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
  173. package/server/src/Http/Controllers/Api/v1/OrderController.php +109 -80
  174. package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
  175. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  176. package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
  177. package/server/src/Http/Controllers/Internal/v1/ContactController.php +154 -0
  178. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
  179. package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
  180. package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
  181. package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
  182. package/server/src/Http/Controllers/Internal/v1/OrderController.php +530 -52
  183. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
  184. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  185. package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
  186. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
  187. package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
  188. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
  189. package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
  190. package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
  191. package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
  192. package/server/src/Http/Requests/CreateIssueRequest.php +4 -0
  193. package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
  194. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
  195. package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
  196. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
  197. package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
  198. package/server/src/Http/Resources/v1/Customer.php +79 -0
  199. package/server/src/Http/Resources/v1/Index/Order.php +12 -9
  200. package/server/src/Http/Resources/v1/Issue.php +25 -0
  201. package/server/src/Http/Resources/v1/Order.php +20 -18
  202. package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
  203. package/server/src/Http/Resources/v1/Payload.php +23 -20
  204. package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
  205. package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
  206. package/server/src/Listeners/HandleOrderDispatched.php +2 -2
  207. package/server/src/Mail/CustomerCredentialsMail.php +5 -6
  208. package/server/src/Models/Contact.php +122 -66
  209. package/server/src/Models/Customer.php +64 -0
  210. package/server/src/Models/Driver.php +12 -3
  211. package/server/src/Models/Entity.php +2 -2
  212. package/server/src/Models/Issue.php +47 -6
  213. package/server/src/Models/Order.php +62 -10
  214. package/server/src/Models/Payload.php +17 -1
  215. package/server/src/Models/ServiceArea.php +1 -1
  216. package/server/src/Models/ServiceRate.php +273 -1
  217. package/server/src/Models/ServiceRateFee.php +39 -9
  218. package/server/src/Models/TrackingNumber.php +8 -2
  219. package/server/src/Models/Vehicle.php +18 -2
  220. package/server/src/Models/VendorPersonnel.php +10 -1
  221. package/server/src/Models/Waypoint.php +2 -2
  222. package/server/src/Models/Zone.php +1 -1
  223. package/server/src/Notifications/LateDeparture.php +2 -2
  224. package/server/src/Notifications/ProlongedStoppage.php +2 -2
  225. package/server/src/Notifications/RouteDeviation.php +2 -2
  226. package/server/src/Observers/ContactObserver.php +2 -0
  227. package/server/src/Observers/PurchaseRateObserver.php +20 -0
  228. package/server/src/Observers/ServiceRateObserver.php +6 -6
  229. package/server/src/Observers/ZoneObserver.php +56 -0
  230. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
  231. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  232. package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
  233. package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
  234. package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
  235. package/server/src/Support/Analytics/RevenueTrend.php +0 -1
  236. package/server/src/Support/Analytics/TopDrivers.php +10 -2
  237. package/server/src/Support/CustomerAuth.php +98 -0
  238. package/server/src/Support/GettingStarted.php +144 -0
  239. package/server/src/Support/LabelPdf.php +52 -0
  240. package/server/src/Support/Metrics/AbstractMetric.php +4 -4
  241. package/server/src/Support/PlaceSearch.php +282 -0
  242. package/server/src/Support/ResolvesOrderServiceStops.php +522 -0
  243. package/server/src/Tracking/TrackingContextBuilder.php +41 -38
  244. package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
  245. package/server/src/Tracking/TrackingStop.php +13 -11
  246. package/server/src/routes.php +76 -10
  247. package/server/tests/AnalyticsRoutesTest.php +8 -0
  248. package/server/tests/CapacityAllocationEngineTest.php +10 -10
  249. package/server/tests/CustomerContactIdentitySafetyTest.php +95 -0
  250. package/server/tests/CustomerEndpointTest.php +199 -0
  251. package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
  252. package/server/tests/GettingStartedStatusTest.php +21 -0
  253. package/server/tests/LabelPdfRenderingTest.php +23 -0
  254. package/server/tests/LiveControllerViewportTest.php +60 -0
  255. package/server/tests/MetricsRegistryTest.php +4 -8
  256. package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
  257. package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
  258. package/server/tests/OrderConfigEndpointTest.php +86 -0
  259. package/server/tests/OrderFileAttachmentTest.php +20 -0
  260. package/server/tests/ServiceAreaZoneApiShapeTest.php +78 -0
  261. package/server/tests/VehicleDriverAssignmentTest.php +18 -0
  262. package/translations/ar-ae.yml +9 -0
  263. package/translations/bg-bg.yaml +9 -0
  264. package/translations/en-us.yaml +12 -0
  265. package/translations/es-pa.yaml +9 -0
  266. package/translations/fr-fr.yaml +9 -0
  267. package/translations/mn-mn.yaml +9 -0
  268. package/translations/pt-br.yaml +9 -0
  269. package/translations/ru-ru.yaml +9 -0
  270. package/translations/vi-vn.yaml +9 -0
  271. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
@@ -1,7 +1,7 @@
1
1
  <Layout::Resource::Panel
2
2
  @resource={{@model}}
3
3
  @controller={{this}}
4
- @headerComponent={{component "contact/panel-header" resource=@model}}
4
+ @headerComponent={{component "contact/panel-header" resource=@model actionButtons=this.actionButtons}}
5
5
  @headerTitle={{or @model.name}}
6
6
  @actionButtons={{this.actionButtons}}
7
7
  @onPressCancel={{transition-to "management.contacts.customers"}}
@@ -1,7 +1,7 @@
1
1
  <Layout::Resource::Panel
2
2
  @resource={{@model}}
3
3
  @controller={{this}}
4
- @headerComponent={{component "contact/panel-header" resource=@model}}
4
+ @headerComponent={{component "contact/panel-header" resource=@model actionButtons=this.actionButtons}}
5
5
  @headerTitle={{or @model.name}}
6
6
  @actionButtons={{this.actionButtons}}
7
7
  @onPressCancel={{transition-to "management.contacts.index"}}
@@ -1,4 +1,4 @@
1
- <TabNavigation @tabs={{this.tabs}} @size="lg" @tablistClass="next-view-section-subheader-fixed-height pl-2">
1
+ <TabNavigation @tabs={{this.tabs}} @size="lg" @containerClass="tab-navigation-fill" @tablistClass="next-view-section-subheader-fixed-height pl-2">
2
2
  <:actions>
3
3
  <div class="flex flex-row items-center space-x-1 pr-4">
4
4
  <Layout::Resource::TabularActions
@@ -16,4 +16,4 @@
16
16
  <:default>
17
17
  {{outlet}}
18
18
  </:default>
19
- </TabNavigation>
19
+ </TabNavigation>
@@ -1 +1,3 @@
1
- <Issue::Details @resource={{@model}} />
1
+ <div>
2
+ <Issue::Details @resource={{@model}} />
3
+ </div>
@@ -1,14 +1,15 @@
1
1
  <Layout::Resource::Panel
2
2
  @resource={{@model}}
3
3
  @controller={{this}}
4
- @headerTitle={{or @model.title (concat "Issue reported on " (format-date-fns @model.created_at "dd MMM yy, HH:mm"))}}
4
+ @headerComponent={{component "issue/panel-header" resource=@model}}
5
5
  @actionButtons={{this.actionButtons}}
6
6
  @onPressCancel={{transition-to "management.issues.index"}}
7
7
  @onOverlayReady={{fn (mut this.overlay)}}
8
8
  @headerClass="no-bottom-border"
9
9
  @bodyClass="no-scroll"
10
+ @width="800px"
10
11
  >
11
12
  <TabNavigation @tabs={{this.tabs}} @contentClass="scrollable" @tablistClass="pl-2">
12
13
  {{outlet}}
13
14
  </TabNavigation>
14
- </Layout::Resource::Panel>
15
+ </Layout::Resource::Panel>
@@ -1,2 +1 @@
1
- {{page-title "Personnel"}}
2
- {{outlet}}
1
+ <Vendor::PersonnelManager @vendor={{@model}} />
@@ -1,2 +1,2 @@
1
- <Order::Details @resource={{@model}} @onActivityChanged={{perform this.parent.refresh}} @isLoadingActivity={{this.parent.refresh.isRunning}} />
2
- <Spacer @height="300px" />
1
+ <Order::Details @resource={{@model}} @onActivityChanged={{perform this.parent.refresh}} @proofReloadToken={{this.parent.proofReloadToken}} @isLoadingActivity={{this.parent.refresh.isRunning}} />
2
+ <Spacer @height="300px" />
@@ -36,7 +36,7 @@
36
36
  @bulkSearchValue={{this.bulk_query}}
37
37
  @onClearBulkSearch={{fn (mut this.bulk_query)}}
38
38
  @onSubmitBulkSearch={{fn (mut this.bulk_query)}}
39
- @bulkSearchPlaceholder={{t "prompt.order-bulk-search"}}
39
+ @bulkSearchPlaceholder={{t "order.prompts.order-bulk-search"}}
40
40
  @data={{@model}}
41
41
  @columns={{this.columns}}
42
42
  @page={{this.page}}
@@ -6,6 +6,7 @@
6
6
  @saveTask={{this.save}}
7
7
  @onPressCancel={{this.cancel}}
8
8
  @onOverlayReady={{fn (mut this.overlay)}}
9
+ @maxResizeWidth={{1000}}
9
10
  >
10
11
  <ServiceRate::Form @resource={{@model}} @controller={{this}} />
11
12
  <Spacer @height="200px" />
@@ -6,6 +6,7 @@
6
6
  @saveTask={{this.save}}
7
7
  @onPressCancel={{transition-to "operations.service-rates.index"}}
8
8
  @onOverlayReady={{fn (mut this.overlay)}}
9
+ @maxResizeWidth={{1000}}
9
10
  >
10
11
  <ServiceRate::Form @resource={{this.serviceRate}} @controller={{this}} />
11
12
  <Spacer @height="200px" />
@@ -1,4 +1,4 @@
1
- <Layout::Section::Header @title={{t "payment.settings.onboard.header-title"}} />
1
+ <Layout::Section::Header @title={{t "settings.payments.onboard.header-title"}} />
2
2
 
3
3
  <Layout::Section::Body class="overflow-y-scroll-i h-full">
4
4
  <div class="container">
@@ -13,16 +13,16 @@
13
13
  >
14
14
  <h2 id="title" class="text-base">
15
15
  {{#if this.onboardCompleted}}
16
- {{t "payment.settings.onboard.title-completed"}}
16
+ {{t "settings.payments.onboard.title-completed"}}
17
17
  {{else}}
18
- {{t "payment.settings.onboard.title-incomplete"}}
18
+ {{t "settings.payments.onboard.title-incomplete"}}
19
19
  {{/if}}
20
20
  </h2>
21
21
  <span id="subtitle">
22
22
  {{#if this.onboardCompleted}}
23
- {{t "payment.settings.onboard.subtitle-completed"}}
23
+ {{t "settings.payments.onboard.subtitle-completed"}}
24
24
  {{else}}
25
- {{t "payment.settings.onboard.subtitle-incomplete"}}
25
+ {{t "settings.payments.onboard.subtitle-incomplete"}}
26
26
  {{/if}}
27
27
  </span>
28
28
  <div class="mt-4 mb-1">
@@ -31,7 +31,7 @@
31
31
  @icon="check"
32
32
  @type="primary"
33
33
  @size="lg"
34
- @text={{t "payment.settings.onboard.button-continue"}}
34
+ @text={{t "settings.payments.onboard.button-continue"}}
35
35
  @onClick={{transition-to "settings.payments.index"}}
36
36
  @disabled={{this.onboardInProgress}}
37
37
  />
@@ -40,7 +40,7 @@
40
40
  @icon="play"
41
41
  @type="primary"
42
42
  @size="lg"
43
- @text={{if this.onboardInProgress (t "payment.settings.onboard.button-in-progress") (t "payment.settings.onboard.button-start")}}
43
+ @text={{if this.onboardInProgress (t "settings.payments.onboard.button-in-progress") (t "settings.payments.onboard.button-start")}}
44
44
  @onClick={{perform this.startOnboard}}
45
45
  @isLoading={{this.startOnboard.isRunning}}
46
46
  @disabled={{this.onboardInProgress}}
@@ -59,4 +59,4 @@
59
59
  </div>
60
60
  </div>
61
61
  </div>
62
- </Layout::Section::Body>
62
+ </Layout::Section::Body>
@@ -15,7 +15,7 @@ export const driverTypes = [
15
15
  ];
16
16
 
17
17
  export const driverStatuses = [
18
- { label: 'Active', value: 'active', description: 'Driver is available for assignments' },
18
+ { label: 'Available', value: 'available', description: 'Driver is available for assignments' },
19
19
  { label: 'Inactive', value: 'inactive', description: 'Driver is not currently active' },
20
20
  { label: 'On Duty', value: 'on_duty', description: 'Driver is working and available' },
21
21
  { label: 'Off Duty', value: 'off_duty', description: 'Driver is not working' },
@@ -382,6 +382,7 @@ export const podOptions = [
382
382
  export const serviceRateCalculationMethods = [
383
383
  { label: 'Per Meter', value: 'per_meter', description: 'Calculate cost based on the number of meters traveled.' },
384
384
  { label: 'Fixed Rate', value: 'fixed_meter', description: 'Charge a fixed rate per meter regardless of distance.' },
385
+ { label: 'Multi-zone Distance', value: 'multi_zone_distance', description: 'Calculate cost by splitting route distance across priced service areas and zones.' },
385
386
  { label: 'Parcel Rate', value: 'parcel', description: 'Charge a dynamic rate based on parcel/payload weight and dimensions.' },
386
387
  { label: 'Per Drop-off', value: 'per_drop', description: 'Cost applied per drop-off point.' },
387
388
  { label: 'Algorithm', value: 'algo', description: 'Cost calculated dynamically using algorithmic logic.' },
@@ -0,0 +1,22 @@
1
+ export function ensureLeafletDrawEditNamespace(leaflet = null) {
2
+ const leaflets = [];
3
+
4
+ if (leaflet) {
5
+ leaflets.push(leaflet);
6
+ }
7
+
8
+ if (typeof window !== 'undefined') {
9
+ leaflets.push(window.leaflet, window.L);
10
+ }
11
+
12
+ const guarded = [];
13
+ for (const L of leaflets) {
14
+ if (!L || guarded.includes(L)) continue;
15
+ L.Edit = L.Edit || {};
16
+ guarded.push(L);
17
+ }
18
+
19
+ return guarded;
20
+ }
21
+
22
+ export default ensureLeafletDrawEditNamespace;
@@ -0,0 +1,219 @@
1
+ import { debug } from '@ember/debug';
2
+ import ensureLeafletDrawEditNamespace from './leaflet-draw-namespace-guard';
3
+
4
+ const DEFAULT_BASE_PATH = 'engines-dist/leaflet';
5
+ const DEFAULT_SCRIPTS = ['leaflet.contextmenu.js', 'leaflet.draw-src.js'];
6
+ const DEFAULT_STYLESHEETS = ['leaflet.contextmenu.css', 'leaflet.draw.css'];
7
+ const SCRIPT_FLAG = 'fleetopsLeafletPluginLoaded';
8
+ const STYLESHEET_FLAG = 'fleetopsLeafletPluginStylesheet';
9
+
10
+ let pluginReadyPromise = null;
11
+
12
+ function normalizePath(path = '') {
13
+ return `/${path.replace(/^\/+/, '')}`;
14
+ }
15
+
16
+ function assetPath(basePath, assetName) {
17
+ return normalizePath(`${basePath ? `${basePath}/` : ''}${assetName}`);
18
+ }
19
+
20
+ function findAssetElement(tagName, srcAttribute, src) {
21
+ return Array.from(document.getElementsByTagName(tagName)).find((element) => {
22
+ const currentSrc = element.getAttribute(srcAttribute);
23
+ if (currentSrc === src) {
24
+ return true;
25
+ }
26
+
27
+ try {
28
+ return new URL(element[srcAttribute], window.location.origin).pathname === src;
29
+ } catch {
30
+ return false;
31
+ }
32
+ });
33
+ }
34
+
35
+ function normalizeLeafletGlobal() {
36
+ if (typeof window === 'undefined') {
37
+ return null;
38
+ }
39
+
40
+ const leaflet = window.L || window.leaflet;
41
+ if (!leaflet) {
42
+ return null;
43
+ }
44
+
45
+ window.L = leaflet;
46
+ window.leaflet = leaflet;
47
+ ensureLeafletDrawEditNamespace(leaflet);
48
+ return leaflet;
49
+ }
50
+
51
+ function hasLeafletDrawPlugins(leaflet = normalizeLeafletGlobal()) {
52
+ return Boolean(leaflet?.Edit?.Marker && leaflet?.Edit?.Poly && leaflet?.Control?.Draw);
53
+ }
54
+
55
+ function hasLeafletContextmenuPlugin(leaflet = normalizeLeafletGlobal()) {
56
+ return Boolean(leaflet?.Map?.ContextMenu);
57
+ }
58
+
59
+ export function hasLeafletPluginsReady() {
60
+ const leaflet = normalizeLeafletGlobal();
61
+ return Boolean(leaflet && hasLeafletDrawPlugins(leaflet) && hasLeafletContextmenuPlugin(leaflet));
62
+ }
63
+
64
+ function waitForLeafletGlobal({ timeoutMs = 8000 } = {}) {
65
+ const leaflet = normalizeLeafletGlobal();
66
+ if (leaflet) {
67
+ return Promise.resolve(leaflet);
68
+ }
69
+
70
+ return new Promise((resolve, reject) => {
71
+ const startedAt = Date.now();
72
+ const interval = setInterval(() => {
73
+ const leaflet = normalizeLeafletGlobal();
74
+ if (leaflet) {
75
+ clearInterval(interval);
76
+ resolve(leaflet);
77
+ return;
78
+ }
79
+
80
+ if (timeoutMs != null && Date.now() - startedAt >= timeoutMs) {
81
+ clearInterval(interval);
82
+ reject(new Error('[FleetOps Leaflet] Leaflet global is not available'));
83
+ }
84
+ }, 50);
85
+ });
86
+ }
87
+
88
+ function appendStylesheet(href) {
89
+ if (typeof document === 'undefined') {
90
+ return;
91
+ }
92
+
93
+ const existing = findAssetElement('link', 'href', href);
94
+ if (existing) {
95
+ existing.dataset[STYLESHEET_FLAG] = 'true';
96
+ return;
97
+ }
98
+
99
+ const link = document.createElement('link');
100
+ link.rel = 'stylesheet';
101
+ link.href = href;
102
+ link.dataset[STYLESHEET_FLAG] = 'true';
103
+ document.head.appendChild(link);
104
+ }
105
+
106
+ function loadScript(src, { timeoutMs = 8000, isReady = null } = {}) {
107
+ if (typeof document === 'undefined') {
108
+ return Promise.reject(new Error('[FleetOps Leaflet] document is not available'));
109
+ }
110
+
111
+ const existing = findAssetElement('script', 'src', src);
112
+ if (existing?.dataset[SCRIPT_FLAG] === 'true' || isReady?.()) {
113
+ if (existing) {
114
+ existing.dataset[SCRIPT_FLAG] = 'true';
115
+ }
116
+ return Promise.resolve(existing);
117
+ }
118
+
119
+ return new Promise((resolve, reject) => {
120
+ const script = existing ?? document.createElement('script');
121
+ let timeoutId;
122
+
123
+ const cleanup = () => {
124
+ script.removeEventListener('load', onLoad);
125
+ script.removeEventListener('error', onError);
126
+ if (timeoutId) {
127
+ clearTimeout(timeoutId);
128
+ }
129
+ };
130
+
131
+ const onLoad = () => {
132
+ cleanup();
133
+ script.dataset[SCRIPT_FLAG] = 'true';
134
+ normalizeLeafletGlobal();
135
+ resolve(script);
136
+ };
137
+
138
+ const onError = () => {
139
+ cleanup();
140
+ reject(new Error(`[FleetOps Leaflet] Failed to load ${src}`));
141
+ };
142
+
143
+ script.addEventListener('load', onLoad);
144
+ script.addEventListener('error', onError);
145
+
146
+ if (timeoutMs != null) {
147
+ timeoutId = setTimeout(() => {
148
+ cleanup();
149
+ reject(new Error(`[FleetOps Leaflet] Timed out loading ${src}`));
150
+ }, timeoutMs);
151
+ }
152
+
153
+ if (!existing) {
154
+ script.src = src;
155
+ script.async = false;
156
+ script.dataset.fleetopsLeafletPlugin = 'true';
157
+ document.body.appendChild(script);
158
+ }
159
+ });
160
+ }
161
+
162
+ export function resetLeafletPluginLoaderForTesting() {
163
+ pluginReadyPromise = null;
164
+ }
165
+
166
+ function pluginReadyCheck(script) {
167
+ if (script.includes('contextmenu')) {
168
+ return () => hasLeafletContextmenuPlugin();
169
+ }
170
+
171
+ if (script.includes('draw')) {
172
+ return () => hasLeafletDrawPlugins();
173
+ }
174
+
175
+ return null;
176
+ }
177
+
178
+ export default function ensureLeafletPluginsReady(options = {}) {
179
+ const { basePath = DEFAULT_BASE_PATH, scripts = DEFAULT_SCRIPTS, stylesheets = DEFAULT_STYLESHEETS, timeoutMs = 8000, force = false } = options;
180
+
181
+ if (!force && hasLeafletPluginsReady()) {
182
+ window.fleetopsLeafletPluginsLoaded = true;
183
+ return Promise.resolve(normalizeLeafletGlobal());
184
+ }
185
+
186
+ if (!force && pluginReadyPromise) {
187
+ return pluginReadyPromise;
188
+ }
189
+
190
+ window.fleetopsLeafletPluginsLoaded = false;
191
+ stylesheets.forEach((stylesheet) => appendStylesheet(assetPath(basePath, stylesheet)));
192
+
193
+ pluginReadyPromise = waitForLeafletGlobal({ timeoutMs })
194
+ .then(() =>
195
+ scripts.reduce((promise, script) => {
196
+ return promise.then(() => {
197
+ normalizeLeafletGlobal();
198
+ return loadScript(assetPath(basePath, script), { timeoutMs, isReady: pluginReadyCheck(script) });
199
+ });
200
+ }, Promise.resolve())
201
+ )
202
+ .then(() => {
203
+ const readyLeaflet = normalizeLeafletGlobal();
204
+ if (!hasLeafletPluginsReady()) {
205
+ throw new Error('[FleetOps Leaflet] Leaflet plugins loaded but required Draw/contextmenu APIs are missing');
206
+ }
207
+
208
+ window.fleetopsLeafletPluginsLoaded = true;
209
+ return readyLeaflet;
210
+ })
211
+ .catch((error) => {
212
+ window.fleetopsLeafletPluginsLoaded = false;
213
+ pluginReadyPromise = null;
214
+ debug(error.message);
215
+ throw error;
216
+ });
217
+
218
+ return pluginReadyPromise;
219
+ }
@@ -0,0 +1,41 @@
1
+ import { isArray } from '@ember/array';
2
+
3
+ export function getOrderConfigFlowRootCode(flow = {}) {
4
+ if (flow.created) {
5
+ return 'created';
6
+ }
7
+
8
+ return Object.keys(flow)[0];
9
+ }
10
+
11
+ export default function normalizeOrderConfigFlow(flow = {}) {
12
+ if (isArray(flow)) {
13
+ return flow.reduce((normalized, activity, index) => {
14
+ if (!activity || typeof activity !== 'object') {
15
+ return normalized;
16
+ }
17
+
18
+ const code = activity.code ?? activity.key;
19
+ if (!code) {
20
+ return normalized;
21
+ }
22
+
23
+ const nextActivity = flow.slice(index + 1).find((candidate) => candidate && typeof candidate === 'object' && (candidate.code ?? candidate.key));
24
+
25
+ normalized[code] = {
26
+ ...activity,
27
+ code,
28
+ key: activity.key ?? code,
29
+ activities: isArray(activity.activities) ? activity.activities : nextActivity ? [nextActivity.code ?? nextActivity.key] : [],
30
+ };
31
+
32
+ return normalized;
33
+ }, {});
34
+ }
35
+
36
+ if (flow && typeof flow === 'object') {
37
+ return flow;
38
+ }
39
+
40
+ return {};
41
+ }
@@ -11,17 +11,18 @@ function line(content, className = '') {
11
11
  return `<div${classAttribute}>${escapeHtml(content)}</div>`;
12
12
  }
13
13
 
14
- export default function placeAddressHtml(place) {
14
+ export default function placeAddressHtml(place, options = {}) {
15
15
  if (!place) {
16
16
  return htmlSafe('');
17
17
  }
18
18
 
19
+ const { showTitle = true } = options;
19
20
  const name = place.name === place.street1 ? null : place.name;
20
21
  const cityStatePostalCode = [place.city, place.province, place.postal_code].filter((value) => !isBlank(value)).join(', ');
21
22
  const lines = [];
22
23
 
23
24
  if (name) {
24
- lines.push(line(name, 'font-semibold'));
25
+ if (showTitle) lines.push(line(name, 'font-semibold'));
25
26
  lines.push(line(place.street1));
26
27
  } else if (place.street1) {
27
28
  lines.push(line(place.street1, 'font-semibold'));
@@ -8,6 +8,18 @@ export function waypointToPlace(waypoint) {
8
8
  return waypoint.place ?? waypoint;
9
9
  }
10
10
 
11
+ export function endpointTrackingNumberUuid(payload, role) {
12
+ if (!payload || !role) {
13
+ return null;
14
+ }
15
+
16
+ return payload[`${role}_tracking_number_uuid`] ?? null;
17
+ }
18
+
19
+ export function waypointTrackingNumberUuid(waypoint) {
20
+ return waypoint?.tracking_number_uuid ?? waypoint?.tracking_number?.uuid ?? waypoint?.trackingNumber?.uuid ?? null;
21
+ }
22
+
11
23
  export function buildPlaceAddressTooltip(title, place) {
12
24
  if (!place) {
13
25
  return title;
@@ -38,30 +50,65 @@ export function buildRoutePointsFromPayload(payload) {
38
50
  const waypoints = payload.waypoints ?? [];
39
51
 
40
52
  if (hasValidCoordinates(payload.pickup)) {
41
- points.push({ role: 'pickup', place: payload.pickup });
53
+ points.push({ role: 'pickup', place: payload.pickup, trackingNumberUuid: endpointTrackingNumberUuid(payload, 'pickup') });
42
54
  }
43
55
 
44
56
  waypoints.forEach((waypoint, index) => {
45
57
  const place = waypointToPlace(waypoint);
46
58
 
47
59
  if (hasValidCoordinates(place)) {
48
- points.push({ role: 'waypoint', place, stopNumber: index + 1 });
60
+ points.push({ role: 'waypoint', place, stopNumber: index + 1, trackingNumberUuid: waypointTrackingNumberUuid(waypoint) });
49
61
  }
50
62
  });
51
63
 
52
64
  if (hasValidCoordinates(payload.dropoff)) {
53
- points.push({ role: 'dropoff', place: payload.dropoff });
65
+ points.push({ role: 'dropoff', place: payload.dropoff, trackingNumberUuid: endpointTrackingNumberUuid(payload, 'dropoff') });
66
+ }
67
+
68
+ if (hasValidCoordinates(payload.return)) {
69
+ points.push({ role: 'return', place: payload.return, trackingNumberUuid: endpointTrackingNumberUuid(payload, 'return') });
54
70
  }
55
71
 
56
72
  return points;
57
73
  }
58
74
 
75
+ export function buildServiceStopsFromPayload(payload) {
76
+ if (!payload) {
77
+ return [];
78
+ }
79
+
80
+ const stops = [];
81
+ const waypoints = payload.waypoints ?? [];
82
+
83
+ if (payload.pickup) {
84
+ stops.push({ role: 'pickup', place: payload.pickup, trackingNumberUuid: endpointTrackingNumberUuid(payload, 'pickup') });
85
+ }
86
+
87
+ waypoints.forEach((waypoint, index) => {
88
+ const place = waypointToPlace(waypoint);
89
+
90
+ if (place) {
91
+ stops.push({ role: 'waypoint', place, stopNumber: index + 1, trackingNumberUuid: waypointTrackingNumberUuid(waypoint) });
92
+ }
93
+ });
94
+
95
+ if (payload.dropoff) {
96
+ stops.push({ role: 'dropoff', place: payload.dropoff, trackingNumberUuid: endpointTrackingNumberUuid(payload, 'dropoff') });
97
+ }
98
+
99
+ if (payload.return) {
100
+ stops.push({ role: 'return', place: payload.return, trackingNumberUuid: endpointTrackingNumberUuid(payload, 'return') });
101
+ }
102
+
103
+ return stops;
104
+ }
105
+
59
106
  export function describeRoutePoint(routePoint, routeColor) {
60
107
  const role = routePoint?.role;
61
108
  const stopNumber = routePoint?.stopNumber ?? 1;
62
- const label = role === 'pickup' ? 'P' : role === 'dropoff' ? 'D' : String(stopNumber);
63
- const markerColor = role === 'pickup' ? '#22C55E' : role === 'dropoff' ? '#EF4444' : routeColor;
64
- const title = role === 'pickup' ? 'Pickup' : role === 'dropoff' ? 'Dropoff' : `Stop ${stopNumber}`;
109
+ const label = role === 'pickup' ? 'P' : role === 'dropoff' ? 'D' : role === 'return' ? 'R' : String(stopNumber);
110
+ const markerColor = role === 'pickup' ? '#22C55E' : role === 'dropoff' ? '#EF4444' : role === 'return' ? '#F97316' : routeColor;
111
+ const title = role === 'pickup' ? 'Pickup' : role === 'dropoff' ? 'Dropoff' : role === 'return' ? 'Return' : `Stop ${stopNumber}`;
65
112
 
66
113
  return {
67
114
  label,
@@ -74,7 +121,7 @@ export function buildRoutePointMarkerPresentation(routePoint, routeColor, option
74
121
  const role = routePoint?.role;
75
122
  const place = routePoint?.place;
76
123
  const { label, markerColor, title } = describeRoutePoint(routePoint, routeColor);
77
- const zIndexOffset = role === 'pickup' ? 2200 : role === 'dropoff' ? 2100 : 1800;
124
+ const zIndexOffset = role === 'pickup' ? 2200 : role === 'dropoff' ? 2100 : role === 'return' ? 2000 : 1800;
78
125
 
79
126
  return {
80
127
  waypointLabel: label,
@@ -1,10 +1,6 @@
1
- import CustomerOrdersComponent from '../components/customer/orders';
2
1
  import CustomerAdminSettingsComponent from '../components/customer/admin-settings';
3
- import { MenuItem } from '@fleetbase/ember-core/contracts';
4
- import { setOwner } from '@ember/application';
5
- import { debug } from '@ember/debug';
6
2
 
7
- export default function setupCustomerPortal(fleetopsEngine, universe) {
3
+ export default function setupCustomerPortal(_fleetopsEngine, universe) {
8
4
  const extensionManager = universe.getService('universe/extension-manager');
9
5
  const isCustomerPortalInstalled = extensionManager.isInstalled('@fleetbase/customer-portal-engine');
10
6
  if (!isCustomerPortalInstalled) return;
@@ -13,81 +9,25 @@ export default function setupCustomerPortal(fleetopsEngine, universe) {
13
9
  if (extensionManager.isEngineLoaded('@fleetbase/customer-portal-engine')) {
14
10
  const customerPortalEngine = extensionManager.getEngineInstance('@fleetbase/customer-portal-engine');
15
11
  if (customerPortalEngine) {
16
- return setup(customerPortalEngine, fleetopsEngine, universe);
12
+ return setup(customerPortalEngine, universe);
17
13
  }
18
14
  }
19
15
 
20
16
  // Otherwise, wait for customer portal engine to load to run setup
21
17
  universe.onEngineLoaded('@fleetbase/customer-portal-engine', (customerPortalEngine) => {
22
- setup(customerPortalEngine, fleetopsEngine, universe);
18
+ setup(customerPortalEngine, universe);
23
19
  });
24
20
  }
25
21
 
26
- function setup(customerPortalEngine, fleetopsEngine, universe) {
22
+ function setup(customerPortalEngine, universe) {
27
23
  // If setup already completed don't run again
28
24
  if (customerPortalEngine?._fleetopsSetupCompleted === true) return;
29
25
 
30
- // Alias FleetOps services into Customer Portal before rendering
31
- createServiceAlias(fleetopsEngine, customerPortalEngine, [
32
- 'leaflet-map-manager',
33
- 'location',
34
- 'movement-tracker',
35
- 'leaflet-routing-control',
36
- 'order-config-actions',
37
- 'order-creation',
38
- 'order-validation',
39
- ]);
40
-
41
- const menuService = universe.getService('universe/menu-service');
42
26
  const registryService = universe.getService('universe/registry-service');
43
27
 
44
- // Register customer orders from Fleet-Ops
45
- menuService.registerMenuItem(
46
- 'customer-portal:sidebar',
47
- new MenuItem({
48
- title: 'Orders',
49
- route: 'customer-portal.portal.virtual',
50
- icon: 'boxes-packing',
51
- component: createEngineBoundComponent(customerPortalEngine, CustomerOrdersComponent),
52
- })
53
- );
54
-
55
28
  // Register registries
56
29
  registryService.register('customer-portal:admin-settings', '@fleetbase/customer-portal-engine', CustomerAdminSettingsComponent);
57
30
 
58
31
  // Flag fleetops setup complete
59
32
  customerPortalEngine._fleetopsSetupCompleted = true;
60
33
  }
61
-
62
- function createServiceAlias(sourceOwner, destOwner, serviceNames) {
63
- serviceNames.forEach((name) => {
64
- const key = `service:${name}`;
65
- if (destOwner.hasRegistration?.(key)) return;
66
-
67
- // alias the fully-wired instance
68
- const instance = sourceOwner.lookup?.(key);
69
- if (instance) {
70
- destOwner.register(key, instance, { instantiate: false });
71
- return;
72
- }
73
-
74
- // Faillback: only use factories for simple/stateless services
75
- const factory = sourceOwner.resolveRegistration?.(key);
76
- if (factory) {
77
- destOwner.register(key, factory);
78
- return;
79
- }
80
-
81
- debug(`[alias] missing ${key} on source; not aliased`);
82
- });
83
- }
84
-
85
- function createEngineBoundComponent(engineInstance, ComponentClass) {
86
- return class EngineBoundComponent extends ComponentClass {
87
- constructor(...args) {
88
- super(...args);
89
- // Ensure DI resolution happens within the engine
90
- setOwner(this, engineInstance);
91
- }
92
- };
93
- }
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/home/getting-started-guidance';