@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.
- package/README.md +185 -67
- package/addon/components/contact/panel-header.hbs +11 -48
- package/addon/components/customer/create-order-form.hbs +2 -2
- package/addon/components/customer/details.hbs +1 -2
- package/addon/components/customer/form.hbs +62 -50
- package/addon/components/customer/form.js +27 -1
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +50 -2
- package/addon/components/customer/orders.js +67 -5
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/home/getting-started-guidance.hbs +113 -0
- package/addon/components/home/getting-started-guidance.js +107 -0
- package/addon/components/issue/details/comments.hbs +3 -0
- package/addon/components/issue/details/comments.js +3 -0
- package/addon/components/issue/details/correspondence.hbs +9 -0
- package/addon/components/issue/details/correspondence.js +83 -0
- package/addon/components/issue/details/documents.hbs +69 -0
- package/addon/components/issue/details/documents.js +85 -0
- package/addon/components/issue/details.hbs +220 -59
- package/addon/components/issue/details.js +245 -0
- package/addon/components/issue/form.hbs +19 -1
- package/addon/components/issue/panel-header.hbs +43 -0
- package/addon/components/issue/panel-header.js +22 -0
- package/addon/components/issue/timeline.hbs +40 -0
- package/addon/components/issue/timeline.js +45 -0
- package/addon/components/leaflet-tracking-marker.js +3 -0
- package/addon/components/map/drawer/position-listing.js +3 -0
- package/addon/components/map/google-live-map.js +6 -2
- package/addon/components/map/leaflet-live-map.hbs +48 -14
- package/addon/components/map/leaflet-live-map.js +201 -20
- package/addon/components/map/order-list-overlay.hbs +4 -4
- package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
- package/addon/components/modals/convert-customer-to-vendor.js +55 -0
- package/addon/components/modals/issue-assign.hbs +20 -0
- package/addon/components/modals/issue-assign.js +49 -0
- package/addon/components/modals/issue-close.hbs +30 -0
- package/addon/components/modals/issue-close.js +73 -0
- package/addon/components/modals/issue-status.hbs +20 -0
- package/addon/components/modals/issue-status.js +43 -0
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/reset-customer-credentials.js +12 -6
- package/addon/components/modals/stop-activity.hbs +28 -0
- package/addon/components/modals/stop-activity.js +52 -0
- package/addon/components/modals/update-order-activity.hbs +78 -2
- package/addon/components/modals/update-order-activity.js +197 -5
- package/addon/components/orchestrator/resource-panel.hbs +0 -10
- package/addon/components/orchestrator/resource-panel.js +2 -4
- package/addon/components/order/activity-list.hbs +2 -2
- package/addon/components/order/activity-timeline.hbs +31 -29
- package/addon/components/order/details/activity.hbs +3 -3
- package/addon/components/order/details/activity.js +13 -2
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/proof.hbs +40 -38
- package/addon/components/order/details/proof.js +31 -4
- package/addon/components/order/details/purchase-rate.hbs +2 -2
- package/addon/components/order/details/route.hbs +11 -3
- package/addon/components/order/details/route.js +91 -14
- package/addon/components/order/details/tracking.hbs +101 -72
- package/addon/components/order/details/tracking.js +149 -2
- package/addon/components/order/details.hbs +5 -5
- package/addon/components/order/details.js +19 -1
- package/addon/components/order/form/details.hbs +3 -3
- package/addon/components/order/form/documents.js +11 -4
- package/addon/components/order/form/route.hbs +108 -104
- package/addon/components/order/form/route.js +37 -1
- package/addon/components/order/form/service-rate.hbs +2 -2
- package/addon/components/order/header.hbs +1 -1
- package/addon/components/order-config-manager/activity-flow.hbs +4 -1
- package/addon/components/order-config-manager/activity-flow.js +178 -12
- package/addon/components/place/address.js +1 -1
- package/addon/components/route-list.hbs +17 -29
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +42 -1
- package/addon/components/service-rate/form.hbs +119 -1
- package/addon/components/service-rate/form.js +59 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/components/vendor/personnel-manager.hbs +166 -0
- package/addon/components/vendor/personnel-manager.js +143 -0
- package/addon/components/widget/live-fleet.hbs +1 -0
- package/addon/components/widget/live-fleet.js +2 -7
- package/addon/components/widget/revenue-trend.hbs +1 -1
- package/addon/components/widget/revenue-trend.js +75 -2
- package/addon/controllers/management/contacts/customers/details.js +31 -13
- package/addon/controllers/management/contacts/customers.js +16 -5
- package/addon/controllers/management/contacts/index/details.js +31 -13
- package/addon/controllers/management/contacts/index.js +7 -0
- package/addon/controllers/management/issues/index/details.js +52 -6
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/management/vendors/index/details.js +4 -0
- package/addon/controllers/operations/orders/index/details.js +15 -7
- package/addon/controllers/operations/orders/index.js +15 -1
- package/addon/extension.js +19 -12
- package/addon/helpers/format-duration.js +1 -38
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/initializers/load-leaflet-assets.js +5 -6
- package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
- package/addon/routes/management/issues/index/details.js +1 -1
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/routes.js +1 -0
- package/addon/services/contact-actions.js +231 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/getting-started.js +44 -0
- package/addon/services/issue-actions.js +71 -0
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/leaflet-map-manager.js +2 -0
- package/addon/services/map-adapter/leaflet.js +19 -0
- package/addon/services/service-areas.js +3 -0
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +1127 -1
- package/addon/templates/management/contacts/customers/details.hbs +1 -1
- package/addon/templates/management/contacts/index/details.hbs +1 -1
- package/addon/templates/management/contacts.hbs +2 -2
- package/addon/templates/management/issues/index/details/index.hbs +3 -1
- package/addon/templates/management/issues/index/details.hbs +3 -2
- package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
- package/addon/templates/operations/orders/index/details/index.hbs +2 -2
- package/addon/templates/operations/orders/index.hbs +1 -1
- package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
- package/addon/templates/operations/service-rates/index/new.hbs +1 -0
- package/addon/templates/settings/payments/onboard.hbs +8 -8
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
- package/addon/utils/leaflet-plugin-loader.js +219 -0
- package/addon/utils/normalize-order-config-flow.js +41 -0
- package/addon/utils/place-address-html.js +3 -2
- package/addon/utils/route-visualization.js +54 -7
- package/addon/utils/setup-customer-portal.js +4 -64
- package/app/components/home/getting-started-guidance.js +1 -0
- package/app/components/issue/details/comments.js +1 -0
- package/app/components/issue/details/correspondence.js +1 -0
- package/app/components/issue/details/documents.js +1 -0
- package/app/components/issue/panel-header.js +1 -0
- package/app/components/modals/convert-customer-to-vendor.js +1 -0
- package/app/components/modals/issue-assign.js +1 -0
- package/app/components/modals/issue-close.js +1 -0
- package/app/components/modals/issue-status.js +1 -0
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/app/components/vendor/personnel-manager.js +1 -0
- package/app/helpers/format-duration.js +1 -1
- package/app/services/getting-started.js +1 -0
- package/app/utils/leaflet-draw-namespace-guard.js +1 -0
- package/app/utils/leaflet-plugin-loader.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +5 -5
- package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
- package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
- package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
- package/server/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
- package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
- package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
- package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
- package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/resources/views/mail/customer-credentials.blade.php +16 -13
- package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
- package/server/seeders/Testing/FleetSeeder.php +22 -22
- package/server/seeders/Testing/OrdersSeeder.php +6 -22
- package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
- package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
- package/server/src/Expansions/UserFilterExpansion.php +28 -0
- package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/IssueController.php +2 -0
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +109 -80
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +154 -0
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
- package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +530 -52
- package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
- package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
- package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/CreateIssueRequest.php +4 -0
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
- package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
- package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
- package/server/src/Http/Resources/v1/Customer.php +79 -0
- package/server/src/Http/Resources/v1/Index/Order.php +12 -9
- package/server/src/Http/Resources/v1/Issue.php +25 -0
- package/server/src/Http/Resources/v1/Order.php +20 -18
- package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
- package/server/src/Http/Resources/v1/Payload.php +23 -20
- package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
- package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
- package/server/src/Listeners/HandleOrderDispatched.php +2 -2
- package/server/src/Mail/CustomerCredentialsMail.php +5 -6
- package/server/src/Models/Contact.php +122 -66
- package/server/src/Models/Customer.php +64 -0
- package/server/src/Models/Driver.php +12 -3
- package/server/src/Models/Entity.php +2 -2
- package/server/src/Models/Issue.php +47 -6
- package/server/src/Models/Order.php +62 -10
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceArea.php +1 -1
- package/server/src/Models/ServiceRate.php +273 -1
- package/server/src/Models/ServiceRateFee.php +39 -9
- package/server/src/Models/TrackingNumber.php +8 -2
- package/server/src/Models/Vehicle.php +18 -2
- package/server/src/Models/VendorPersonnel.php +10 -1
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Models/Zone.php +1 -1
- package/server/src/Notifications/LateDeparture.php +2 -2
- package/server/src/Notifications/ProlongedStoppage.php +2 -2
- package/server/src/Notifications/RouteDeviation.php +2 -2
- package/server/src/Observers/ContactObserver.php +2 -0
- package/server/src/Observers/PurchaseRateObserver.php +20 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Observers/ZoneObserver.php +56 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
- package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
- package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
- package/server/src/Support/Analytics/RevenueTrend.php +0 -1
- package/server/src/Support/Analytics/TopDrivers.php +10 -2
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/GettingStarted.php +144 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +4 -4
- package/server/src/Support/PlaceSearch.php +282 -0
- package/server/src/Support/ResolvesOrderServiceStops.php +522 -0
- package/server/src/Tracking/TrackingContextBuilder.php +41 -38
- package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
- package/server/src/Tracking/TrackingStop.php +13 -11
- package/server/src/routes.php +76 -10
- package/server/tests/AnalyticsRoutesTest.php +8 -0
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +95 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/GettingStartedStatusTest.php +21 -0
- package/server/tests/LabelPdfRenderingTest.php +23 -0
- package/server/tests/LiveControllerViewportTest.php +60 -0
- package/server/tests/MetricsRegistryTest.php +4 -8
- package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
- package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +78 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/translations/ar-ae.yml +9 -0
- package/translations/bg-bg.yaml +9 -0
- package/translations/en-us.yaml +12 -0
- package/translations/es-pa.yaml +9 -0
- package/translations/fr-fr.yaml +9 -0
- package/translations/mn-mn.yaml +9 -0
- package/translations/pt-br.yaml +9 -0
- package/translations/ru-ru.yaml +9 -0
- package/translations/vi-vn.yaml +9 -0
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action, get } from '@ember/object';
|
|
4
5
|
import { task } from 'ember-concurrency';
|
|
6
|
+
import config from 'ember-get-config';
|
|
7
|
+
import { buildServiceStopsFromPayload } from '../../utils/route-visualization';
|
|
5
8
|
|
|
6
9
|
export default class ModalsUpdateOrderActivityComponent extends Component {
|
|
7
10
|
@service fetch;
|
|
11
|
+
@service intl;
|
|
8
12
|
@service notifications;
|
|
9
13
|
@service modalsManager;
|
|
10
14
|
@tracked activityOptions = [];
|
|
11
15
|
@tracked order;
|
|
12
16
|
@tracked selectedActivity;
|
|
13
|
-
@tracked customActivity;
|
|
17
|
+
@tracked customActivity = {};
|
|
18
|
+
@tracked proofFiles = [];
|
|
19
|
+
@tracked proof;
|
|
20
|
+
@tracked bypassProof = false;
|
|
14
21
|
|
|
15
22
|
constructor(owner, { options = {} }) {
|
|
16
23
|
super(...arguments);
|
|
@@ -18,9 +25,103 @@ export default class ModalsUpdateOrderActivityComponent extends Component {
|
|
|
18
25
|
this.loadActivity.perform();
|
|
19
26
|
}
|
|
20
27
|
|
|
28
|
+
get waypoints() {
|
|
29
|
+
const waypoints = this.order?.payload?.waypoints;
|
|
30
|
+
if (typeof waypoints?.toArray === 'function') {
|
|
31
|
+
return waypoints.toArray();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Array.isArray(waypoints) ? waypoints : Array.from(waypoints ?? []);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get serviceStops() {
|
|
38
|
+
return buildServiceStopsFromPayload(this.order?.payload);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get activeStopId() {
|
|
42
|
+
const activeStop = this.order?.tracker_data?.active_stop;
|
|
43
|
+
|
|
44
|
+
return activeStop?.uuid ?? activeStop?.public_id ?? this.order?.payload?.current_waypoint_uuid ?? this.order?.payload?.current_waypoint;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get isMultipleWaypointOrder() {
|
|
48
|
+
return this.waypoints.length > 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get orderHasStarted() {
|
|
52
|
+
const status = this.order?.status;
|
|
53
|
+
|
|
54
|
+
return Boolean(this.order?.started || this.order?.started_at || (status && !['created', 'dispatched'].includes(status)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get shouldUseWaypointActivity() {
|
|
58
|
+
return this.isMultipleWaypointOrder && this.orderHasStarted;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get currentWaypoint() {
|
|
62
|
+
if (!this.shouldUseWaypointActivity) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const currentWaypointId = this.activeStopId;
|
|
67
|
+
|
|
68
|
+
if (currentWaypointId) {
|
|
69
|
+
const stop = this.serviceStops.find(({ place }) => {
|
|
70
|
+
return [place?.id, place?.uuid, place?.public_id, place?.waypoint_public_id].includes(currentWaypointId);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (stop?.place) {
|
|
74
|
+
return stop.place;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this.serviceStops.find(({ place }) => !place?.complete)?.place ?? this.serviceStops[0]?.place;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get currentWaypointId() {
|
|
82
|
+
const waypoint = this.currentWaypoint;
|
|
83
|
+
|
|
84
|
+
return waypoint?.id ?? waypoint?.public_id ?? waypoint?.uuid;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get currentWaypointProofSubjectId() {
|
|
88
|
+
const waypoint = this.currentWaypoint;
|
|
89
|
+
|
|
90
|
+
return waypoint?.waypoint_public_id ?? waypoint?.id ?? waypoint?.public_id ?? waypoint?.uuid;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get selectedActivityRecord() {
|
|
94
|
+
if (this.selectedActivity === 'custom') {
|
|
95
|
+
return this.customActivity;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return this.activityOptions[this.selectedActivity];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get selectedActivityRequiresPod() {
|
|
102
|
+
return Boolean(this.selectedActivityRecord?.require_pod);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get acceptedProofFileTypes() {
|
|
106
|
+
return ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get hasUploadedProof() {
|
|
110
|
+
return Boolean(this.proof?.id || this.proof?.public_id);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get uploadedProofId() {
|
|
114
|
+
return this.proof?.id ?? this.proof?.public_id;
|
|
115
|
+
}
|
|
116
|
+
|
|
21
117
|
@task *loadActivity() {
|
|
118
|
+
if (!this.order?.id) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
22
122
|
try {
|
|
23
|
-
const
|
|
123
|
+
const params = this.shouldUseWaypointActivity && this.currentWaypointId ? { waypoint: this.currentWaypointId } : {};
|
|
124
|
+
const activityOptions = yield this.fetch.get(`orders/next-activity/${this.order.id}`, params);
|
|
24
125
|
this.activityOptions = activityOptions;
|
|
25
126
|
return activityOptions;
|
|
26
127
|
} catch (err) {
|
|
@@ -31,21 +132,112 @@ export default class ModalsUpdateOrderActivityComponent extends Component {
|
|
|
31
132
|
@task *submitActivity(modal) {
|
|
32
133
|
modal.startLoading();
|
|
33
134
|
|
|
34
|
-
const activity = this.
|
|
135
|
+
const activity = this.selectedActivityRecord;
|
|
35
136
|
if (this.selectedActivity === 'custom' && !activity.status && !activity.details && !activity.code) {
|
|
36
137
|
modal.stopLoading();
|
|
37
138
|
return this.notifications.warning(this.intl.t('fleet-ops.operations.orders.index.view.invalid-warning'));
|
|
38
139
|
}
|
|
39
140
|
|
|
141
|
+
if (this.selectedActivityRequiresPod && this.uploadProofFile.isRunning) {
|
|
142
|
+
modal.stopLoading();
|
|
143
|
+
return this.notifications.warning('Photo proof of delivery is still uploading.');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (this.selectedActivityRequiresPod && !this.hasUploadedProof && !this.bypassProof) {
|
|
147
|
+
modal.stopLoading();
|
|
148
|
+
return this.notifications.warning('Upload photo proof of delivery or choose to bypass proof of delivery.');
|
|
149
|
+
}
|
|
150
|
+
|
|
40
151
|
try {
|
|
41
|
-
yield this.fetch.patch(`orders/update-activity/${this.order.id}`, {
|
|
152
|
+
yield this.fetch.patch(`orders/update-activity/${this.order.id}`, {
|
|
153
|
+
activity,
|
|
154
|
+
proof: this.hasUploadedProof ? this.uploadedProofId : null,
|
|
155
|
+
bypass_proof: this.selectedActivityRequiresPod && this.bypassProof,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (typeof this.order?.reload === 'function') {
|
|
159
|
+
yield this.order.reload();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (typeof this.order?.loadTrackerData === 'function') {
|
|
163
|
+
if (typeof this.order.set === 'function') {
|
|
164
|
+
this.order.set('tracker_data', null);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
yield this.order.loadTrackerData();
|
|
168
|
+
}
|
|
169
|
+
|
|
42
170
|
this.modalsManager.setOption('activityCreated', activity);
|
|
171
|
+
this.modalsManager.setOption('proofCreated', this.hasUploadedProof ? this.proof : null);
|
|
43
172
|
this.notifications.success(`Order activity has been updated to ${activity.status}`);
|
|
173
|
+
modal.done();
|
|
44
174
|
} catch (error) {
|
|
45
175
|
this.notifications.serverError(error);
|
|
46
176
|
} finally {
|
|
47
177
|
modal.stopLoading();
|
|
48
|
-
modal.done();
|
|
49
178
|
}
|
|
50
179
|
}
|
|
180
|
+
|
|
181
|
+
@task *capturePhotoProof() {
|
|
182
|
+
const [proofFile] = this.proofFiles;
|
|
183
|
+
if (!proofFile) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const headers = this.fetch.getHeaders();
|
|
188
|
+
delete headers['Content-Type'];
|
|
189
|
+
|
|
190
|
+
const subjectId = this.currentWaypointProofSubjectId;
|
|
191
|
+
const subjectPath = subjectId ? `/${subjectId}` : '';
|
|
192
|
+
const url = [get(config, 'API.host'), get(config, 'API.namespace'), `orders/${this.order.id}/capture-photo${subjectPath}`].filter(Boolean).join('/');
|
|
193
|
+
|
|
194
|
+
const response = yield proofFile.upload(url, {
|
|
195
|
+
data: {
|
|
196
|
+
remarks: 'Verified by Photo',
|
|
197
|
+
},
|
|
198
|
+
headers,
|
|
199
|
+
withCredentials: true,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return response.json();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@task *uploadProofFile(file) {
|
|
206
|
+
if (!file) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.bypassProof = false;
|
|
211
|
+
this.proof = null;
|
|
212
|
+
this.proofFiles = [file];
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
this.proof = this.normalizeProofResponse(yield this.capturePhotoProof.perform());
|
|
216
|
+
this.notifications.success('Photo proof of delivery uploaded.');
|
|
217
|
+
} catch (error) {
|
|
218
|
+
this.proof = null;
|
|
219
|
+
this.proofFiles = [];
|
|
220
|
+
this.notifications.serverError(error);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@action toggleBypassProof(value) {
|
|
225
|
+
this.bypassProof = value;
|
|
226
|
+
if (value) {
|
|
227
|
+
this.proofFiles = [];
|
|
228
|
+
this.proof = null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
normalizeProofResponse(response) {
|
|
233
|
+
if (response?.proof) {
|
|
234
|
+
return response.proof;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (response?.data?.id || response?.data?.public_id) {
|
|
238
|
+
return response.data;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return response;
|
|
242
|
+
}
|
|
51
243
|
}
|
|
@@ -69,16 +69,6 @@
|
|
|
69
69
|
}}"
|
|
70
70
|
{{on "click" (fn this.setVehicleFilter "all")}}
|
|
71
71
|
>{{t "orchestrator.filter-all"}}</button>
|
|
72
|
-
<button
|
|
73
|
-
type="button"
|
|
74
|
-
class="px-2 py-0.5 rounded text-xs transition-colors
|
|
75
|
-
{{if
|
|
76
|
-
(eq this.vehicleFilter 'active')
|
|
77
|
-
'bg-indigo-500 text-white'
|
|
78
|
-
'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
|
|
79
|
-
}}"
|
|
80
|
-
{{on "click" (fn this.setVehicleFilter "active")}}
|
|
81
|
-
>{{t "orchestrator.filter-active"}}</button>
|
|
82
72
|
<button
|
|
83
73
|
type="button"
|
|
84
74
|
class="px-2 py-0.5 rounded text-xs transition-colors
|
|
@@ -59,12 +59,10 @@ export default class OrchestratorResourcePanelComponent extends Component {
|
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
if (this.vehicleFilter === '
|
|
63
|
-
vehicles = vehicles.filter((v) => v.status === 'active');
|
|
64
|
-
} else if (this.vehicleFilter === 'no-driver') {
|
|
62
|
+
if (this.vehicleFilter === 'no-driver') {
|
|
65
63
|
vehicles = vehicles.filter((v) => !v.driver?.id);
|
|
66
64
|
} else if (this.vehicleFilter === 'available') {
|
|
67
|
-
vehicles = vehicles.filter((v) => v.status === '
|
|
65
|
+
vehicles = vehicles.filter((v) => v.status === 'available' && !v.driver?.current_job);
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
return vehicles;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="order-activity-list space-y-2">
|
|
2
|
-
{{#each @resource.tracking_statuses as |trackingStatus|}}
|
|
2
|
+
{{#each (or @activity @resource.tracking_statuses) as |trackingStatus|}}
|
|
3
3
|
<div class="order-activity-list-item flex flex-row justify-between">
|
|
4
4
|
<div class="flex flex-row">
|
|
5
5
|
<div class="w-8 pt-1">
|
|
@@ -31,4 +31,4 @@
|
|
|
31
31
|
</div>
|
|
32
32
|
</div>
|
|
33
33
|
{{/each}}
|
|
34
|
-
</div>
|
|
34
|
+
</div>
|
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
{{#
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
<div class="flex flex-col space-y-
|
|
6
|
-
<div class="
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
{{#let (or @activity @resource.tracking_statuses) as |activity|}}
|
|
2
|
+
{{#if activity.length}}
|
|
3
|
+
<Timeline @activity={{activity}} @arrowClass="bg-gray-600 border border-gray-900 shadow-md" as |TimelineItem|>
|
|
4
|
+
<TimelineItem @activeStatus={{@resource.status}} as |trackingStatus|>
|
|
5
|
+
<div class="flex flex-col px-3 py-2 rounded text-sm leading-4 space-y-2 bg-gray-100 dark:bg-gray-900 dark:text-white">
|
|
6
|
+
<div class="flex flex-col space-y-1">
|
|
7
|
+
<div class="text-xs font-semibold truncate">{{trackingStatus.status}}</div>
|
|
8
|
+
<div class="text-xs truncate">{{n-a trackingStatus.details}}</div>
|
|
9
|
+
<div class="text-xs">{{format-date-fns trackingStatus.created_at "d MMM HH:mm"}}</div>
|
|
10
|
+
</div>
|
|
11
|
+
<Attach::Tooltip @class="clean" @animation="scale" @placement="top">
|
|
12
|
+
<InputInfo>
|
|
13
|
+
<div class="text-xs">{{or trackingStatus.details trackingStatus.status}}</div>
|
|
14
|
+
<div class="text-xs">{{format-date-fns trackingStatus.created_at "d MMM yyyy HH:mm"}}</div>
|
|
15
|
+
</InputInfo>
|
|
16
|
+
</Attach::Tooltip>
|
|
9
17
|
</div>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<FaIcon @icon="layer-group" @size="2x" class="text-gray-400 mb-2" />
|
|
24
|
-
<div class="text-sm text-gray-600 dark:text-gray-400 font-medium">
|
|
25
|
-
No order activity
|
|
26
|
-
</div>
|
|
27
|
-
<div class="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
|
28
|
-
Dispatch or update the order to create activity
|
|
18
|
+
</TimelineItem>
|
|
19
|
+
</Timeline>
|
|
20
|
+
{{else}}
|
|
21
|
+
<div class="min-h-24 p-4">
|
|
22
|
+
<div class="flex items-center justify-center border border-dashed border-gray-300 dark:border-gray-600 p-6 rounded-xl bg-gray-50 dark:bg-gray-800">
|
|
23
|
+
<div class="text-center">
|
|
24
|
+
<FaIcon @icon="layer-group" @size="2x" class="text-gray-400 mb-2" />
|
|
25
|
+
<div class="text-sm text-gray-600 dark:text-gray-400 font-medium">
|
|
26
|
+
No order activity
|
|
27
|
+
</div>
|
|
28
|
+
<div class="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
|
29
|
+
Dispatch or update the order to create activity
|
|
30
|
+
</div>
|
|
29
31
|
</div>
|
|
30
32
|
</div>
|
|
31
33
|
</div>
|
|
32
|
-
|
|
33
|
-
{{/
|
|
34
|
+
{{/if}}
|
|
35
|
+
{{/let}}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
@wrapperClass="bordered-top"
|
|
7
7
|
>
|
|
8
8
|
{{#if (eq this.layout "timeline")}}
|
|
9
|
-
<Order::ActivityTimeline @resource={{@resource}} />
|
|
9
|
+
<Order::ActivityTimeline @resource={{@resource}} @activity={{this.parentOrderActivity}} />
|
|
10
10
|
{{else}}
|
|
11
|
-
<Order::ActivityList @resource={{@resource}} />
|
|
11
|
+
<Order::ActivityList @resource={{@resource}} @activity={{this.parentOrderActivity}} />
|
|
12
12
|
{{/if}}
|
|
13
|
-
</ContentPanel>
|
|
13
|
+
</ContentPanel>
|
|
@@ -9,6 +9,17 @@ export default class OrderDetailsActivityComponent extends Component {
|
|
|
9
9
|
@service intl;
|
|
10
10
|
@tracked layout = this.appCache.get('fleetops:order:activity:layout', 'timeline');
|
|
11
11
|
|
|
12
|
+
get parentOrderActivity() {
|
|
13
|
+
const orderTrackingNumberUuid = this.args.resource?.tracking_number_uuid;
|
|
14
|
+
const activity = this.args.resource?.tracking_statuses ?? [];
|
|
15
|
+
|
|
16
|
+
if (!orderTrackingNumberUuid) {
|
|
17
|
+
return activity;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return activity.filter((trackingStatus) => trackingStatus.tracking_number_uuid === orderTrackingNumberUuid);
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
/* eslint-disable ember/no-side-effects */
|
|
13
24
|
get actionButtons() {
|
|
14
25
|
return [
|
|
@@ -20,9 +31,9 @@ export default class OrderDetailsActivityComponent extends Component {
|
|
|
20
31
|
disabled: this.args.resource.status === 'canceled',
|
|
21
32
|
onClick: () => {
|
|
22
33
|
this.orderActions.updateActivity(this.args.resource, {
|
|
23
|
-
onFinish: (
|
|
34
|
+
onFinish: (options) => {
|
|
24
35
|
if (typeof this.args.onChange === 'function') {
|
|
25
|
-
this.args.onChange(
|
|
36
|
+
this.args.onChange(options);
|
|
26
37
|
}
|
|
27
38
|
},
|
|
28
39
|
});
|
|
@@ -1,42 +1,44 @@
|
|
|
1
|
-
<
|
|
2
|
-
{{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{{/each}}
|
|
26
|
-
</div>
|
|
27
|
-
{{else}}
|
|
28
|
-
<div class="min-h-24 p-4">
|
|
29
|
-
<div class="flex items-center justify-center border border-dashed border-gray-300 dark:border-gray-600 p-6 rounded-xl bg-gray-50 dark:bg-gray-800">
|
|
30
|
-
<div class="text-center">
|
|
31
|
-
<FaIcon @icon="camera-retro" @size="2x" class="text-gray-400 mb-2" />
|
|
32
|
-
<div class="text-sm text-gray-600 dark:text-gray-400 font-medium">
|
|
33
|
-
No proof of delivery
|
|
1
|
+
<div {{did-update this.reloadProofs @reloadToken}}>
|
|
2
|
+
<ContentPanel @title={{t "order.fields.proof-of-delivery"}} @isLoading={{@isLoading}} @open={{true}} @wrapperClass="bordered-top">
|
|
3
|
+
{{#if this.proofs}}
|
|
4
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-3">
|
|
5
|
+
{{#each this.proofs as |proof|}}
|
|
6
|
+
<div>
|
|
7
|
+
{{#if (eq proof.type "photo")}}
|
|
8
|
+
<a href={{proof.url}} target={{proof.id}}>
|
|
9
|
+
<Image src={{proof.url}} class="w-48 h-48 rounded-md shadow-sm mb-2" />
|
|
10
|
+
</a>
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#if (eq proof.type "signature")}}
|
|
13
|
+
<a href={{proof.raw}} target={{proof.id}} download>
|
|
14
|
+
<Image src={{proof.raw}} class="w-48 h-48 rounded-md shadow-sm mb-2" />
|
|
15
|
+
</a>
|
|
16
|
+
{{/if}}
|
|
17
|
+
{{#if (eq proof.type "scan")}}
|
|
18
|
+
<div class="flex items-center mb-2">
|
|
19
|
+
<FaIcon @icon="qrcode" @size="2xl" class="text-blue-400" />
|
|
20
|
+
</div>
|
|
21
|
+
{{/if}}
|
|
22
|
+
<div class="font-semibold text-sm mb-0.5">{{proof.remarks}}</div>
|
|
23
|
+
<div class="text-xs">{{proof.public_id}}</div>
|
|
24
|
+
<div class="text-xs">{{format-date-fns proof.created_at}}</div>
|
|
34
25
|
</div>
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
{{/each}}
|
|
27
|
+
</div>
|
|
28
|
+
{{else}}
|
|
29
|
+
<div class="min-h-24 p-4">
|
|
30
|
+
<div class="flex items-center justify-center border border-dashed border-gray-300 dark:border-gray-600 p-6 rounded-xl bg-gray-50 dark:bg-gray-800">
|
|
31
|
+
<div class="text-center">
|
|
32
|
+
<FaIcon @icon="camera-retro" @size="2x" class="text-gray-400 mb-2" />
|
|
33
|
+
<div class="text-sm text-gray-600 dark:text-gray-400 font-medium">
|
|
34
|
+
No proof of delivery
|
|
35
|
+
</div>
|
|
36
|
+
<div class="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
|
37
|
+
Captured proof of delivery will show up here
|
|
38
|
+
</div>
|
|
37
39
|
</div>
|
|
38
40
|
</div>
|
|
39
41
|
</div>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
</
|
|
42
|
+
{{/if}}
|
|
43
|
+
</ContentPanel>
|
|
44
|
+
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action } from '@ember/object';
|
|
3
4
|
import { inject as service } from '@ember/service';
|
|
4
5
|
import { task } from 'ember-concurrency-decorators';
|
|
5
6
|
|
|
@@ -13,12 +14,18 @@ export default class OrderDetailsProofComponent extends Component {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
@task *loadOrderProofs(order) {
|
|
17
|
+
if (!order?.id) {
|
|
18
|
+
this.proofs = [];
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
const proofs = yield this.fetch.get(`orders/${order.id}/proofs`);
|
|
17
23
|
|
|
18
|
-
this.proofs = proofs
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
this.proofs = this.#normalizeProofs(proofs);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@action reloadProofs() {
|
|
28
|
+
this.loadOrderProofs.perform(this.args.resource);
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
#getTypeFromRemarks(remarks = '') {
|
|
@@ -27,4 +34,24 @@ export default class OrderDetailsProofComponent extends Component {
|
|
|
27
34
|
if (remarks.endsWith('Signature')) return 'signature';
|
|
28
35
|
return undefined;
|
|
29
36
|
}
|
|
37
|
+
|
|
38
|
+
#normalizeProofs(proofs = []) {
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
|
|
41
|
+
return proofs
|
|
42
|
+
.filter((proof) => {
|
|
43
|
+
const key = proof.uuid ?? proof.public_id ?? proof.id;
|
|
44
|
+
if (!key || seen.has(key)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
seen.add(key);
|
|
49
|
+
return true;
|
|
50
|
+
})
|
|
51
|
+
.map((proof) => ({
|
|
52
|
+
...proof,
|
|
53
|
+
type: this.#getTypeFromRemarks(proof.remarks),
|
|
54
|
+
}))
|
|
55
|
+
.sort((a, b) => new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime());
|
|
56
|
+
}
|
|
30
57
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{{#each @resource.purchase_rate.service_quote.items as |item|}}
|
|
13
13
|
<tr class="border-b border-gray-100 dark:border-gray-800 last:border-0">
|
|
14
14
|
<td class="py-1 pr-2 text-gray-700 dark:text-gray-300">
|
|
15
|
-
{{
|
|
15
|
+
{{item.details}}
|
|
16
16
|
</td>
|
|
17
17
|
<td class="py-1 pl-2 text-right font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap">
|
|
18
18
|
{{format-currency item.amount @resource.purchase_rate.service_quote.currency}}
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
</table>
|
|
34
34
|
</div>
|
|
35
35
|
</ContentPanel>
|
|
36
|
-
{{/if}}
|
|
36
|
+
{{/if}}
|
|
@@ -26,17 +26,25 @@
|
|
|
26
26
|
|
|
27
27
|
{{#if @resource.hasIntermediateWaypoints}}
|
|
28
28
|
<div>
|
|
29
|
-
<RouteList
|
|
29
|
+
<RouteList
|
|
30
|
+
@order={{@resource}}
|
|
31
|
+
@eta={{if this.showRouteEtaData @resource.eta}}
|
|
32
|
+
@showEta={{this.showRouteEtaData}}
|
|
33
|
+
@waypointActions={{hash
|
|
34
|
+
viewStopActivity=(hash label="View activity" fn=this.viewStopActivity)
|
|
35
|
+
viewWaypointLabel=(hash label=(t "order.fields.get-label") fn=this.viewWaypointLabel)
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
30
38
|
</div>
|
|
31
39
|
{{else}}
|
|
32
40
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 lg:gap-2">
|
|
33
41
|
<div class="field-info-container">
|
|
34
42
|
<label class="field-name">{{t "order.fields.pickup"}}</label>
|
|
35
|
-
<DisplayPlace @place={{@resource.payload.pickup}} @eta={{get @resource.eta @resource.payload.pickup.id}} />
|
|
43
|
+
<DisplayPlace @place={{@resource.payload.pickup}} @eta={{if this.showRouteEtaData (get @resource.eta @resource.payload.pickup.id)}} />
|
|
36
44
|
</div>
|
|
37
45
|
<div class="field-info-container">
|
|
38
46
|
<label class="field-name">{{t "order.fields.dropoff"}}</label>
|
|
39
|
-
<DisplayPlace @place={{@resource.payload.dropoff}} @eta={{get @resource.eta @resource.payload.dropoff.id}} />
|
|
47
|
+
<DisplayPlace @place={{@resource.payload.dropoff}} @eta={{if this.showRouteEtaData (get @resource.eta @resource.payload.dropoff.id)}} />
|
|
40
48
|
</div>
|
|
41
49
|
<div class="field-info-container">
|
|
42
50
|
<label class="field-name">{{t "order.fields.return"}}</label>
|