@fleetbase/storefront-engine 0.3.31 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/addon/components/customer-panel/orders.hbs +2 -2
  2. package/addon/components/customer-panel/orders.js +1 -1
  3. package/addon/components/modals/create-gateway.hbs +33 -12
  4. package/addon/components/modals/share-network.hbs +1 -1
  5. package/addon/components/network-category-picker.hbs +2 -1
  6. package/addon/components/network-category-picker.js +52 -22
  7. package/addon/components/order-panel.hbs +1 -1
  8. package/addon/components/widget/customers.hbs +5 -2
  9. package/addon/components/widget/customers.js +14 -6
  10. package/addon/components/widget/orders.hbs +30 -9
  11. package/addon/components/widget/orders.js +7 -1
  12. package/addon/components/widget/storefront-key-metrics.js +3 -3
  13. package/addon/components/widget/storefront-metrics.hbs +11 -1
  14. package/addon/components/widget/storefront-metrics.js +103 -1
  15. package/addon/controllers/customers/index.js +2 -2
  16. package/addon/controllers/networks/index/network/index.js +2 -0
  17. package/addon/controllers/networks/index/network/orders.js +1 -2
  18. package/addon/controllers/networks/index/network/stores.js +68 -64
  19. package/addon/controllers/networks/index.js +1 -2
  20. package/addon/controllers/products/index/category.js +1 -2
  21. package/addon/controllers/products/index/index.js +1 -2
  22. package/addon/controllers/settings/gateways.js +6 -1
  23. package/addon/controllers/settings/index.js +1 -0
  24. package/addon/controllers/settings/notifications.js +3 -5
  25. package/addon/models/network.js +1 -0
  26. package/addon/models/store.js +1 -0
  27. package/addon/routes/networks/index/network/index.js +5 -0
  28. package/addon/routes/networks/index/network/stores.js +6 -5
  29. package/addon/routes/settings/index.js +5 -0
  30. package/addon/services/order-actions.js +31 -0
  31. package/addon/styles/storefront-engine.css +29 -0
  32. package/addon/templates/networks/index/network/index.hbs +13 -0
  33. package/addon/templates/networks/index/network/stores.hbs +15 -1
  34. package/addon/templates/networks/index.hbs +1 -1
  35. package/addon/templates/settings/gateways.hbs +15 -4
  36. package/addon/templates/settings/index.hbs +13 -0
  37. package/addon/templates/settings/notifications.hbs +2 -2
  38. package/addon/utils/commerce-date-ranges.js +263 -0
  39. package/app/utils/commerce-date-ranges.js +1 -0
  40. package/composer.json +1 -1
  41. package/extension.json +1 -1
  42. package/package.json +2 -3
  43. package/server/migrations/2025_09_01_041353_add_default_order_config_column.php +110 -0
  44. package/server/src/Console/Commands/MigrateStripeSandboxCustomers.php +150 -0
  45. package/server/src/Expansions/OrderExpansion.php +43 -0
  46. package/server/src/Http/Controllers/NetworkController.php +20 -3
  47. package/server/src/Http/Controllers/OrderController.php +62 -9
  48. package/server/src/Http/Controllers/v1/CheckoutController.php +45 -48
  49. package/server/src/Http/Controllers/v1/ServiceQuoteController.php +19 -3
  50. package/server/src/Http/Middleware/SetStorefrontSession.php +8 -6
  51. package/server/src/Http/Resources/Customer.php +41 -17
  52. package/server/src/Http/Resources/Gateway.php +16 -11
  53. package/server/src/Http/Resources/Network.php +35 -34
  54. package/server/src/Http/Resources/NotificationChannel.php +17 -10
  55. package/server/src/Http/Resources/Store.php +36 -35
  56. package/server/src/Models/Customer.php +18 -2
  57. package/server/src/Models/FoodTruck.php +15 -0
  58. package/server/src/Models/Network.php +65 -2
  59. package/server/src/Models/Store.php +73 -10
  60. package/server/src/Notifications/StorefrontOrderAccepted.php +154 -0
  61. package/server/src/Observers/OrderObserver.php +6 -4
  62. package/server/src/Providers/StorefrontServiceProvider.php +1 -0
  63. package/server/src/Rules/IsValidLocation.php +12 -0
  64. package/server/src/Support/PushNotification.php +13 -4
  65. package/server/src/Support/Storefront.php +199 -37
  66. package/server/src/routes.php +2 -0
  67. package/translations/en-us.yaml +8 -0
