@fleetbase/fleetops-engine 0.6.32 → 0.6.33

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/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/fleetops-api",
3
- "version": "0.6.32",
3
+ "version": "0.6.33",
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.6.32",
3
+ "version": "0.6.33",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/fleetops",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/fleetops-engine",
3
- "version": "0.6.32",
3
+ "version": "0.6.33",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "fleet-ops"
@@ -0,0 +1,98 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Console\Commands;
4
+
5
+ use Fleetbase\FleetOps\Mail\CustomerCredentialsMail;
6
+ use Fleetbase\FleetOps\Models\Contact;
7
+ use Fleetbase\Models\Company;
8
+ use Fleetbase\Models\User;
9
+ use Illuminate\Console\Command;
10
+ use Illuminate\Support\Facades\Mail;
11
+
12
+ class TestEmail extends Command
13
+ {
14
+ /**
15
+ * The name and signature of the console command.
16
+ *
17
+ * @var string
18
+ */
19
+ protected $signature = 'fleetops:test-email {email} {--type=customer_credentials : The type of email to test}';
20
+
21
+ /**
22
+ * The console command description.
23
+ *
24
+ * @var string
25
+ */
26
+ protected $description = 'Test FleetOps email templates';
27
+
28
+ /**
29
+ * Execute the console command.
30
+ *
31
+ * @return int
32
+ */
33
+ public function handle()
34
+ {
35
+ $email = $this->argument('email');
36
+ $type = $this->option('type');
37
+
38
+ $this->info('Sending test email...');
39
+ $this->info("Type: {$type}");
40
+ $this->info("To: {$email}");
41
+
42
+ try {
43
+ switch ($type) {
44
+ case 'customer_credentials':
45
+ $this->sendCustomerCredentialsEmail($email);
46
+ break;
47
+
48
+ default:
49
+ $this->error("Unknown email type: {$type}");
50
+ return Command::FAILURE;
51
+ }
52
+
53
+ $this->info('✓ Test email sent successfully!');
54
+ return Command::SUCCESS;
55
+ } catch (\Exception $e) {
56
+ $this->error('Failed to send test email: ' . $e->getMessage());
57
+ return Command::FAILURE;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Send a test customer credentials email.
63
+ *
64
+ * @param string $email
65
+ * @return void
66
+ */
67
+ private function sendCustomerCredentialsEmail(string $email): void
68
+ {
69
+ // Create a mock user
70
+ $user = new User([
71
+ 'name' => 'Test Customer',
72
+ 'email' => $email,
73
+ ]);
74
+
75
+ // Create a mock company
76
+ $company = new Company([
77
+ 'name' => 'Test Company',
78
+ 'public_id' => 'test_company_123',
79
+ ]);
80
+
81
+ // Create a mock customer
82
+ $customer = new Contact([
83
+ 'name' => 'Test Customer',
84
+ 'email' => $email,
85
+ 'phone' => '+1234567890',
86
+ ]);
87
+
88
+ // Set relations
89
+ $customer->setRelation('company', $company);
90
+ $customer->setRelation('user', $user);
91
+
92
+ // Mock password
93
+ $plaintextPassword = 'TestPassword123!';
94
+
95
+ // Send the email
96
+ Mail::to($email)->send(new CustomerCredentialsMail($plaintextPassword, $customer));
97
+ }
98
+ }
@@ -62,6 +62,9 @@ class DriverController extends Controller
62
62
  // Apply user infos
63
63
  $userDetails = User::applyUserInfoFromRequest($request, $userDetails);
64
64
 
65
+ // Set company_uuid before creating user
66
+ $userDetails['company_uuid'] = $company->uuid;
67
+
65
68
  // create user account for driver
66
69
  $user = User::create($userDetails);
67
70
 
@@ -27,12 +27,10 @@ class VehicleController extends Controller
27
27
  {
28
28
  // get request input
29
29
  $input = $request->only(['status', 'make', 'model', 'year', 'trim', 'type', 'plate_number', 'vin', 'meta', 'online', 'location', 'altitude', 'heading', 'speed']);
30
+
30
31
  // make sure company is set
31
32
  $input['company_uuid'] = session('company');
32
33
 
33
- // create instance of vehicle model
34
- $vehicle = new Vehicle();
35
-
36
34
  // set default online
37
35
  if (!isset($input['online'])) {
38
36
  $input['online'] = 0;
@@ -51,11 +49,8 @@ class VehicleController extends Controller
51
49
  $input['location'] = Utils::getPointFromCoordinates($request->only(['latitude', 'longitude']));
52
50
  }
53
51
 
54
- // apply user input to vehicle
55
- $vehicle = $vehicle->fill($input);
56
-
57
- // save the vehicle
58
- $vehicle->save();
52
+ // create the vehicle (fires 'created' event for billing resource tracking)
53
+ $vehicle = Vehicle::create($input);
59
54
 
60
55
  // driver assignment
61
56
  if ($request->has('driver')) {
@@ -136,7 +136,37 @@ class DriverController extends FleetOpsController
136
136
 
137
137
  if ($input->has('user_uuid')) {
138
138
  $user = User::where('uuid', $input->get('user_uuid'))->first();
139
- if ($user && $input->has('photo_uuid')) {
139
+
140
+ // If user doesn't exist with provided UUID, create new user
141
+ if (!$user) {
142
+ $userInput = $input
143
+ ->only(['name', 'password', 'email', 'phone', 'status', 'avatar_uuid'])
144
+ ->filter()
145
+ ->toArray();
146
+
147
+ // handle `photo_uuid`
148
+ if (isset($input['photo_uuid']) && Str::isUuid($input['photo_uuid'])) {
149
+ $userInput['avatar_uuid'] = $input['photo_uuid'];
150
+ }
151
+
152
+ // Make sure password is set
153
+ if (empty($userInput['password'])) {
154
+ $userInput['password'] = Str::random(14);
155
+ }
156
+
157
+ // Set user company
158
+ $userInput['company_uuid'] = session('company', $company->uuid);
159
+
160
+ // Apply user infos
161
+ $userInput = User::applyUserInfoFromRequest($request, $userInput);
162
+
163
+ // Create user account
164
+ $user = User::create($userInput);
165
+
166
+ // Set the user type to driver
167
+ $user->setType('driver');
168
+ } elseif ($input->has('photo_uuid')) {
169
+ // Update existing user's avatar if photo provided
140
170
  $user->update(['avatar_uuid' => $input->get('photo_uuid')]);
141
171
  }
142
172
  } else {
@@ -29,14 +29,12 @@ use Fleetbase\FleetOps\Models\Waypoint;
29
29
  use Fleetbase\FleetOps\Support\Utils;
30
30
  use Fleetbase\Http\Requests\ExportRequest;
31
31
  use Fleetbase\Http\Requests\Internal\BulkActionRequest;
32
- use Fleetbase\Http\Requests\Internal\BulkDeleteRequest;
33
32
  use Fleetbase\Models\File;
34
33
  use Fleetbase\Models\Type;
35
34
  use Fleetbase\Support\TemplateString;
36
35
  use Illuminate\Database\Eloquent\ModelNotFoundException;
37
36
  use Illuminate\Database\QueryException;
38
37
  use Illuminate\Http\Request;
39
- use Illuminate\Support\Collection;
40
38
  use Illuminate\Support\Facades\Cache;
41
39
  use Illuminate\Support\Facades\DB;
42
40
  use Illuminate\Support\Facades\Validator;
@@ -330,36 +328,6 @@ class OrderController extends FleetOpsController
330
328
  );
331
329
  }
332
330
 
333
- /**
334
- * Updates a order to canceled and updates order activity.
335
- *
336
- * @return \Illuminate\Http\Response
337
- */
338
- public function bulkDelete(BulkDeleteRequest $request)
339
- {
340
- $ids = $request->input('ids', []);
341
-
342
- if (!$ids) {
343
- return response()->error('Nothing to delete.');
344
- }
345
-
346
- /** @var Order */
347
- $count = Order::whereIn('uuid', $ids)->count();
348
- $deleted = Order::whereIn('uuid', $ids)->delete();
349
-
350
- if (!$deleted) {
351
- return response()->error('Failed to bulk delete orders.');
352
- }
353
-
354
- return response()->json(
355
- [
356
- 'status' => 'OK',
357
- 'message' => 'Deleted ' . $count . ' orders',
358
- 'count' => $count,
359
- ]
360
- );
361
- }
362
-
363
331
  /**
364
332
  * Updates a order to canceled and updates order activity.
365
333
  *
@@ -10,7 +10,6 @@ use Fleetbase\FleetOps\Models\Place;
10
10
  use Fleetbase\FleetOps\Support\Geocoding;
11
11
  use Fleetbase\Http\Requests\ExportRequest;
12
12
  use Fleetbase\Http\Requests\ImportRequest;
13
- use Fleetbase\Http\Requests\Internal\BulkDeleteRequest;
14
13
  use Fleetbase\LaravelMysqlSpatial\Types\Point;
15
14
  use Illuminate\Http\Request;
16
15
  use Illuminate\Support\Str;
@@ -150,38 +149,6 @@ class PlaceController extends FleetOpsController
150
149
  return Excel::download(new PlaceExport($selections), $fileName);
151
150
  }
152
151
 
153
- /**
154
- * Bulk deletes resources.
155
- *
156
- * @return \Illuminate\Http\Response
157
- */
158
- public function bulkDelete(BulkDeleteRequest $request)
159
- {
160
- $ids = $request->input('ids', []);
161
-
162
- if (!$ids) {
163
- return response()->error('Nothing to delete.');
164
- }
165
-
166
- /**
167
- * @var \Fleetbase\Models\Place
168
- */
169
- $count = Place::whereIn('uuid', $ids)->applyDirectivesForPermissions('fleet-ops list place')->count();
170
- $deleted = Place::whereIn('uuid', $ids)->applyDirectivesForPermissions('fleet-ops list place')->delete();
171
-
172
- if (!$deleted) {
173
- return response()->error('Failed to bulk delete places.');
174
- }
175
-
176
- return response()->json(
177
- [
178
- 'status' => 'OK',
179
- 'message' => 'Deleted ' . $count . ' places',
180
- ],
181
- 200
182
- );
183
- }
184
-
185
152
  /**
186
153
  * Get all avatar options for an vehicle.
187
154
  *
@@ -9,7 +9,6 @@ use Fleetbase\FleetOps\Models\Driver;
9
9
  use Fleetbase\FleetOps\Models\Vendor;
10
10
  use Fleetbase\Http\Requests\ExportRequest;
11
11
  use Fleetbase\Http\Requests\ImportRequest;
12
- use Fleetbase\Http\Requests\Internal\BulkDeleteRequest;
13
12
  use Illuminate\Http\Request;
14
13
  use Illuminate\Support\Facades\DB;
15
14
  use Illuminate\Support\Str;
@@ -85,36 +84,6 @@ class VendorController extends FleetOpsController
85
84
  return Excel::download(new VendorExport($selections), $fileName);
86
85
  }
87
86
 
88
- /**
89
- * Bulk delete resources.
90
- *
91
- * @return \Illuminate\Http\Response
92
- */
93
- public function bulkDelete(BulkDeleteRequest $request)
94
- {
95
- $ids = $request->input('ids', []);
96
-
97
- if (!$ids) {
98
- return response()->error('Nothing to delete.');
99
- }
100
-
101
- /** @var \Fleetbase\Models\Vendor */
102
- $count = Vendor::whereIn('uuid', $ids)->count();
103
- $deleted = Vendor::whereIn('uuid', $ids)->delete();
104
-
105
- if (!$deleted) {
106
- return response()->error('Failed to bulk delete vendors.');
107
- }
108
-
109
- return response()->json(
110
- [
111
- 'status' => 'OK',
112
- 'message' => 'Deleted ' . $count . ' vendors',
113
- ],
114
- 200
115
- );
116
- }
117
-
118
87
  /**
119
88
  * Get all status options for an vehicle.
120
89
  *
@@ -36,8 +36,8 @@ class CreateServiceRateRequest extends FleetbaseRequest
36
36
  'base_fee' => ['numeric'],
37
37
  'per_meter_unit' => ['required_if:rate_calculation_method,per_meter', 'string', 'in:km,m'],
38
38
  'per_meter_flat_rate_fee' => ['required_if:rate_calculation_method,per_meter', 'numeric'],
39
- 'meter_fees' => [Rule::requiredIf(function ($input) {
40
- return in_array($input->rate_calculation_method, ['fixed_meter', 'fixed_rate']);
39
+ 'meter_fees' => [Rule::requiredIf(function () {
40
+ return in_array($this->input('rate_calculation_method'), ['fixed_meter', 'fixed_rate']);
41
41
  }), 'array'],
42
42
  'meter_fees.*.distance' => ['numeric'],
43
43
  'meter_fees.*.fee' => ['numeric'],
@@ -3,7 +3,9 @@
3
3
  namespace Fleetbase\FleetOps\Http\Requests\Internal;
4
4
 
5
5
  use Fleetbase\FleetOps\Http\Requests\CreateDriverRequest as CreateDriverApiRequest;
6
+ use Fleetbase\FleetOps\Rules\ResolvablePoint;
6
7
  use Fleetbase\Support\Auth;
8
+ use Illuminate\Validation\Rule;
7
9
 
8
10
  class CreateDriverRequest extends CreateDriverApiRequest
9
11
  {
@@ -25,13 +27,75 @@ class CreateDriverRequest extends CreateDriverApiRequest
25
27
  public function rules()
26
28
  {
27
29
  $isCreating = $this->isMethod('POST');
30
+ $isCreatingWithUser = $this->filled('driver.user_uuid');
31
+ $shouldValidateUserAttributes = $isCreating && !$isCreatingWithUser;
28
32
 
29
33
  return [
30
- 'password' => 'nullable|string',
31
- 'country' => 'nullable|size:2',
32
- 'city' => 'nullable|string',
33
- 'status' => 'nullable|string|in:active,inactive',
34
- 'job' => 'nullable|exists:orders,public_id',
34
+ // Required fields for driver creation
35
+ 'name' => [Rule::requiredIf($shouldValidateUserAttributes), 'nullable', 'string', 'max:255'],
36
+ 'email' => [
37
+ Rule::requiredIf($shouldValidateUserAttributes),
38
+ Rule::when($this->filled('email'), ['email']),
39
+ Rule::when($shouldValidateUserAttributes, [Rule::unique('users')->whereNull('deleted_at')])
40
+ ],
41
+ 'phone' => [
42
+ Rule::requiredIf($shouldValidateUserAttributes),
43
+ Rule::when($shouldValidateUserAttributes, [Rule::unique('users')->whereNull('deleted_at')])
44
+ ],
45
+
46
+ // Optional fields
47
+ 'password' => 'nullable|string|min:8',
48
+ 'drivers_license_number' => 'nullable|string|max:255',
49
+ 'internal_id' => 'nullable|string|max:255',
50
+ 'country' => 'nullable|string|size:2',
51
+ 'city' => 'nullable|string|max:255',
52
+ 'vehicle' => 'nullable|string|starts_with:vehicle_|exists:vehicles,public_id',
53
+ 'status' => 'nullable|string|in:active,inactive',
54
+ 'vendor' => 'nullable|exists:vendors,public_id',
55
+ 'job' => 'nullable|exists:orders,public_id',
56
+ 'location' => ['nullable', new ResolvablePoint()],
57
+ 'latitude' => ['nullable', 'required_with:longitude', 'numeric'],
58
+ 'longitude' => ['nullable', 'required_with:latitude', 'numeric'],
59
+
60
+ // Photo/avatar
61
+ 'photo_uuid' => 'nullable|exists:files,uuid',
62
+ 'avatar_uuid' => 'nullable|exists:files,uuid',
63
+ ];
64
+ }
65
+
66
+ /**
67
+ * Get custom attributes for validator errors.
68
+ *
69
+ * @return array
70
+ */
71
+ public function attributes()
72
+ {
73
+ return [
74
+ 'name' => 'driver name',
75
+ 'email' => 'email address',
76
+ 'phone' => 'phone number',
77
+ 'drivers_license_number' => 'driver\'s license number',
78
+ 'internal_id' => 'internal ID',
79
+ 'photo_uuid' => 'photo',
80
+ 'avatar_uuid' => 'avatar',
81
+ ];
82
+ }
83
+
84
+ /**
85
+ * Get custom messages for validator errors.
86
+ *
87
+ * @return array
88
+ */
89
+ public function messages()
90
+ {
91
+ return [
92
+ 'name.required' => 'Driver name is required.',
93
+ 'email.required' => 'Email address is required.',
94
+ 'email.email' => 'Please provide a valid email address.',
95
+ 'email.unique' => 'This email address is already registered.',
96
+ 'phone.required' => 'Phone number is required.',
97
+ 'phone.unique' => 'This phone number is already registered.',
98
+ 'password.min' => 'Password must be at least 8 characters.',
35
99
  ];
36
100
  }
37
101
  }
@@ -333,6 +333,28 @@ class Place extends Model
333
333
  {
334
334
  $instance = (new static())->fillWithGoogleAddress($address);
335
335
 
336
+ // Before saving or returning this instance check the database for a duplicate address
337
+ // it cannot have any owner, and must belong to this session
338
+ if ($companyUuid = session('company')) {
339
+ $duplicate = static::where([
340
+ 'company_uuid' => $companyUuid,
341
+ 'street1' => $instance->street1,
342
+ 'city' => $instance->city,
343
+ 'country' => $instance->country,
344
+ ])
345
+ ->when(
346
+ $instance->postal_code !== null,
347
+ fn ($q) => $q->where('postal_code', $instance->postal_code),
348
+ fn ($q) => $q->whereNull('postal_code')
349
+ )
350
+ ->whereNull('owner_uuid')
351
+ ->first();
352
+
353
+ if ($duplicate) {
354
+ return $duplicate;
355
+ }
356
+ }
357
+
336
358
  if ($saveInstance) {
337
359
  $instance->save();
338
360
  }
@@ -60,6 +60,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
60
60
  \Fleetbase\FleetOps\Console\Commands\PurgeUnpurchasedServiceQuotes::class,
61
61
  \Fleetbase\FleetOps\Console\Commands\SendDriverNotification::class,
62
62
  \Fleetbase\FleetOps\Console\Commands\ReplayVehicleLocations::class,
63
+ \Fleetbase\FleetOps\Console\Commands\TestEmail::class,
63
64
  ];
64
65
 
65
66
  /**