@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.
@@ -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
- const { latitude, longitude } = await this.location.getUserLocation();
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
- // trigger that initial coordinates is set to livemap component
396
- this.universe.trigger('fleet-ops.live-map.has_coordinates', { latitude: this.latitude, longitude: this.longitude });
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
- * Different service types available, based on order type.
51
+ * Available order configs.
52
52
  *
53
53
  * @var {Array}
54
54
  */
55
- @tracked serviceTypes = [];
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
- const serviceTypes = await this.currentUser.getInstalledOrderConfigs();
37
- const serviceAreas = await this.store.findAll('service-area');
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
- const serviceTypes = await this.currentUser.getInstalledOrderConfigs();
10
- const serviceAreas = await this.store.findAll('service-area');
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
- * Uses cached data, navigator geolocation, or WHOIS data as fallbacks.
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
- return this.fetch.cachedGet('fleet-ops/live/coordinates', {}, { expirationInterval: 1, expirationIntervalUnit: 'hour' }).then((coordinates) => {
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().then((navigatorCoordinates) => {
89
- this.updateLocation(navigatorCoordinates);
90
- return navigatorCoordinates;
91
- });
98
+ return await this.getUserLocationFromNavigator();
92
99
  }
93
100
 
94
- if (isArray(coordinates)) {
95
- const validCoordinates = coordinates.filter((point) => point.coordinates[0] !== 0);
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] = getWithDefault(validCoordinates, '0.coordinates', [0, 0]);
98
- const userCoordinates = {
99
- latitude,
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
- * @returns {Promise<Object>} A promise that resolves to geolocation coordinates.
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
- return new Promise((resolve) => {
118
- // eslint-disable-next-line no-undef
119
- if (window.navigator && window.navigator.geolocation) {
120
- // eslint-disable-next-line no-undef
121
- return navigator.geolocation.getCurrentPosition(
122
- ({ coords }) => {
123
- this.updateLocation(coords);
124
- return resolve(coords);
125
- },
126
- () => {
127
- // If failed use user whois
128
- return resolve(this.getUserLocationFromWhois());
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
- // default to whois lookup coordinates
134
- return resolve(this.getUserLocationFromWhois());
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 the user's account.
140
- * @returns {Object} An object containing latitude and longitude from WHOIS data.
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 and triggers an event.
155
- * @param {Object} coordinates - An object containing the latitude and longitude.
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
- <Select class="w-full" @options={{this.serviceTypes}} @value={{this.serviceRate.service_type}} @optionLabel="name" @optionValue="key" @onSelect={{fn (mut this.serviceRate.service_type)}} @placeholder={{t "fleet-ops.operations.service-rates.index.new.service-order-placeholder"}} />
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/fleetops-api",
3
- "version": "0.4.18",
3
+ "version": "0.4.19",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Fleet-Ops",
3
- "version": "0.4.18",
3
+ "version": "0.4.19",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/fleetops",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/fleetops-engine",
3
- "version": "0.4.18",
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.13",
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
- * Fleetbase Core API Configuration
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
- 'connection' => [
20
- 'db' => env('DB_CONNECTION', 'mysql')
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
  ];
@@ -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\Utils;
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
- if (Utils::classExists('\\Fleetbase\\FleetOps\\Seeders\\OrderConfigSeeder')) {
39
- $this->info('Starting the database seeding...');
40
-
41
- Artisan::call('db:seed', [
42
- '--class' => '\\Fleetbase\\FleetOps\\Seeders\\OrderConfigSeeder',
43
- ]);
44
-
45
- $this->info('Database seeding completed.');
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('uuid', $service)->where(function ($q) use ($currency) {
80
- if ($currency) {
81
- $q->where(DB::raw('lower(currency)'), strtolower($currency));
82
- }
83
- })->first();
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' => 'in:true,false,1,0',
29
- 'dispatch' => ['nullable', 'boolean'],
30
- 'adhoc_distance' => 'numeric',
31
- 'pod_required' => 'in:true,false,1,0',
32
- 'pod_method' => 'in:' . config('api.pod_methods'),
33
- 'scheduled_at' => ['nullable', 'date'],
34
- 'driver' => 'nullable|exists:drivers,public_id',
35
- 'service_quote' => 'nullable|exists:service_quotes,public_id',
36
- 'purchase_rate' => 'nullable|exists:purchase_rates,public_id',
37
- 'facilitator' => ['nullable', new ExistsInAny(['vendors', 'contacts', 'integrated_vendors'], ['public_id', 'provider'])],
38
- 'customer' => ['nullable', new ExistsInAny(['vendors', 'contacts'], 'public_id')],
39
- 'status' => 'string',
40
- 'type' => 'string',
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
- // 'adhoc' => 'in:true,false,1,0',
29
+ 'order_config_uuid' => ['required'],
30
+ 'adhoc' => ['nullable', 'boolean'],
30
31
  'dispatch' => ['nullable', 'boolean'],
31
- // 'adhoc_distance' => 'numeric',
32
- 'pod_required' => 'in:true,false,1,0',
33
- 'pod_method' => 'in:' . config('api.pod_methods'),
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|exists:drivers,uuid',
36
- 'service_quote' => 'nullable|exists:service_quotes,uuid',
37
- 'purchase_rate' => 'nullable|exists:purchase_rates,uuid',
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|string',
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' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
22
- 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
23
- 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
24
- 'service_area' => $this->whenLoaded('serviceArea', new ServiceArea($this->serviceArea)),
25
- 'service_area_name' => $this->when(Http::isInternalRequest(), data_get($this, 'serviceArea.name')),
26
- 'zone' => $this->whenLoaded('zone', new Zone($this->zone)),
27
- 'zone_name' => $this->when(Http::isInternalRequest(), data_get($this, 'zone.name')),
28
- 'service_name' => $this->service_name,
29
- 'service_type' => $this->service_type,
30
- 'base_fee' => $this->base_fee,
31
- 'rate_calculation_method' => $this->rate_calculation_method,
32
- 'per_meter_flat_rate_fee' => $this->per_meter_flat_rate_fee,
33
- 'per_meter_unit' => $this->per_meter_unit,
34
- 'meter_fees' => ServiceRateFee::collection($this->rateFees ?? []),
35
- 'parcel_fees' => ServiceRateParcelFee::collection($this->rateFees ?? []),
36
- 'algorithm' => $this->algorithm,
37
- 'has_cod_fee' => Utils::castBoolean($this->has_cod_fee),
38
- 'cod_calculation_method' => $this->cod_calculation_method,
39
- 'cod_flat_fee' => $this->cod_flat_fee,
40
- 'cod_percent' => $this->cod_percent,
41
- 'has_peak_hours_fee' => Utils::castBoolean($this->has_peak_hours_fee),
42
- 'peak_hours_calculation_method' => $this->peak_hours_calculation_method,
43
- 'peak_hours_flat_fee' => $this->peak_hours_flat_fee,
44
- 'peak_hours_percent' => $this->peak_hours_percent,
45
- 'peak_hours_start' => $this->peak_hours_start,
46
- 'peak_hours_end' => $this->peak_hours_end,
47
- 'currency' => $this->currency,
48
- 'duration_terms' => $this->duration_terms,
49
- 'estimated_days' => $this->estimated_days,
50
- 'updated_at' => $this->updated_at,
51
- 'created_at' => $this->created_at,
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
  */
@@ -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: Restrict this service rate to an explicit `order type`.
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.