@@ -20,41 +20,42 @@ class Store extends FleetbaseResource
20
20
  $currency = $this->currency ?? 'USD';
21
21
 
22
22
  return [
23
- 'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
24
- 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
25
- 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
26
- 'key' => $this->when(Http::isInternalRequest(), $this->key),
27
- 'company_uuid' => $this->when(Http::isInternalRequest(), $this->company_uuid),
28
- 'created_by_uuid' => $this->when(Http::isInternalRequest(), $this->created_by_uuid),
29
- 'logo_uuid' => $this->when(Http::isInternalRequest(), $this->logo_uuid),
30
- 'backdrop_uuid' => $this->when(Http::isInternalRequest(), $this->backdrop_uuid),
31
- 'name' => $this->name,
32
- 'description' => $this->description,
33
- 'translations' => $this->translations ?? [],
34
- 'website' => $this->website,
35
- 'facebook' => $this->facebook,
36
- 'instagram' => $this->instagram,
37
- 'twitter' => $this->twitter,
38
- 'email' => $this->email,
39
- 'phone' => $this->phone,
40
- 'tags' => $this->tags ?? [],
41
- 'currency' => $currency,
42
- 'country' => Utils::getCountryCodeByCurrency($currency, 'US'),
43
- 'options' => $this->formatOptions($this->options),
44
- 'logo_url' => $this->logo_url,
45
- 'backdrop_url' => $this->backdrop_url,
46
- 'rating' => $this->rating,
47
- 'online' => $this->online,
48
- 'alertable' => $this->alertable,
49
- 'is_network' => false,
50
- 'is_store' => true,
51
- 'category' => $this->when($request->filled('network') && ($request->has('with_category') || $request->inArray('with', 'category')), new Category($this->getNetworkCategoryUsingId($request->input('network')))),
52
- 'networks' => $this->when($request->boolean('with_networks') || $request->inArray('with', 'networks'), Network::collection($this->networks)),
53
- 'locations' => $this->when($request->boolean('with_locations') || $request->inArray('with', 'locations'), $this->locations->mapInto(StoreLocation::class)),
54
- 'media' => $this->when($request->boolean('with_media') || $request->inArray('with', 'media'), Media::collection($this->media)),
55
- 'slug' => $this->slug,
56
- 'created_at' => $this->created_at,
57
- 'updated_at' => $this->updated_at,
23
+ 'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
24
+ 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
25
+ 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
26
+ 'key' => $this->when(Http::isInternalRequest(), $this->key),
27
+ 'company_uuid' => $this->when(Http::isInternalRequest(), $this->company_uuid),
28
+ 'created_by_uuid' => $this->when(Http::isInternalRequest(), $this->created_by_uuid),
29
+ 'logo_uuid' => $this->when(Http::isInternalRequest(), $this->logo_uuid),
30
+ 'backdrop_uuid' => $this->when(Http::isInternalRequest(), $this->backdrop_uuid),
31
+ 'order_config_uuid' => $this->when(Http::isInternalRequest(), $this->order_config_uuid),
32
+ 'name' => $this->name,
33
+ 'description' => $this->description,
34
+ 'translations' => $this->translations ?? [],
35
+ 'website' => $this->website,
36
+ 'facebook' => $this->facebook,
37
+ 'instagram' => $this->instagram,
38
+ 'twitter' => $this->twitter,
39
+ 'email' => $this->email,
40
+ 'phone' => $this->phone,
41
+ 'tags' => $this->tags ?? [],
42
+ 'currency' => $currency,
43
+ 'country' => Utils::getCountryCodeByCurrency($currency, 'US'),
44
+ 'options' => $this->formatOptions($this->options),
45
+ 'logo_url' => $this->logo_url,
46
+ 'backdrop_url' => $this->backdrop_url,
47
+ 'rating' => $this->rating,
48
+ 'online' => $this->online,
49
+ 'alertable' => $this->alertable,
50
+ 'is_network' => false,
51
+ 'is_store' => true,
52
+ 'category' => $this->when($request->filled('network') && ($request->has('with_category') || $request->inArray('with', 'category')), new Category($this->getNetworkCategoryUsingId($request->input('network')))),
53
+ 'networks' => $this->when($request->boolean('with_networks') || $request->inArray('with', 'networks'), Network::collection($this->networks)),
54
+ 'locations' => $this->when($request->boolean('with_locations') || $request->inArray('with', 'locations'), $this->locations->mapInto(StoreLocation::class)),
55
+ 'media' => $this->when($request->boolean('with_media') || $request->inArray('with', 'media'), Media::collection($this->media)),
56
+ 'slug' => $this->slug,
57
+ 'created_at' => $this->created_at,
58
+ 'updated_at' => $this->updated_at,
58
59
  ];
