@fleetbase/fleetops-engine 0.4.18 → 0.4.19
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/addon/components/activity-form-panel.hbs +19 -0
- package/addon/components/activity-form-panel.js +18 -0
- package/addon/components/live-map.js +14 -7
- package/addon/controllers/operations/orders/index/new.js +5 -0
- package/addon/controllers/operations/service-rates/index/new.js +23 -2
- package/addon/routes/operations/service-rates/index/edit.js +14 -5
- package/addon/routes/operations/service-rates/index/new.js +2 -6
- package/addon/services/location.js +54 -44
- package/addon/templates/operations/service-rates/index/new.hbs +10 -1
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +2 -2
- package/server/config/fleetops.php +127 -4
- package/server/migrations/2024_03_13_061249_add_order_config_uuid_column_to_service_rates_table.php +28 -0
- package/server/src/Console/Commands/FixLegacyOrderConfigs.php +17 -11
- package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +10 -5
- package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +3 -0
- package/server/src/Http/Requests/CreateOrderRequest.php +32 -13
- package/server/src/Http/Requests/Internal/CreateOrderRequest.php +41 -10
- package/server/src/Http/Resources/v1/ServiceRate.php +34 -31
- package/server/src/Models/ServiceRate.php +9 -0
- package/translations/en-us.yaml +6 -1
|
@@ -16,6 +16,25 @@
|
|
|
16
16
|
<span class="dark:text-gray-100 text-sm ml-2">{{t "fleet-ops.component.activity-form-panel.completes-order"}}</span>
|
|
17
17
|
</Toggle>
|
|
18
18
|
</InputGroup>
|
|
19
|
+
<InputGroup @helpText={{t "fleet-ops.component.activity-form-panel.require-pod-help-text"}}>
|
|
20
|
+
<Toggle @isToggled={{this.activity.require_pod}} @onToggle={{fn (mut this.activity.require_pod)}} @helpText={{t "fleet-ops.component.activity-form-panel.require-pod-help-text"}}>
|
|
21
|
+
<span class="dark:text-gray-100 text-sm mx-2">{{t "fleet-ops.component.activity-form-panel.require-pod"}}</span>
|
|
22
|
+
</Toggle>
|
|
23
|
+
</InputGroup>
|
|
24
|
+
{{#if this.activity.require_pod}}
|
|
25
|
+
<InputGroup @name={{t "fleet-ops.component.activity-form-panel.select-pod-method"}}>
|
|
26
|
+
<select class="form-select w-full has--placeholder" {{on "change" this.setProofOfDeliveryMethod}}>
|
|
27
|
+
<option selected disabled>
|
|
28
|
+
{{t "fleet-ops.component.activity-form-panel.pod-method-placeholder"}}
|
|
29
|
+
</option>
|
|
30
|
+
{{#each this.podOptions as |podOption|}}
|
|
31
|
+
<option value={{podOption}} selected={{eq this.activity.pod_method podOption}}>
|
|
32
|
+
{{smart-humanize podOption}}
|
|
33
|
+
</option>
|
|
34
|
+
{{/each}}
|
|
35
|
+
</select>
|
|
36
|
+
</InputGroup>
|
|
37
|
+
{{/if}}
|
|
19
38
|
<ActivityLogicBuilder @activity={{this.activity}} @onChange={{this.updateActivityLogic}} class="mb-4" />
|
|
20
39
|
<ActivityEventSelector @activity={{this.activity}} @onChange={{this.updateActivityEvents}} />
|
|
21
40
|
<Spacer @height="300px" />
|
|
@@ -25,6 +25,13 @@ export default class ActivityFormPanelComponent extends Component {
|
|
|
25
25
|
*/
|
|
26
26
|
@tracked targetActivity;
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Proof of delivery options.
|
|
30
|
+
*
|
|
31
|
+
* @memberof ActivityFormPanelComponent
|
|
32
|
+
*/
|
|
33
|
+
@tracked podOptions = ['scan', 'signature', 'photo'];
|
|
34
|
+
|
|
28
35
|
/**
|
|
29
36
|
* Constructor for ActivityFormPanelComponent.
|
|
30
37
|
* Applies context component arguments upon instantiation.
|
|
@@ -46,6 +53,17 @@ export default class ActivityFormPanelComponent extends Component {
|
|
|
46
53
|
}
|
|
47
54
|
}
|
|
48
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Sets the proof of delivery method to be used for this activity.
|
|
58
|
+
*
|
|
59
|
+
* @param {Event} event
|
|
60
|
+
* @memberof ActivityFormPanelComponent
|
|
61
|
+
*/
|
|
62
|
+
@action setProofOfDeliveryMethod(event) {
|
|
63
|
+
const value = event.target.value;
|
|
64
|
+
this.activity.set('pod_method', value);
|
|
65
|
+
}
|
|
66
|
+
|
|
49
67
|
/**
|
|
50
68
|
* Action method to set the activity code. It uses the underscore function to format
|
|
51
69
|
* the code and updates the status by capitalizing each word.
|
|
@@ -280,7 +280,6 @@ export default class LiveMapComponent extends Component {
|
|
|
280
280
|
this.tileSourceUrl = 'https://{s}.tile.jawg.io/jawg-matrix/{z}/{x}/{y}{r}.png?access-token=';
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
this.ready();
|
|
284
283
|
this.setupComponent();
|
|
285
284
|
}
|
|
286
285
|
|
|
@@ -332,7 +331,6 @@ export default class LiveMapComponent extends Component {
|
|
|
332
331
|
})
|
|
333
332
|
.finally(() => {
|
|
334
333
|
this.listen();
|
|
335
|
-
this.ready();
|
|
336
334
|
});
|
|
337
335
|
}
|
|
338
336
|
|
|
@@ -386,14 +384,23 @@ export default class LiveMapComponent extends Component {
|
|
|
386
384
|
* if available, or null if the function is skipped.
|
|
387
385
|
*/
|
|
388
386
|
async setInitialCoordinates() {
|
|
389
|
-
|
|
387
|
+
try {
|
|
388
|
+
const { latitude, longitude } = await this.location.getUserLocation();
|
|
389
|
+
|
|
390
|
+
this.latitude = latitude || this.location.DEFAULT_LATITUDE;
|
|
391
|
+
this.longitude = longitude || this.location.DEFAULT_LONGITUDE;
|
|
392
|
+
} catch (error) {
|
|
393
|
+
this.latitude = this.location.DEFAULT_LATITUDE;
|
|
394
|
+
this.longitude = this.location.DEFAULT_LONGITUDE;
|
|
395
|
+
}
|
|
390
396
|
|
|
391
|
-
this.latitude = latitude;
|
|
392
|
-
this.longitude = longitude;
|
|
393
397
|
this.ready();
|
|
394
398
|
|
|
395
|
-
//
|
|
396
|
-
this.universe.trigger('fleet-ops.live-map.has_coordinates', {
|
|
399
|
+
// Trigger that initial coordinates are set to live map component
|
|
400
|
+
this.universe.trigger('fleet-ops.live-map.has_coordinates', {
|
|
401
|
+
latitude: this.latitude,
|
|
402
|
+
longitude: this.longitude,
|
|
403
|
+
});
|
|
397
404
|
}
|
|
398
405
|
|
|
399
406
|
/**
|
|
@@ -534,6 +534,11 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
534
534
|
params.facilitator = this.order.facilitator.public_id;
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
+
// filter by order config type
|
|
538
|
+
if (this.orderConfig) {
|
|
539
|
+
params.service_type = this.orderConfig.key;
|
|
540
|
+
}
|
|
541
|
+
|
|
537
542
|
if (shouldCheck) {
|
|
538
543
|
try {
|
|
539
544
|
serviceRates = await this.fetch.get(`service-rates/for-route`, params);
|
|
@@ -48,11 +48,11 @@ export default class OperationsServiceRatesIndexNewController extends BaseContro
|
|
|
48
48
|
@tracked serviceRate = this.store.createRecord('service-rate', { per_meter_unit: 'm' });
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
51
|
+
* Available order configs.
|
|
52
52
|
*
|
|
53
53
|
* @var {Array}
|
|
54
54
|
*/
|
|
55
|
-
@tracked
|
|
55
|
+
@tracked orderConfigs = [];
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Service areas.
|
|
@@ -75,6 +75,13 @@ export default class OperationsServiceRatesIndexNewController extends BaseContro
|
|
|
75
75
|
*/
|
|
76
76
|
@tracked isCreatingServiceRate = false;
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The current selected order config.
|
|
80
|
+
*
|
|
81
|
+
* @var {OrderConfigModel|null}
|
|
82
|
+
*/
|
|
83
|
+
@tracked orderConfig;
|
|
84
|
+
|
|
78
85
|
/**
|
|
79
86
|
* True if updating service rate.
|
|
80
87
|
*
|
|
@@ -279,6 +286,20 @@ export default class OperationsServiceRatesIndexNewController extends BaseContro
|
|
|
279
286
|
});
|
|
280
287
|
}
|
|
281
288
|
|
|
289
|
+
@action setConfig(event) {
|
|
290
|
+
const orderConfigId = event.target.value;
|
|
291
|
+
if (!orderConfigId) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const orderConfig = this.store.peekRecord('order-config', orderConfigId);
|
|
296
|
+
if (orderConfig) {
|
|
297
|
+
this.orderConfig = orderConfig;
|
|
298
|
+
this.serviceRate.set('order_config_uuid', orderConfig.id);
|
|
299
|
+
this.serviceRate.set('service_type', orderConfig.key);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
282
303
|
/**
|
|
283
304
|
* Adds a per drop-off rate fee
|
|
284
305
|
*/
|
|
@@ -7,8 +7,20 @@ export default class OperationsServiceRatesIndexEditRoute extends Route {
|
|
|
7
7
|
@service currentUser;
|
|
8
8
|
@service notifications;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Re-use the new service rate form template.
|
|
12
|
+
*
|
|
13
|
+
* @memberof OperationsServiceRatesIndexEditRoute
|
|
14
|
+
*/
|
|
10
15
|
templateName = 'operations.service-rates.index.new';
|
|
11
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Handle any async error.
|
|
19
|
+
*
|
|
20
|
+
* @param {*} error
|
|
21
|
+
* @return {*}
|
|
22
|
+
* @memberof OperationsServiceRatesIndexEditRoute
|
|
23
|
+
*/
|
|
12
24
|
@action error(error) {
|
|
13
25
|
this.notifications.serverError(error);
|
|
14
26
|
return this.transitionTo('operations.service-rates.index');
|
|
@@ -33,10 +45,7 @@ export default class OperationsServiceRatesIndexEditRoute extends Route {
|
|
|
33
45
|
controller.parcelFees = model.parcel_fees;
|
|
34
46
|
}
|
|
35
47
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
controller.serviceTypes = serviceTypes;
|
|
40
|
-
controller.serviceAreas = serviceAreas;
|
|
48
|
+
controller.orderConfigs = await this.store.findAll('order-config');
|
|
49
|
+
controller.serviceAreas = await this.store.findAll('service-area');
|
|
41
50
|
}
|
|
42
51
|
}
|
|
@@ -3,13 +3,9 @@ import { inject as service } from '@ember/service';
|
|
|
3
3
|
|
|
4
4
|
export default class OperationsServiceRatesIndexNewRoute extends Route {
|
|
5
5
|
@service store;
|
|
6
|
-
@service currentUser;
|
|
7
6
|
|
|
8
7
|
async setupController(controller) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
controller.serviceTypes = serviceTypes;
|
|
13
|
-
controller.serviceAreas = serviceAreas;
|
|
8
|
+
controller.orderConfigs = await this.store.findAll('order-config');
|
|
9
|
+
controller.serviceAreas = await this.store.findAll('service-area');
|
|
14
10
|
}
|
|
15
11
|
}
|
|
@@ -78,66 +78,74 @@ export default class LocationService extends Service {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Attempts to fetch the user's location from various sources
|
|
82
|
-
*
|
|
81
|
+
* Attempts to fetch the user's location from various sources including cached data,
|
|
82
|
+
* navigator geolocation, or WHOIS data. It first tries to get the cached coordinates.
|
|
83
|
+
* If not available or outdated, it tries the browser's geolocation API.
|
|
84
|
+
* As a fallback, it uses WHOIS data associated with the user's account.
|
|
85
|
+
*
|
|
83
86
|
* @returns {Promise<Object>} A promise that resolves to an object containing latitude and longitude.
|
|
84
87
|
*/
|
|
85
|
-
getUserLocation() {
|
|
86
|
-
|
|
88
|
+
async getUserLocation() {
|
|
89
|
+
// If the location has already been located, return the existing coordinates
|
|
90
|
+
if (this.located) {
|
|
91
|
+
return { latitude: this.latitude, longitude: this.longitude };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const coordinates = await this.fetch.cachedGet('fleet-ops/live/coordinates', {}, { expirationInterval: 1, expirationIntervalUnit: 'hour' });
|
|
96
|
+
|
|
87
97
|
if (isBlank(coordinates)) {
|
|
88
|
-
return this.getUserLocationFromNavigator()
|
|
89
|
-
this.updateLocation(navigatorCoordinates);
|
|
90
|
-
return navigatorCoordinates;
|
|
91
|
-
});
|
|
98
|
+
return await this.getUserLocationFromNavigator();
|
|
92
99
|
}
|
|
93
100
|
|
|
94
|
-
if (isArray(coordinates)) {
|
|
95
|
-
|
|
101
|
+
if (isArray(coordinates) && coordinates.length > 0) {
|
|
102
|
+
// Ensure the coordinates array contains valid data
|
|
103
|
+
const validCoordinates = coordinates.find((point) => point.coordinates[0] !== 0 && point.coordinates[1] !== 0);
|
|
96
104
|
if (validCoordinates) {
|
|
97
|
-
const [longitude, latitude] =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
longitude,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
this.updateLocation(userCoordinates);
|
|
104
|
-
return userCoordinates;
|
|
105
|
+
const [longitude, latitude] = validCoordinates.coordinates;
|
|
106
|
+
this.updateLocation({ latitude, longitude });
|
|
107
|
+
return { latitude, longitude };
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
|
|
108
|
-
return this.getUserLocationFromWhois();
|
|
109
|
-
})
|
|
111
|
+
return await this.getUserLocationFromWhois();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return await this.getUserLocationFromWhois();
|
|
114
|
+
}
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
/**
|
|
113
118
|
* Retrieves the user's location using the browser's navigator geolocation API.
|
|
114
|
-
*
|
|
119
|
+
* It creates a promise that resolves with the geolocation if successful,
|
|
120
|
+
* or with WHOIS data as a fallback in case of failure or absence of navigator geolocation.
|
|
121
|
+
*
|
|
122
|
+
* @returns {Promise<Object>} A promise that resolves to geolocation coordinates or WHOIS data.
|
|
115
123
|
*/
|
|
116
|
-
getUserLocationFromNavigator() {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
124
|
+
async getUserLocationFromNavigator() {
|
|
125
|
+
if (window.navigator && window.navigator.geolocation) {
|
|
126
|
+
try {
|
|
127
|
+
const position = await new Promise((resolve, reject) => {
|
|
128
|
+
navigator.geolocation.getCurrentPosition(resolve, reject, {
|
|
129
|
+
enableHighAccuracy: true,
|
|
130
|
+
timeout: 600,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
const { latitude, longitude } = position.coords;
|
|
134
|
+
this.updateLocation({ latitude, longitude });
|
|
135
|
+
return { latitude, longitude };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return await this.getUserLocationFromWhois();
|
|
131
138
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
139
|
+
} else {
|
|
140
|
+
return await this.getUserLocationFromWhois();
|
|
141
|
+
}
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
/**
|
|
139
|
-
* Retrieves the user's location based on WHOIS data associated with
|
|
140
|
-
*
|
|
145
|
+
* Retrieves the user's location based on WHOIS data associated with their account.
|
|
146
|
+
* Defaults to predefined coordinates if WHOIS data is not available.
|
|
147
|
+
*
|
|
148
|
+
* @returns {Object} An object containing latitude and longitude from WHOIS data or default values.
|
|
141
149
|
*/
|
|
142
150
|
getUserLocationFromWhois() {
|
|
143
151
|
const whois = this.currentUser.getOption('whois');
|
|
@@ -151,8 +159,10 @@ export default class LocationService extends Service {
|
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
/**
|
|
154
|
-
* Updates the service's tracked properties with the new location data
|
|
155
|
-
*
|
|
162
|
+
* Updates the service's tracked properties with the new location data.
|
|
163
|
+
* Triggers an event to notify other parts of the application that the user's location has been updated.
|
|
164
|
+
*
|
|
165
|
+
* @param {Object} coordinates - An object containing the latitude and longitude to be set.
|
|
156
166
|
*/
|
|
157
167
|
updateLocation({ latitude, longitude }) {
|
|
158
168
|
this.latitude = latitude;
|
|
@@ -17,7 +17,16 @@
|
|
|
17
17
|
|
|
18
18
|
<div class="input-group">
|
|
19
19
|
<InputLabel @labelText={{t "fleet-ops.operations.service-rates.index.new.service-order-label"}} @helpText={{t "fleet-ops.operations.service-rates.index.new.service-order-help-text"}} />
|
|
20
|
-
<
|
|
20
|
+
<select class="form-select w-full has--placeholder" {{on "change" this.setConfig}}>
|
|
21
|
+
<option selected disabled>
|
|
22
|
+
{{t "fleet-ops.operations.service-rates.index.new.service-order-placeholder"}}
|
|
23
|
+
</option>
|
|
24
|
+
{{#each this.orderConfigs as |orderConfig|}}
|
|
25
|
+
<option value={{orderConfig.id}} selected={{eq orderConfig.id this.serviceRate.order_config_uuid}}>
|
|
26
|
+
{{orderConfig.name}}
|
|
27
|
+
</option>
|
|
28
|
+
{{/each}}
|
|
29
|
+
</select>
|
|
21
30
|
</div>
|
|
22
31
|
|
|
23
32
|
<div class="input-group">
|
package/composer.json
CHANGED
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetbase/fleetops-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.19",
|
|
4
4
|
"description": "Fleet & Transport Management Extension for Fleetbase",
|
|
5
5
|
"fleetbase": {
|
|
6
6
|
"route": "fleet-ops"
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@babel/core": "^7.23.2",
|
|
45
45
|
"@fleetbase/ember-core": "^0.2.6",
|
|
46
46
|
"@fleetbase/ember-ui": "^0.2.11",
|
|
47
|
-
"@fleetbase/fleetops-data": "^0.1.
|
|
47
|
+
"@fleetbase/fleetops-data": "^0.1.14",
|
|
48
48
|
"@fleetbase/leaflet-routing-machine": "^3.2.16",
|
|
49
49
|
"@fortawesome/ember-fontawesome": "^0.4.1",
|
|
50
50
|
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* -------------------------------------------
|
|
5
|
-
*
|
|
5
|
+
* FleetOps API Configuration
|
|
6
6
|
* -------------------------------------------
|
|
7
7
|
*/
|
|
8
8
|
return [
|
|
9
|
+
/*
|
|
10
|
+
|--------------------------------------------------------------------------
|
|
11
|
+
| API Config
|
|
12
|
+
|--------------------------------------------------------------------------
|
|
13
|
+
*/
|
|
9
14
|
'api' => [
|
|
10
15
|
'version' => '0.0.1',
|
|
11
16
|
'routing' => [
|
|
@@ -13,10 +18,128 @@ return [
|
|
|
13
18
|
'internal_prefix' => 'int'
|
|
14
19
|
]
|
|
15
20
|
],
|
|
21
|
+
'connection' => [
|
|
22
|
+
'db' => env('DB_CONNECTION', 'mysql')
|
|
23
|
+
],
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
|--------------------------------------------------------------------------
|
|
27
|
+
| Navigator App
|
|
28
|
+
|--------------------------------------------------------------------------
|
|
29
|
+
*/
|
|
16
30
|
'navigator' => [
|
|
17
31
|
'bypass_verification_code' => env('NAVIGATOR_BYPASS_VERIFICATION_CODE', '999000')
|
|
18
32
|
],
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
|--------------------------------------------------------------------------
|
|
36
|
+
| API Events
|
|
37
|
+
|--------------------------------------------------------------------------
|
|
38
|
+
*/
|
|
39
|
+
'events' => [
|
|
40
|
+
// order events
|
|
41
|
+
'order.created',
|
|
42
|
+
'order.updated',
|
|
43
|
+
'order.deleted',
|
|
44
|
+
'order.dispatched',
|
|
45
|
+
'order.dispatch_failed',
|
|
46
|
+
'order.completed',
|
|
47
|
+
'order.failed',
|
|
48
|
+
'order.driver_assigned',
|
|
49
|
+
'order.completed',
|
|
50
|
+
|
|
51
|
+
// payload events
|
|
52
|
+
'payload.created',
|
|
53
|
+
'payload.updated',
|
|
54
|
+
'payload.deleted',
|
|
55
|
+
|
|
56
|
+
// entity events
|
|
57
|
+
'entity.created',
|
|
58
|
+
'entity.updated',
|
|
59
|
+
'entity.deleted',
|
|
60
|
+
'entity.driver_assigned',
|
|
61
|
+
|
|
62
|
+
// driver events
|
|
63
|
+
'driver.created',
|
|
64
|
+
'driver.updated',
|
|
65
|
+
'driver.deleted',
|
|
66
|
+
'driver.assigned',
|
|
67
|
+
// 'driver.entered_zone',
|
|
68
|
+
// 'driver.exited_zone',
|
|
69
|
+
|
|
70
|
+
// fleet events
|
|
71
|
+
'fleet.created',
|
|
72
|
+
'fleet.updated',
|
|
73
|
+
'fleet.deleted',
|
|
74
|
+
|
|
75
|
+
// purchase_rate events
|
|
76
|
+
'purchase_rate.created',
|
|
77
|
+
'purchase_rate.updated',
|
|
78
|
+
'purchase_rate.deleted',
|
|
79
|
+
|
|
80
|
+
// contact events
|
|
81
|
+
'contact.created',
|
|
82
|
+
'contact.updated',
|
|
83
|
+
'contact.deleted',
|
|
84
|
+
|
|
85
|
+
// place events
|
|
86
|
+
'place.created',
|
|
87
|
+
'place.updated',
|
|
88
|
+
'place.deleted',
|
|
89
|
+
|
|
90
|
+
// service_area events
|
|
91
|
+
'service_area.created',
|
|
92
|
+
'service_area.updated',
|
|
93
|
+
'service_area.deleted',
|
|
94
|
+
|
|
95
|
+
// service_quote events
|
|
96
|
+
'service_quote.created',
|
|
97
|
+
'service_quote.updated',
|
|
98
|
+
'service_quote.deleted',
|
|
99
|
+
|
|
100
|
+
// service_rate events
|
|
101
|
+
'service_rate.created',
|
|
102
|
+
'service_rate.updated',
|
|
103
|
+
'service_rate.deleted',
|
|
104
|
+
|
|
105
|
+
// tracking_number events
|
|
106
|
+
'tracking_number.created',
|
|
107
|
+
'tracking_number.updated',
|
|
108
|
+
'tracking_number.deleted',
|
|
109
|
+
|
|
110
|
+
// tracking_status events
|
|
111
|
+
'tracking_status.created',
|
|
112
|
+
'tracking_status.updated',
|
|
113
|
+
'tracking_status.deleted',
|
|
114
|
+
|
|
115
|
+
// vehicle events
|
|
116
|
+
'vehicle.created',
|
|
117
|
+
'vehicle.updated',
|
|
118
|
+
'vehicle.deleted',
|
|
119
|
+
|
|
120
|
+
// vendor events
|
|
121
|
+
'vendor.created',
|
|
122
|
+
'vendor.updated',
|
|
123
|
+
'vendor.deleted',
|
|
124
|
+
|
|
125
|
+
// zone events
|
|
126
|
+
'zone.created',
|
|
127
|
+
'zone.updated',
|
|
128
|
+
'zone.deleted',
|
|
129
|
+
],
|
|
130
|
+
|
|
131
|
+
/*
|
|
132
|
+
|--------------------------------------------------------------------------
|
|
133
|
+
| Proof of Delivery Methods
|
|
134
|
+
|--------------------------------------------------------------------------
|
|
135
|
+
*/
|
|
136
|
+
'pod_methods' => 'scan,signature,photo',
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
|--------------------------------------------------------------------------
|
|
140
|
+
| API/Webhook Versions
|
|
141
|
+
|--------------------------------------------------------------------------
|
|
142
|
+
*/
|
|
143
|
+
'versions' => ['2020-09-30', '2024-03-12'],
|
|
144
|
+
'version' => '2024-03-12',
|
|
22
145
|
];
|
package/server/migrations/2024_03_13_061249_add_order_config_uuid_column_to_service_rates_table.php
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Illuminate\Database\Migrations\Migration;
|
|
4
|
+
use Illuminate\Database\Schema\Blueprint;
|
|
5
|
+
use Illuminate\Support\Facades\Schema;
|
|
6
|
+
|
|
7
|
+
return new class() extends Migration {
|
|
8
|
+
/**
|
|
9
|
+
* Run the migrations.
|
|
10
|
+
*/
|
|
11
|
+
public function up(): void
|
|
12
|
+
{
|
|
13
|
+
Schema::table('service_rates', function (Blueprint $table) {
|
|
14
|
+
$table->foreignUuid('order_config_uuid')->nullable()->after('zone_uuid')->references('uuid')->on('order_configs')->onUpdate('CASCADE')->onDelete('CASCADE');
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Reverse the migrations.
|
|
20
|
+
*/
|
|
21
|
+
public function down(): void
|
|
22
|
+
{
|
|
23
|
+
Schema::table('service_rates', function (Blueprint $table) {
|
|
24
|
+
$table->dropForeign(['order_config_uuid']);
|
|
25
|
+
$table->dropColumn(['order_config_uuid']);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -4,9 +4,9 @@ namespace Fleetbase\FleetOps\Console\Commands;
|
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Models\Order;
|
|
6
6
|
use Fleetbase\FleetOps\Models\OrderConfig;
|
|
7
|
-
use Fleetbase\Support\
|
|
7
|
+
use Fleetbase\FleetOps\Support\FleetOps;
|
|
8
|
+
use Fleetbase\Models\Company;
|
|
8
9
|
use Illuminate\Console\Command;
|
|
9
|
-
use Illuminate\Support\Facades\Artisan;
|
|
10
10
|
|
|
11
11
|
class FixLegacyOrderConfigs extends Command
|
|
12
12
|
{
|
|
@@ -15,7 +15,7 @@ class FixLegacyOrderConfigs extends Command
|
|
|
15
15
|
*
|
|
16
16
|
* @var string
|
|
17
17
|
*/
|
|
18
|
-
protected $signature = 'fleetops:fix-legacy-order-configs';
|
|
18
|
+
protected $signature = 'fleetops:fix-legacy-order-configs {--create-configs}';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* The console command description.
|
|
@@ -35,14 +35,20 @@ class FixLegacyOrderConfigs extends Command
|
|
|
35
35
|
*/
|
|
36
36
|
public function handle()
|
|
37
37
|
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
$
|
|
38
|
+
$shouldCreateConfigs = $this->option('create-configs');
|
|
39
|
+
if ($shouldCreateConfigs) {
|
|
40
|
+
$companies = Company::all();
|
|
41
|
+
$totalCompanies = $companies->count();
|
|
42
|
+
$this->info('Initializing transport config for ' . $totalCompanies . ' companies.');
|
|
43
|
+
$progressBar = $this->output->createProgressBar($totalCompanies);
|
|
44
|
+
$progressBar->start();
|
|
45
|
+
foreach ($companies as $company) {
|
|
46
|
+
FleetOps::createTransportConfig($company);
|
|
47
|
+
$progressBar->advance();
|
|
48
|
+
}
|
|
49
|
+
$progressBar->finish();
|
|
50
|
+
$this->line('');
|
|
51
|
+
$this->info('All transport configs created.');
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
$orders = Order::whereNull('order_config_uuid')->get();
|
|
@@ -4,6 +4,7 @@ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
|
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Http\Requests\QueryServiceQuotesRequest;
|
|
6
6
|
use Fleetbase\FleetOps\Http\Resources\v1\ServiceQuote as ServiceQuoteResource;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Entity;
|
|
7
8
|
use Fleetbase\FleetOps\Models\IntegratedVendor;
|
|
8
9
|
use Fleetbase\FleetOps\Models\Payload;
|
|
9
10
|
use Fleetbase\FleetOps\Models\Place;
|
|
@@ -76,11 +77,15 @@ class ServiceQuoteController extends Controller
|
|
|
76
77
|
|
|
77
78
|
// if quote for single service
|
|
78
79
|
if ($service && $service !== 'all') {
|
|
79
|
-
$serviceRate = ServiceRate::where(
|
|
80
|
-
|
|
81
|
-
$
|
|
82
|
-
}
|
|
83
|
-
|
|
80
|
+
$serviceRate = ServiceRate::where(
|
|
81
|
+
function ($query) use ($service) {
|
|
82
|
+
$query->where('uuid', $service)->orWhere('public_id', $service);
|
|
83
|
+
})->where(
|
|
84
|
+
function ($q) use ($currency) {
|
|
85
|
+
if ($currency) {
|
|
86
|
+
$q->where(DB::raw('lower(currency)'), strtolower($currency));
|
|
87
|
+
}
|
|
88
|
+
})->first();
|
|
84
89
|
$serviceQuotes = collect();
|
|
85
90
|
|
|
86
91
|
if ($serviceRate) {
|
|
@@ -39,6 +39,9 @@ class ServiceRateController extends FleetOpsController
|
|
|
39
39
|
$waypoints,
|
|
40
40
|
function ($query) use ($request) {
|
|
41
41
|
$query->where('company_uuid', $request->session()->get('company'));
|
|
42
|
+
if ($request->filled('service_type')) {
|
|
43
|
+
$query->where('service_type', $request->input('service_type'));
|
|
44
|
+
}
|
|
42
45
|
}
|
|
43
46
|
);
|
|
44
47
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Requests;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
5
6
|
use Fleetbase\Http\Requests\FleetbaseRequest;
|
|
6
7
|
use Fleetbase\Rules\ExistsInAny;
|
|
7
8
|
|
|
@@ -25,21 +26,26 @@ class CreateOrderRequest extends FleetbaseRequest
|
|
|
25
26
|
public function rules()
|
|
26
27
|
{
|
|
27
28
|
$validations = [
|
|
28
|
-
'adhoc'
|
|
29
|
-
'dispatch'
|
|
30
|
-
'adhoc_distance'
|
|
31
|
-
'pod_required'
|
|
32
|
-
'pod_method'
|
|
33
|
-
'scheduled_at'
|
|
34
|
-
'driver'
|
|
35
|
-
'service_quote'
|
|
36
|
-
'purchase_rate'
|
|
37
|
-
'facilitator'
|
|
38
|
-
'customer'
|
|
39
|
-
'status'
|
|
40
|
-
'type'
|
|
29
|
+
'adhoc' => ['nullable', 'boolean'],
|
|
30
|
+
'dispatch' => ['nullable', 'boolean'],
|
|
31
|
+
'adhoc_distance' => ['nullable', 'numeric'],
|
|
32
|
+
'pod_required' => ['nullable', 'boolean'],
|
|
33
|
+
'pod_method' => ['nullable', 'in:' . config('fleetops.pod_methods')],
|
|
34
|
+
'scheduled_at' => ['nullable', 'date'],
|
|
35
|
+
'driver' => ['nullable', 'exists:drivers,public_id'],
|
|
36
|
+
'service_quote' => ['nullable', 'exists:service_quotes,public_id'],
|
|
37
|
+
'purchase_rate' => ['nullable', 'exists:purchase_rates,public_id'],
|
|
38
|
+
'facilitator' => ['nullable', new ExistsInAny(['vendors', 'contacts', 'integrated_vendors'], ['public_id', 'provider'])],
|
|
39
|
+
'customer' => ['nullable', new ExistsInAny(['vendors', 'contacts'], 'public_id')],
|
|
40
|
+
'status' => ['nullable', 'string'],
|
|
41
|
+
'type' => ['string'],
|
|
41
42
|
];
|
|
42
43
|
|
|
44
|
+
// Conditionally require 'pod_method' if 'pod_required' is truthy
|
|
45
|
+
if (Utils::isTrue($this->input('pod_required'))) {
|
|
46
|
+
$validations['pod_method'] = ['required', 'in:' . implode(',', config('fleetops.pod_methods'))];
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
if ($this->has('payload')) {
|
|
44
50
|
$validations['payload.entities'] = 'array';
|
|
45
51
|
$validations['payload.waypoints'] = 'array';
|
|
@@ -77,4 +83,17 @@ class CreateOrderRequest extends FleetbaseRequest
|
|
|
77
83
|
|
|
78
84
|
return $validations;
|
|
79
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get custom attributes for validator errors.
|
|
89
|
+
*
|
|
90
|
+
* @return array<string, string>
|
|
91
|
+
*/
|
|
92
|
+
public function attributes(): array
|
|
93
|
+
{
|
|
94
|
+
return [
|
|
95
|
+
'pod_required' => 'proof of delivery required',
|
|
96
|
+
'pod_method' => 'proof of delivery method',
|
|
97
|
+
];
|
|
98
|
+
}
|
|
80
99
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Requests\Internal;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
5
6
|
use Fleetbase\Http\Requests\FleetbaseRequest;
|
|
6
7
|
use Fleetbase\Rules\ExistsInAny;
|
|
7
8
|
|
|
@@ -25,22 +26,27 @@ class CreateOrderRequest extends FleetbaseRequest
|
|
|
25
26
|
public function rules()
|
|
26
27
|
{
|
|
27
28
|
$validations = [
|
|
28
|
-
'order_config_uuid' => 'required',
|
|
29
|
-
|
|
29
|
+
'order_config_uuid' => ['required'],
|
|
30
|
+
'adhoc' => ['nullable', 'boolean'],
|
|
30
31
|
'dispatch' => ['nullable', 'boolean'],
|
|
31
|
-
|
|
32
|
-
'pod_required' => '
|
|
33
|
-
'pod_method' => 'in:' . config('
|
|
32
|
+
'adhoc_distance' => ['nullable', 'numeric'],
|
|
33
|
+
'pod_required' => ['nullable', 'boolean'],
|
|
34
|
+
'pod_method' => ['nullable', 'in:' . config('fleetops.pod_methods')],
|
|
34
35
|
'scheduled_at' => ['nullable', 'date'],
|
|
35
|
-
'driver' => 'nullable
|
|
36
|
-
'service_quote' => 'nullable
|
|
37
|
-
'purchase_rate' => 'nullable
|
|
36
|
+
'driver' => ['nullable', 'exists:drivers,uuid'],
|
|
37
|
+
'service_quote' => ['nullable', 'exists:service_quotes,uuid'],
|
|
38
|
+
'purchase_rate' => ['nullable', 'exists:purchase_rates,uuid'],
|
|
38
39
|
'facilitator' => ['nullable', new ExistsInAny(['vendors', 'contacts', 'integrated_vendors'], ['uuid', 'provider'])],
|
|
39
40
|
'customer' => ['nullable', new ExistsInAny(['vendors', 'contacts'], 'uuid')],
|
|
40
|
-
'status' => 'nullable
|
|
41
|
-
'type' => 'string',
|
|
41
|
+
'status' => ['nullable', 'string'],
|
|
42
|
+
'type' => ['string'],
|
|
42
43
|
];
|
|
43
44
|
|
|
45
|
+
// Conditionally require 'pod_method' if 'pod_required' is truthy
|
|
46
|
+
if (Utils::isTrue($this->input('order.pod_required'))) {
|
|
47
|
+
$validations['pod_method'] = ['required', 'in:' . config('fleetops.pod_methods')];
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
if ($this->has('payload')) {
|
|
45
51
|
$validations['payload.entities'] = 'array';
|
|
46
52
|
$validations['payload.waypoints'] = 'array';
|
|
@@ -63,4 +69,29 @@ class CreateOrderRequest extends FleetbaseRequest
|
|
|
63
69
|
|
|
64
70
|
return $validations;
|
|
65
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get the error messages for the defined validation rules.
|
|
75
|
+
*
|
|
76
|
+
* @return array<string, string>
|
|
77
|
+
*/
|
|
78
|
+
public function messages(): array
|
|
79
|
+
{
|
|
80
|
+
return [
|
|
81
|
+
'pod_method.required' => 'A proof of delivery method is required.',
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get custom attributes for validator errors.
|
|
87
|
+
*
|
|
88
|
+
* @return array<string, string>
|
|
89
|
+
*/
|
|
90
|
+
public function attributes(): array
|
|
91
|
+
{
|
|
92
|
+
return [
|
|
93
|
+
'pod_required' => 'proof of delivery required',
|
|
94
|
+
'pod_method' => 'proof of delivery method',
|
|
95
|
+
];
|
|
96
|
+
}
|
|
66
97
|
}
|
|
@@ -18,37 +18,40 @@ class ServiceRate extends FleetbaseResource
|
|
|
18
18
|
public function toArray($request)
|
|
19
19
|
{
|
|
20
20
|
return [
|
|
21
|
-
'id'
|
|
22
|
-
'uuid'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
21
|
+
'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
|
|
22
|
+
'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
|
|
23
|
+
'service_area_uuid' => $this->when(Http::isInternalRequest(), $this->service_area_uuid),
|
|
24
|
+
'zone_uuid' => $this->when(Http::isInternalRequest(), $this->zone_uuid),
|
|
25
|
+
'order_config_uuid' => $this->when(Http::isInternalRequest(), $this->order_config_uuid),
|
|
26
|
+
'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
|
|
27
|
+
'service_area' => $this->whenLoaded('serviceArea', new ServiceArea($this->serviceArea)),
|
|
28
|
+
'service_area_name' => $this->when(Http::isInternalRequest(), data_get($this, 'serviceArea.name')),
|
|
29
|
+
'zone' => $this->whenLoaded('zone', new Zone($this->zone)),
|
|
30
|
+
'zone_name' => $this->when(Http::isInternalRequest(), data_get($this, 'zone.name')),
|
|
31
|
+
'service_name' => $this->service_name,
|
|
32
|
+
'service_type' => $this->service_type,
|
|
33
|
+
'base_fee' => $this->base_fee,
|
|
34
|
+
'rate_calculation_method' => $this->rate_calculation_method,
|
|
35
|
+
'per_meter_flat_rate_fee' => $this->per_meter_flat_rate_fee,
|
|
36
|
+
'per_meter_unit' => $this->per_meter_unit,
|
|
37
|
+
'meter_fees' => ServiceRateFee::collection($this->rateFees ?? []),
|
|
38
|
+
'parcel_fees' => ServiceRateParcelFee::collection($this->rateFees ?? []),
|
|
39
|
+
'algorithm' => $this->algorithm,
|
|
40
|
+
'has_cod_fee' => Utils::castBoolean($this->has_cod_fee),
|
|
41
|
+
'cod_calculation_method' => $this->cod_calculation_method,
|
|
42
|
+
'cod_flat_fee' => $this->cod_flat_fee,
|
|
43
|
+
'cod_percent' => $this->cod_percent,
|
|
44
|
+
'has_peak_hours_fee' => Utils::castBoolean($this->has_peak_hours_fee),
|
|
45
|
+
'peak_hours_calculation_method' => $this->peak_hours_calculation_method,
|
|
46
|
+
'peak_hours_flat_fee' => $this->peak_hours_flat_fee,
|
|
47
|
+
'peak_hours_percent' => $this->peak_hours_percent,
|
|
48
|
+
'peak_hours_start' => $this->peak_hours_start,
|
|
49
|
+
'peak_hours_end' => $this->peak_hours_end,
|
|
50
|
+
'currency' => $this->currency,
|
|
51
|
+
'duration_terms' => $this->duration_terms,
|
|
52
|
+
'estimated_days' => $this->estimated_days,
|
|
53
|
+
'updated_at' => $this->updated_at,
|
|
54
|
+
'created_at' => $this->created_at,
|
|
52
55
|
];
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -53,6 +53,7 @@ class ServiceRate extends Model
|
|
|
53
53
|
'company_uuid',
|
|
54
54
|
'service_area_uuid',
|
|
55
55
|
'zone_uuid',
|
|
56
|
+
'order_config_uuid',
|
|
56
57
|
'service_name',
|
|
57
58
|
'service_type',
|
|
58
59
|
'per_meter_flat_rate_fee',
|
|
@@ -124,6 +125,14 @@ class ServiceRate extends Model
|
|
|
124
125
|
return $this->hasMany(ServiceRateParcelFee::class);
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
/**
|
|
129
|
+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
130
|
+
*/
|
|
131
|
+
public function orderConfig()
|
|
132
|
+
{
|
|
133
|
+
return $this->belongsTo(OrderConfig::class)->withTrashed();
|
|
134
|
+
}
|
|
135
|
+
|
|
127
136
|
/**
|
|
128
137
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
129
138
|
*/
|
package/translations/en-us.yaml
CHANGED
|
@@ -139,6 +139,11 @@ fleet-ops:
|
|
|
139
139
|
code: Code
|
|
140
140
|
status: Status
|
|
141
141
|
details: Details
|
|
142
|
+
proof-of-delivery: Proof of Delivery
|
|
143
|
+
require-pod: Requires Proof of Delivery
|
|
144
|
+
require-pod-help-text: This activity requires a proof to be created before it can be applied to the order.
|
|
145
|
+
select-pod-method: Proof of Delivery Method
|
|
146
|
+
pod-method-placeholder: Select Proof of Delivery Method
|
|
142
147
|
completes-order: Completes Order
|
|
143
148
|
complete: Complete
|
|
144
149
|
key-help: "Enter a key used for programmatic decision-making. This key can represent one or several activities with the same identifier. Note: This key is not required to be unique."
|
|
@@ -1406,7 +1411,7 @@ fleet-ops:
|
|
|
1406
1411
|
service-name: Service Name
|
|
1407
1412
|
service-name-help-text: Display name for this service.
|
|
1408
1413
|
service-order-label: Service Order Type
|
|
1409
|
-
service-order-help-text:
|
|
1414
|
+
service-order-help-text: Select an order configuration to which this service rate will be exclusively applied. This ensures that only orders created with the selected order configuration can utilize this specific service rate.
|
|
1410
1415
|
service-order-placeholder: Select order type to restrict service to
|
|
1411
1416
|
base-fee-label: Base Fee
|
|
1412
1417
|
base-fee-help-text: Set a base fee which represents the minimum cost for this serice.
|