59
60
  }
60
61
 
@@ -3,6 +3,7 @@
3
3
  namespace Fleetbase\Storefront\Models;
4
4
 
5
5
  use Fleetbase\FleetOps\Models\Contact;
6
+ use Fleetbase\FleetOps\Models\Order;
6
7
  use Illuminate\Support\Str;
7
8
 
8
9
  class Customer extends Contact
@@ -12,6 +13,22 @@ class Customer extends Contact
12
13
  */
13
14
  protected string $payloadKey = 'customer';
14
15
 
16
+ /**
17
+ * Boot the model.
18
+ */
19
+ protected static function boot()
20
+ {
21
+ parent::boot();
22
+
23
+ static::creating(function ($model) {
24
+ $model->type = 'customer';
25
+ });
26
+
27
+ static::addGlobalScope('type', function ($builder) {
28
+ $builder->where('type', 'customer');
29
+ });
30
+ }
31
+
15
32
  /**
16
33
  * @return \Illuminate\Database\Eloquent\Relations\HasMany
17
34
  */
@@ -61,10 +78,9 @@ class Customer extends Contact
61
78
  */
62
79
  public function countStorefrontOrdersFrom($id)
63
80
  {
64
- return \Fleetbase\FleetOps\Models\Order::where(
81
+ return Order::where(
65
82
  [
66
83
  'customer_uuid' => $this->uuid,
67
- 'type' => 'storefront',
68
84
  'meta->storefront_id' => $id,
69
85
  ]
70
86
  )->count();
@@ -5,6 +5,7 @@ namespace Fleetbase\Storefront\Models;
5
5
  use Fleetbase\FleetOps\Models\ServiceArea;
6
6
  use Fleetbase\FleetOps\Models\Vehicle;
7
7
  use Fleetbase\FleetOps\Models\Zone;
8
+ use Fleetbase\LaravelMysqlSpatial\Types\Point;
8
9
  use Fleetbase\Storefront\Http\Resources\FoodTruck as FoodTruckResource;
9
10
  use Fleetbase\Traits\HasApiModelBehavior;
10
11
  use Fleetbase\Traits\HasPublicid;
@@ -61,6 +62,13 @@ class FoodTruck extends StorefrontModel
61
62
  'status',
62
63
  ];
63
64
 
65
+ /**
66
+ * Dynamic attributes that are appended to object.
67
+ *
68
+ * @var array
69
+ */
70
+ protected $appends = ['location'];
71
+
64
72
  /**
65
73
  * Get the store that owns this food truck.
66
74
  */
@@ -179,4 +187,11 @@ class FoodTruck extends StorefrontModel
179
187
 
180
188
  return $this;
181
189
  }
190
+
191
+ public function getLocationAttribute(): ?Point
192
+ {
193
+ $this->loadMissing('vehicle');
194
+
195
+ return $this->vehicle->location;
196
+ }
182
197
  }
@@ -3,12 +3,14 @@
3
3
  namespace Fleetbase\Storefront\Models;
4
4
 
5
5
  use Fleetbase\Casts\Json;
6
+ use Fleetbase\FleetOps\Models\OrderConfig;
6
7
  use Fleetbase\FleetOps\Support\Utils;
7
8
  use Fleetbase\Models\Category;
8
9
  use Fleetbase\Models\Company;
9
10
  use Fleetbase\Models\File;
10
11
  use Fleetbase\Models\Invite;
11
12
  use Fleetbase\Models\User;
13
+ use Fleetbase\Storefront\Support\Storefront;
12
14
  use Fleetbase\Traits\HasApiModelBehavior;
13
15
  use Fleetbase\Traits\HasOptionsAttributes;
14
16
  use Fleetbase\Traits\HasPublicid;
@@ -53,7 +55,7 @@ class Network extends StorefrontModel
53
55
  *
54
56
  * @var array
55
57
  */
56
- protected $fillable = ['created_by_uuid', 'company_uuid', 'logo_uuid', 'backdrop_uuid', 'key', 'online', 'name', 'description', 'website', 'facebook', 'instagram', 'twitter', 'email', 'phone', 'translations', 'tags', 'currency', 'timezone', 'pod_method', 'options', 'alertable', 'slug'];
58
+ protected $fillable = ['created_by_uuid', 'company_uuid', 'logo_uuid', 'backdrop_uuid', 'order_config_uuid', 'key', 'online', 'name', 'description', 'website', 'facebook', 'instagram', 'twitter', 'email', 'phone', 'translations', 'tags', 'currency', 'timezone', 'pod_method', 'options', 'alertable', 'slug'];
57
59
 
58
60
  /**
59
61
  * The attributes that should be cast to native types.
@@ -133,6 +135,14 @@ class Network extends StorefrontModel
133
135
  return $this->setConnection(config('fleetbase.connection.db'))->belongsTo(File::class);
134
136
  }
135
137
 
138
+ /**
139
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
140
+ */
141
+ public function orderConfig()
142
+ {
143
+ return $this->setConnection(config('fleetbase.connection.db'))->belongsTo(OrderConfig::class);
144
+ }
145
+
136
146
  /**
137
147
  * @return \Illuminate\Database\Eloquent\Relations\HasMany
138
148
  */
@@ -156,7 +166,8 @@ class Network extends StorefrontModel
156
166
  {
157
167
  return $this->belongsToMany(Store::class, 'network_stores', 'network_uuid', 'store_uuid')
158
168
  ->using(NetworkStore::class)
159
- ->withPivot('category_uuid');
169
+ ->withPivot(['category_uuid', 'deleted_at'])
170
+ ->wherePivotNull('deleted_at');
160
171
  }
161
172
 
162
173
  /**
@@ -286,4 +297,56 @@ class Network extends StorefrontModel
286
297
 
287
298
  return $this->createCategory($name, $description, $meta, $translations, $parent, $icon, $iconColor);
288
299
  }
300
+
301
+ /**
302
+ * Get the effective OrderConfig for this model.
303
+ *
304
+ * Loads the related `orderConfig` if missing and returns it.
305
+ * If no specific config is set, it falls back to the global default
306
+ * and memoizes that default into the relation (no DB write; just the relation cache)
307
+ * to avoid repeat lookups in the same request lifecycle.
308
+ *
309
+ * @throws \RuntimeException if no default OrderConfig is available (should not happen in a healthy install)
310
+ */
311
+ public function getOrderConfig(): OrderConfig
312
+ {
313
+ // Ensure relation is loaded once
314
+ $this->loadMissing('orderConfig');
315
+
316
+ if ($this->orderConfig instanceof OrderConfig) {
317
+ return $this->orderConfig;
318
+ }
319
+
320
+ // Fallback to default (must exist in your environment)
321
+ $default = Storefront::getDefaultOrderConfig();
322
+
323
+ if (!$default instanceof OrderConfig) {
324
+ // Keep your hard return type contract honest
325
+ throw new \RuntimeException('No default OrderConfig is configured.');
326
+ }
327
+
328
+ // Memoize the fallback in the in-memory relation to avoid repeated lookups
329
+ $this->setRelation('orderConfig', $default);
330
+
331
+ return $default;
332
+ }
333
+
334
+ /**
335
+ * Get the UUID of the effective OrderConfig for this model.
336
+ *
337
+ * Tries to use the local FK if present to avoid loading the relation;
338
+ * otherwise, resolves via getOrderConfig() (which memoizes).
339
+ *
340
+ * @return string non-empty UUID of the effective order config
341
+ */
342
+ public function getOrderConfigId(): string
343
+ {
344
+ // Fast path: if the FK column is set, use it directly
345
+ if (!empty($this->order_config_uuid)) {
346
+ return (string) $this->order_config_uuid;
347
+ }
348
+
349
+ // Otherwise rely on the resolved config (relation or default)
350
+ return (string) $this->getOrderConfig()->uuid;
351
+ }
289
352
  }
@@ -3,11 +3,13 @@
3
3
  namespace Fleetbase\Storefront\Models;
4
4
 
5
5
  use Fleetbase\Casts\Json;
6
+ use Fleetbase\FleetOps\Models\OrderConfig;
6
7
  use Fleetbase\FleetOps\Models\Place;
7
8
  use Fleetbase\Models\Category;
8
9
  use Fleetbase\Models\Company;
9
10
  use Fleetbase\Models\File;
10
11
  use Fleetbase\Models\User;
12
+ use Fleetbase\Storefront\Support\Storefront;
11
13
  use Fleetbase\Support\Utils as FleetbaseUtils;
12
14
  use Fleetbase\Traits\HasApiModelBehavior;
13
15
  use Fleetbase\Traits\HasMetaAttributes;
@@ -56,7 +58,7 @@ class Store extends StorefrontModel
56
58
  *
57
59
  * @var array
58
60
  */
59
- protected $fillable = ['created_by_uuid', 'company_uuid', 'logo_uuid', 'backdrop_uuid', 'key', 'online', 'name', 'description', 'translations', 'website', 'facebook', 'instagram', 'twitter', 'email', 'phone', 'tags', 'currency', 'meta', 'timezone', 'pod_method', 'options', 'alertable', 'slug'];
61
+ protected $fillable = ['created_by_uuid', 'company_uuid', 'logo_uuid', 'backdrop_uuid', 'order_config_uuid', 'key', 'online', 'name', 'description', 'translations', 'website', 'facebook', 'instagram', 'twitter', 'email', 'phone', 'tags', 'currency', 'meta', 'timezone', 'pod_method', 'options', 'alertable', 'slug'];
60
62
 
61
63
  /**
62
64
  * The attributes that should be cast to native types.
@@ -137,6 +139,22 @@ class Store extends StorefrontModel
137
139
  return $this->setConnection(config('fleetbase.connection.db'))->belongsTo(File::class);
138
140
  }
139
141
 
142
+ /**
143
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
144
+ */
145
+ public function backdrop()
146
+ {
147
+ return $this->setConnection(config('fleetbase.connection.db'))->belongsTo(File::class);
148
+ }
149
+
150
+ /**
151
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
152
+ */
153
+ public function orderConfig()
154
+ {
155
+ return $this->setConnection(config('fleetbase.connection.db'))->belongsTo(OrderConfig::class);
156
+ }
157
+
140
158
  /**
141
159
  * @return \Illuminate\Database\Eloquent\Relations\HasMany
142
160
  */
@@ -167,14 +185,6 @@ class Store extends StorefrontModel
167
185
  )->where('type', 'storefront_store_media');
168
186
  }
169
187
 
170
- /**
171
- * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
172
- */
173
- public function backdrop()
174
- {
175
- return $this->setConnection(config('fleetbase.connection.db'))->belongsTo(File::class);
176
- }
177
-
178
188
  /**
179
189
  * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
180
190
  */
@@ -278,7 +288,8 @@ class Store extends StorefrontModel
278
288
  {
279
289
  return $this->belongsToMany(Network::class, 'network_stores', 'store_uuid', 'network_uuid')
280
290
  ->using(NetworkStore::class)
281
- ->withPivot('category_uuid');
291
+ ->withPivot(['category_uuid', 'deleted_at'])
292
+ ->wherePivotNull('deleted_at');
282
293
  }
283
294
 
284
295
  /**
@@ -459,4 +470,56 @@ class Store extends StorefrontModel
459
470
 
460
471
  return null;
461
472
  }
473
+
474
+ /**
475
+ * Get the effective OrderConfig for this model.
476
+ *
477
+ * Loads the related `orderConfig` if missing and returns it.
478
+ * If no specific config is set, it falls back to the global default
479
+ * and memoizes that default into the relation (no DB write; just the relation cache)
480
+ * to avoid repeat lookups in the same request lifecycle.
481
+ *
482
+ * @throws \RuntimeException if no default OrderConfig is available (should not happen in a healthy install)
483
+ */
484
+ public function getOrderConfig(): OrderConfig
485
+ {
486
+ // Ensure relation is loaded once
487
+ $this->loadMissing('orderConfig');
488
+
489
+ if ($this->orderConfig instanceof OrderConfig) {
490
+ return $this->orderConfig;
491
+ }
492
+
493
+ // Fallback to default (must exist in your environment)
494
+ $default = Storefront::getDefaultOrderConfig();
495
+
496
+ if (!$default instanceof OrderConfig) {
497
+ // Keep your hard return type contract honest
498
+ throw new \RuntimeException('No default OrderConfig is configured.');
499
+ }
500
+
501
+ // Memoize the fallback in the in-memory relation to avoid repeated lookups
502
+ $this->setRelation('orderConfig', $default);
503
+
504
+ return $default;
505
+ }
506
+
507
+ /**
508
+ * Get the UUID of the effective OrderConfig for this model.
509
+ *
510
+ * Tries to use the local FK if present to avoid loading the relation;
511
+ * otherwise, resolves via getOrderConfig() (which memoizes).
512
+ *
513
+ * @return string non-empty UUID of the effective order config
514
+ */
515
+ public function getOrderConfigId(): string
516
+ {
517
+ // Fast path: if the FK column is set, use it directly
518
+ if (!empty($this->order_config_uuid)) {
519
+ return (string) $this->order_config_uuid;
520
+ }
521
+
522
+ // Otherwise rely on the resolved config (relation or default)
523
+ return (string) $this->getOrderConfig()->uuid;
524
+ }
462
525
  }
@@ -0,0 +1,154 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Notifications;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+ use Fleetbase\Storefront\Models\Network;
7
+ use Fleetbase\Storefront\Models\Store;
8
+ use Fleetbase\Storefront\Support\PushNotification;
9
+ use Fleetbase\Storefront\Support\Storefront;
10
+ use Illuminate\Bus\Queueable;
11
+ use Illuminate\Notifications\Messages\MailMessage;
12
+ use Illuminate\Notifications\Notification;
13
+ use NotificationChannels\Apn\ApnChannel;
14
+ use NotificationChannels\Fcm\FcmChannel;
15
+
16
+ class StorefrontOrderAccepted extends Notification
17
+ {
18
+ use Queueable;
19
+
20
+ /**
21
+ * The order instance this notification is for.
22
+ */
23
+ public Order $order;
24
+
25
+ /**
26
+ * The order instance this notification is for.
27
+ */
28
+ public Store|Network $storefront;
29
+
30
+ /**
31
+ * The time the notification was sent.
32
+ */
33
+ public string $sentAt;
34
+
35
+ /**
36
+ * The ID of the notification.
37
+ */
38
+ public string $notificationId;
39
+
40
+ /**
41
+ * The notification subject.
42
+ */
43
+ public string $subject;
44
+
45
+ /**
46
+ * The notification body.
47
+ */
48
+ public string $body;
49
+
50
+ /**
51
+ * The notification order status.
52
+ */
53
+ public string $status;
54
+
55
+ /**
56
+ * Create a new notification instance.
57
+ *
58
+ * @return void
59
+ */
60
+ public function __construct(Order $order)
61
+ {
62
+ $this->order = $order;
63
+ $this->storefront = Storefront::findAbout($order->getMeta('storefront_id'));
64
+ $this->sentAt = now()->toDateTimeString();
65
+ $this->notificationId = uniqid('notification_');
66
+
67
+ $this->subject = 'Your order from ' . $this->storefront->name . ' has been accepted.';
68
+ $this->body = 'Your order was accepted.';
69
+ $this->status = 'order_accepted';
70
+ }
71
+
72
+ /**
73
+ * Get the notification's delivery channels.
74
+ *
75
+ * @return array
76
+ */
77
+ public function via($notifiable)
78
+ {
79
+ return ['mail', 'database', FcmChannel::class, ApnChannel::class];
80
+ }
81
+
82
+ /**
83
+ * Get the mail representation of the notification.
84
+ *
85
+ * @return MailMessage
86
+ */
87
+ public function toMail($notifiable)
88
+ {
89
+ $message = (new MailMessage())
90
+ ->subject($this->subject)
91
+ ->line($this->body);
92
+
93
+ // $message->action('View Details', Utils::consoleUrl('', ['shift' => 'fleet-ops/orders/view/' . $this->order->public_id]));
94
+
95
+ return $message;
96
+ }
97
+
98
+ /**
99
+ * Get the firebase cloud message representation of the notification.
100
+ *
101
+ * @return \NotificationChannels\Fcm\FcmMessage
102
+ */
103
+ public function toFcm($notifiable)
104
+ {
105
+ return PushNotification::createFcmMessage(
106
+ $this->order,
107
+ $this->subject,
108
+ $this->body,
109
+ $this->status,
110
+ $notifiable
111
+ );
112
+ }
113
+
114
+ /**
115
+ * Get the apns message representation of the notification.
116
+ *
117
+ * @return \NotificationChannels\Apn\ApnMessage
118
+ */
119
+ public function toApn($notifiable)
120
+ {
121
+ return PushNotification::createApnMessage(
122
+ $this->order,
123
+ $this->subject,
124
+ $this->body,
125
+ $this->status,
126
+ $notifiable
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Get the array representation of the notification.
132
+ */
133
+ public function toArray($notifiable): array
134
+ {
135
+ $this->order->loadMissing(['customer', 'company']);
136
+ $customer = $this->order->customer;
137
+ $company = $this->order->company;
138
+
139
+ return [
140
+ 'notifiable' => $notifiable->public_id,
141
+ 'notification_id' => $this->notificationId,
142
+ 'sent_at' => $this->sentAt,
143
+ 'subject' => $this->subject,
144
+ 'message' => $this->status,
145
+ 'storefront' => $this->storefront->name,
146
+ 'storefront_id' => $this->storefront->public_id,
147
+ 'id' => $customer->public_id,
148
+ 'email' => $customer->email,
149
+ 'phone' => $customer->phone,
150
+ 'companyId' => $company->public_id,
151
+ 'company' => $company->name,
152
+ ];
153
+ }
154
+ }
@@ -14,10 +14,12 @@ class OrderObserver
14
14
  */
15
15
  public function creating(Order $order)
16
16
  {
17
- // Set the storefront order config
18
- $orderConfig = Storefront::getDefaultOrderConfig();
19
- if ($orderConfig) {
20
- $order->order_config_uuid = $orderConfig->uuid;
17
+ // Set the storefront order config if none is set already
18
+ if (!$order->order_config_uuid) {
19
+ $orderConfig = Storefront::getDefaultOrderConfig();
20
+ if ($orderConfig) {
21
+ $order->order_config_uuid = $orderConfig->uuid;
22
+ }
21
23
  }
22
24
  }
23
25
  }
@@ -58,6 +58,7 @@ class StorefrontServiceProvider extends CoreServiceProvider
58
58
  \Fleetbase\Storefront\Console\Commands\NotifyStorefrontOrderNearby::class,
59
59
  \Fleetbase\Storefront\Console\Commands\SendOrderNotification::class,
60
60
  \Fleetbase\Storefront\Console\Commands\PurgeExpiredCarts::class,
61
+ \Fleetbase\Storefront\Console\Commands\MigrateStripeSandboxCustomers::class,
61
62
  ];
62
63
 
63
64
  /**
@@ -3,7 +3,9 @@
3
3
  namespace Fleetbase\Storefront\Rules;
4
4
 
5
5
  use Fleetbase\FleetOps\Models\Place;
6
+ use Fleetbase\FleetOps\Models\Vehicle;
6
7
  use Fleetbase\FleetOps\Support\Utils;
8
+ use Fleetbase\Storefront\Models\FoodTruck;
7
9
  use Fleetbase\Storefront\Models\StoreLocation;
8
10
  use Illuminate\Contracts\Validation\Rule;
9
11
  use Illuminate\Support\Str;
@@ -29,6 +31,16 @@ class IsValidLocation implements Rule
29
31
  return StoreLocation::where('public_id', $value)->exists();
30
32
  }
31
33
 
34
+ // Validate Vehicle id
35
+ if (is_string($value) && Str::startsWith($value, 'vehicle_')) {
36
+ return Vehicle::where('public_id', $value)->exists();
37
+ }
38
+
39
+ // Validate FoodTruck id
40
+ if (is_string($value) && Str::startsWith($value, 'food_truck_')) {
41
+ return FoodTruck::where('public_id', $value)->exists();
42
+ }
43
+
32
44
  // Validate object with coordinates
33
45
  if (isset($value->coordinates)) {
34
46
  return Utils::isCoordinates($value->coordinates);
@@ -17,10 +17,13 @@ use Pushok\Client as PushOkClient;
17
17
 
18
18
  class PushNotification
19
19
  {
20
- public static function createApnMessage(Order $order, string $title, string $body, string $status, $notifiable = null): ApnMessage
20
+ public static function createApnMessage(Order $order, string $title, string $body, string $status, $notifiable = null): ?ApnMessage
21
21
  {
22
22
  $storefront = static::getStorefrontFromOrder($order);
23
23
  $client = static::getApnClient($storefront, $order);
24
+ if (!$client) {
25
+ return null;
26
+ }
24
27
 
25
28
  return ApnMessage::create()
26
29
  ->badge(1)
@@ -33,10 +36,13 @@ class PushNotification
33
36
  ->via($client);
34
37
  }
35
38
 
36
- public static function createFcmMessage(Order $order, string $title, string $body, string $status, $notifiable = null): FcmMessage
39
+ public static function createFcmMessage(Order $order, string $title, string $body, string $status, $notifiable = null): ?FcmMessage
37
40
  {
38
41
  $storefront = static::getStorefrontFromOrder($order);
39
42
  $notificationChannel = static::getNotificationChannel('apn', $storefront, $order);
43
+ if (!$notificationChannel) {
44
+ return null;
45
+ }
40
46
 
41
47
  // Configure FCM
42
48
  static::configureFcm($notificationChannel);
@@ -97,7 +103,7 @@ class PushNotification
97
103
  return $firebaseConfig;
98
104
  }
99
105
 
100
- public static function getNotificationChannel(string $scheme, Network|Store $storefront, ?Order $order = null): NotificationChannel
106
+ public static function getNotificationChannel(string $scheme, Network|Store $storefront, ?Order $order = null): ?NotificationChannel
101
107
  {
102
108
  if ($order && $order->hasMeta('storefront_notification_channel')) {
103
109
  return NotificationChannel::where([
@@ -113,9 +119,12 @@ class PushNotification
113
119
  ])->first();
114
120
  }
115
121
 
116
- public static function getApnClient(Network|Store $storefront, ?Order $order = null): PushOkClient
122
+ public static function getApnClient(Network|Store $storefront, ?Order $order = null): ?PushOkClient
117
123
  {
118
124
  $notificationChannel = static::getNotificationChannel('apn', $storefront, $order);
125
+ if (!$notificationChannel) {
126
+ return null;
127
+ }
119
128
  $config = (array) $notificationChannel->config;
120
129
 
121
130
  $isProductionEnv = Utils::castBoolean(data_get($config, 'production', app()->isProduction()));