@fleetbase/storefront-engine 0.1.6 → 0.1.8

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 (333) hide show
  1. package/.php-cs-fixer.php +29 -0
  2. package/LICENSE.md +16 -4
  3. package/README.md +106 -13
  4. package/addon/components/modals/add-store-to-category.hbs +5 -0
  5. package/addon/components/modals/create-network-category.hbs +28 -28
  6. package/addon/components/modals/share-network.hbs +3 -3
  7. package/addon/components/modals/store-details.hbs +22 -0
  8. package/addon/components/modals/store-form.hbs +22 -0
  9. package/addon/components/modals/store-form.js +21 -0
  10. package/addon/components/network-category-picker.hbs +40 -0
  11. package/addon/components/network-category-picker.js +83 -0
  12. package/addon/components/store-selector.hbs +4 -2
  13. package/addon/components/widget/orders.js +22 -17
  14. package/addon/controllers/networks/index/network/index.js +133 -9
  15. package/addon/controllers/networks/index/network/stores.js +422 -153
  16. package/addon/controllers/networks/index.js +66 -4
  17. package/addon/controllers/products/index/category.js +1 -1
  18. package/addon/controllers/settings/index.js +14 -7
  19. package/addon/controllers/settings/notifications.js +1 -1
  20. package/addon/models/store.js +44 -7
  21. package/addon/routes/networks/index/network/customers.js +4 -1
  22. package/addon/routes/networks/index/network/orders.js +4 -1
  23. package/addon/routes/networks/index/network/stores.js +19 -15
  24. package/addon/serializers/store.js +1 -0
  25. package/addon/templates/customers/index.hbs +0 -11
  26. package/addon/templates/networks/index/network/index.hbs +1 -1
  27. package/addon/templates/networks/index/network/stores.hbs +8 -163
  28. package/addon/templates/networks/index/network.hbs +2 -3
  29. package/addon/utils/create-shareable-link.js +21 -0
  30. package/app/adapters/addon-category.js +1 -1
  31. package/app/adapters/gateway.js +1 -1
  32. package/app/adapters/network.js +1 -1
  33. package/app/adapters/notification-channel.js +1 -1
  34. package/app/adapters/product-addon-category.js +1 -1
  35. package/app/adapters/product-addon.js +1 -1
  36. package/app/adapters/product-hour.js +1 -1
  37. package/app/adapters/product-store-location.js +1 -1
  38. package/app/adapters/product-variant-option.js +1 -1
  39. package/app/adapters/product-variant.js +1 -1
  40. package/app/adapters/product.js +1 -1
  41. package/app/adapters/store-hour.js +1 -1
  42. package/app/adapters/store-location.js +1 -1
  43. package/app/adapters/store.js +1 -1
  44. package/app/adapters/storefront.js +1 -1
  45. package/app/components/file-record.js +1 -1
  46. package/app/components/modals/add-store-hours.js +1 -1
  47. package/app/components/modals/add-store-to-category.js +1 -0
  48. package/app/components/modals/add-stores-to-network.js +1 -1
  49. package/app/components/modals/assign-driver.js +1 -1
  50. package/app/components/modals/create-first-store.js +1 -1
  51. package/app/components/modals/create-gateway.js +1 -1
  52. package/app/components/modals/create-network-category.js +1 -1
  53. package/app/components/modals/create-network.js +1 -1
  54. package/app/components/modals/create-new-variant.js +1 -1
  55. package/app/components/modals/create-notification-channel.js +1 -1
  56. package/app/components/modals/create-product-category.js +1 -1
  57. package/app/components/modals/create-store.js +1 -1
  58. package/app/components/modals/edit-network.js +1 -1
  59. package/app/components/modals/import-products.js +1 -1
  60. package/app/components/modals/incoming-order.js +1 -1
  61. package/app/components/modals/manage-addons.js +1 -1
  62. package/app/components/modals/order-ready-assign-driver.js +1 -1
  63. package/app/components/modals/select-addon-category.js +1 -1
  64. package/app/components/modals/share-network.js +1 -1
  65. package/app/components/modals/store-details.js +1 -0
  66. package/app/components/modals/store-form.js +1 -0
  67. package/app/components/modals/store-location-form.js +1 -1
  68. package/app/components/network-category-picker.js +1 -0
  69. package/app/components/order-card.js +1 -1
  70. package/app/components/schedule-manager.js +1 -1
  71. package/app/components/settings-container.js +1 -1
  72. package/app/components/store-selector.js +1 -1
  73. package/app/components/widget/customers.js +1 -1
  74. package/app/components/widget/orders.js +1 -1
  75. package/app/components/widget/storefront-metrics.js +1 -1
  76. package/app/controllers/application.js +1 -1
  77. package/app/controllers/customers/index.js +1 -1
  78. package/app/controllers/home.js +1 -1
  79. package/app/controllers/networks/index/network/customers.js +1 -1
  80. package/app/controllers/networks/index/network/index.js +1 -1
  81. package/app/controllers/networks/index/network/orders.js +1 -1
  82. package/app/controllers/networks/index/network/stores.js +1 -1
  83. package/app/controllers/networks/index/network.js +1 -1
  84. package/app/controllers/networks/index.js +1 -1
  85. package/app/controllers/orders/index.js +1 -1
  86. package/app/controllers/products/index/category/edit.js +1 -1
  87. package/app/controllers/products/index/category/new.js +1 -1
  88. package/app/controllers/products/index/category.js +1 -1
  89. package/app/controllers/products/index/index/edit.js +1 -1
  90. package/app/controllers/products/index/index.js +1 -1
  91. package/app/controllers/products/index.js +1 -1
  92. package/app/controllers/settings/api.js +1 -1
  93. package/app/controllers/settings/gateways.js +1 -1
  94. package/app/controllers/settings/index.js +1 -1
  95. package/app/controllers/settings/locations.js +1 -1
  96. package/app/controllers/settings/notifications.js +1 -1
  97. package/app/helpers/get-tip-amount.js +1 -1
  98. package/app/models/addon-category.js +1 -1
  99. package/app/models/gateway.js +1 -1
  100. package/app/models/network.js +1 -1
  101. package/app/models/notification-channel.js +1 -1
  102. package/app/models/product-addon-category.js +1 -1
  103. package/app/models/product-addon.js +1 -1
  104. package/app/models/product-hour.js +1 -1
  105. package/app/models/product-store-location.js +1 -1
  106. package/app/models/product-variant-option.js +1 -1
  107. package/app/models/product-variant.js +1 -1
  108. package/app/models/product.js +1 -1
  109. package/app/models/store-hour.js +1 -1
  110. package/app/models/store-location.js +1 -1
  111. package/app/models/store.js +1 -1
  112. package/app/routes/application.js +1 -1
  113. package/app/routes/customers/index/edit.js +1 -1
  114. package/app/routes/customers/index.js +1 -1
  115. package/app/routes/home.js +1 -1
  116. package/app/routes/networks/index/network/customers.js +1 -1
  117. package/app/routes/networks/index/network/index.js +1 -1
  118. package/app/routes/networks/index/network/orders.js +1 -1
  119. package/app/routes/networks/index/network/stores.js +1 -1
  120. package/app/routes/networks/index/network.js +1 -1
  121. package/app/routes/networks/index.js +1 -1
  122. package/app/routes/orders/index/edit.js +1 -1
  123. package/app/routes/orders/index/new.js +1 -1
  124. package/app/routes/orders/index/view.js +1 -1
  125. package/app/routes/orders/index.js +1 -1
  126. package/app/routes/products/index/category/edit.js +1 -1
  127. package/app/routes/products/index/category/new.js +1 -1
  128. package/app/routes/products/index/category.js +1 -1
  129. package/app/routes/products/index/index/edit.js +1 -1
  130. package/app/routes/products/index/index.js +1 -1
  131. package/app/routes/products/index.js +1 -1
  132. package/app/routes/settings/api.js +1 -1
  133. package/app/routes/settings/gateways.js +1 -1
  134. package/app/routes/settings/index.js +1 -1
  135. package/app/routes/settings/locations.js +1 -1
  136. package/app/routes/settings/notifications.js +1 -1
  137. package/app/serializers/addon-category.js +1 -1
  138. package/app/serializers/network.js +1 -1
  139. package/app/serializers/notification-channel.js +1 -1
  140. package/app/serializers/product-addon-category.js +1 -1
  141. package/app/serializers/product-variant.js +1 -1
  142. package/app/serializers/product.js +1 -1
  143. package/app/serializers/store-location.js +1 -1
  144. package/app/serializers/store.js +1 -1
  145. package/app/services/storefront.js +1 -1
  146. package/app/templates/customers/index/edit.js +1 -1
  147. package/app/templates/customers/index.js +1 -1
  148. package/app/templates/home.js +1 -1
  149. package/app/templates/networks/index/network/customers.js +1 -1
  150. package/app/templates/networks/index/network/index.js +1 -1
  151. package/app/templates/networks/index/network/orders.js +1 -1
  152. package/app/templates/networks/index/network/stores.js +1 -1
  153. package/app/templates/networks/index/network.js +1 -1
  154. package/app/templates/networks/index.js +1 -1
  155. package/app/templates/orders/index/edit.js +1 -1
  156. package/app/templates/orders/index/new.js +1 -1
  157. package/app/templates/orders/index/view.js +1 -1
  158. package/app/templates/orders/index.js +1 -1
  159. package/app/templates/products/index/category/edit.js +1 -1
  160. package/app/templates/products/index/category/new.js +1 -1
  161. package/app/templates/products/index/category.js +1 -1
  162. package/app/templates/products/index/index/edit.js +1 -1
  163. package/app/templates/products/index/index.js +1 -1
  164. package/app/templates/products/index.js +1 -1
  165. package/app/templates/settings/api.js +1 -1
  166. package/app/templates/settings/gateways.js +1 -1
  167. package/app/templates/settings/index.js +1 -1
  168. package/app/templates/settings/locations.js +1 -1
  169. package/app/templates/settings/notifications.js +1 -1
  170. package/app/templates/settings.js +1 -1
  171. package/app/utils/create-shareable-link.js +1 -0
  172. package/app/utils/get-gateway-schemas.js +1 -1
  173. package/app/utils/get-notification-schemas.js +1 -1
  174. package/composer.json +88 -0
  175. package/extension.json +10 -0
  176. package/package.json +6 -7
  177. package/phpstan.neon.dist +8 -0
  178. package/phpunit.xml.dist +16 -0
  179. package/server/.gitattributes +14 -0
  180. package/server/README.md +40 -0
  181. package/server/config/api.php +101 -0
  182. package/server/config/database.connections.php +57 -0
  183. package/server/config/storefront.php +19 -0
  184. package/server/config/twilio-notification-channel.php +36 -0
  185. package/server/migrations/2023_05_03_025307_create_carts_table.php +44 -0
  186. package/server/migrations/2023_05_03_025307_create_checkouts_table.php +51 -0
  187. package/server/migrations/2023_05_03_025307_create_gateways_table.php +48 -0
  188. package/server/migrations/2023_05_03_025307_create_network_stores_table.php +36 -0
  189. package/server/migrations/2023_05_03_025307_create_networks_table.php +56 -0
  190. package/server/migrations/2023_05_03_025307_create_notification_channels_table.php +43 -0
  191. package/server/migrations/2023_05_03_025307_create_payment_methods_table.php +44 -0
  192. package/server/migrations/2023_05_03_025307_create_product_addon_categories_table.php +38 -0
  193. package/server/migrations/2023_05_03_025307_create_product_addons_table.php +43 -0
  194. package/server/migrations/2023_05_03_025307_create_product_hours_table.php +37 -0
  195. package/server/migrations/2023_05_03_025307_create_product_store_locations_table.php +34 -0
  196. package/server/migrations/2023_05_03_025307_create_product_variant_options_table.php +40 -0
  197. package/server/migrations/2023_05_03_025307_create_product_variants_table.php +44 -0
  198. package/server/migrations/2023_05_03_025307_create_products_table.php +59 -0
  199. package/server/migrations/2023_05_03_025307_create_reviews_table.php +41 -0
  200. package/server/migrations/2023_05_03_025307_create_store_hours_table.php +37 -0
  201. package/server/migrations/2023_05_03_025307_create_store_locations_table.php +38 -0
  202. package/server/migrations/2023_05_03_025307_create_stores_table.php +57 -0
  203. package/server/migrations/2023_05_03_025307_create_votes_table.php +39 -0
  204. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_carts_table.php +40 -0
  205. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_checkouts_table.php +48 -0
  206. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_gateways_table.php +40 -0
  207. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_network_stores_table.php +40 -0
  208. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_networks_table.php +42 -0
  209. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_notification_channels_table.php +40 -0
  210. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_payment_methods_table.php +40 -0
  211. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_addon_categories_table.php +38 -0
  212. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_addons_table.php +38 -0
  213. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_hours_table.php +32 -0
  214. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_store_locations_table.php +34 -0
  215. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_variant_options_table.php +32 -0
  216. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_product_variants_table.php +32 -0
  217. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_products_table.php +44 -0
  218. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_reviews_table.php +38 -0
  219. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_store_hours_table.php +32 -0
  220. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_store_locations_table.php +40 -0
  221. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_stores_table.php +42 -0
  222. package/server/migrations/2023_05_03_025310_add_foreign_keys_to_votes_table.php +38 -0
  223. package/server/src/Auth/Schemas/Storefront.php +95 -0
  224. package/server/src/Console/Commands/NotifyStorefrontOrderNearby.php +86 -0
  225. package/server/src/Expansions/EntityExpansion.php +44 -0
  226. package/server/src/Http/Controllers/ActionController.php +119 -0
  227. package/server/src/Http/Controllers/AddonCategoryController.php +13 -0
  228. package/server/src/Http/Controllers/CustomerController.php +13 -0
  229. package/server/src/Http/Controllers/GatewayController.php +13 -0
  230. package/server/src/Http/Controllers/MetricsController.php +71 -0
  231. package/server/src/Http/Controllers/NetworkController.php +170 -0
  232. package/server/src/Http/Controllers/NotificationChannelController.php +13 -0
  233. package/server/src/Http/Controllers/OrderController.php +154 -0
  234. package/server/src/Http/Controllers/ProductAddonCategoryController.php +14 -0
  235. package/server/src/Http/Controllers/ProductAddonController.php +14 -0
  236. package/server/src/Http/Controllers/ProductController.php +123 -0
  237. package/server/src/Http/Controllers/ProductHourController.php +13 -0
  238. package/server/src/Http/Controllers/ProductVariantController.php +13 -0
  239. package/server/src/Http/Controllers/ProductVariantOptionController.php +13 -0
  240. package/server/src/Http/Controllers/ReviewController.php +13 -0
  241. package/server/src/Http/Controllers/StoreController.php +26 -0
  242. package/server/src/Http/Controllers/StoreHourController.php +13 -0
  243. package/server/src/Http/Controllers/StoreLocationController.php +13 -0
  244. package/server/src/Http/Controllers/StorefrontController.php +15 -0
  245. package/server/src/Http/Controllers/VoteController.php +13 -0
  246. package/server/src/Http/Controllers/v1/CartController.php +161 -0
  247. package/server/src/Http/Controllers/v1/CategoryController.php +138 -0
  248. package/server/src/Http/Controllers/v1/CheckoutController.php +957 -0
  249. package/server/src/Http/Controllers/v1/CustomerController.php +482 -0
  250. package/server/src/Http/Controllers/v1/GatewayControllerController.php +11 -0
  251. package/server/src/Http/Controllers/v1/NetworkController.php +281 -0
  252. package/server/src/Http/Controllers/v1/PaymentMethodController.php +11 -0
  253. package/server/src/Http/Controllers/v1/ProductController.php +94 -0
  254. package/server/src/Http/Controllers/v1/ReviewController.php +270 -0
  255. package/server/src/Http/Controllers/v1/ServiceQuoteController.php +402 -0
  256. package/server/src/Http/Controllers/v1/StoreController.php +176 -0
  257. package/server/src/Http/Filter/AddonCategoryFilter.php +19 -0
  258. package/server/src/Http/Filter/CustomerFilter.php +18 -0
  259. package/server/src/Http/Filter/GatewayFilter.php +13 -0
  260. package/server/src/Http/Filter/NetworkFilter.php +18 -0
  261. package/server/src/Http/Filter/NotificationChannelFilter.php +13 -0
  262. package/server/src/Http/Filter/OrderFilter.php +46 -0
  263. package/server/src/Http/Filter/ProductFilter.php +28 -0
  264. package/server/src/Http/Filter/StoreFilter.php +42 -0
  265. package/server/src/Http/Filter/StoreLocationFilter.php +23 -0
  266. package/server/src/Http/Middleware/SetStorefrontSession.php +130 -0
  267. package/server/src/Http/Requests/AddStoreToNetworkCategory.php +45 -0
  268. package/server/src/Http/Requests/CaptureOrderRequest.php +30 -0
  269. package/server/src/Http/Requests/CreateCustomerRequest.php +44 -0
  270. package/server/src/Http/Requests/CreateReviewRequest.php +34 -0
  271. package/server/src/Http/Requests/GetServiceQuoteFromCart.php +40 -0
  272. package/server/src/Http/Requests/InitializeCheckoutRequest.php +38 -0
  273. package/server/src/Http/Requests/NetworkActionRequest.php +43 -0
  274. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +31 -0
  275. package/server/src/Http/Resources/Cart.php +31 -0
  276. package/server/src/Http/Resources/Category.php +48 -0
  277. package/server/src/Http/Resources/Customer.php +36 -0
  278. package/server/src/Http/Resources/Gateway.php +32 -0
  279. package/server/src/Http/Resources/Media.php +29 -0
  280. package/server/src/Http/Resources/Network.php +48 -0
  281. package/server/src/Http/Resources/Product.php +209 -0
  282. package/server/src/Http/Resources/Review.php +45 -0
  283. package/server/src/Http/Resources/ReviewCustomer.php +60 -0
  284. package/server/src/Http/Resources/Store.php +76 -0
  285. package/server/src/Http/Resources/StoreHour.php +29 -0
  286. package/server/src/Http/Resources/StoreLocation.php +33 -0
  287. package/server/src/Imports/ProductsImport.php +20 -0
  288. package/server/src/Jobs/DownloadProductImageUrl.php +60 -0
  289. package/server/src/Listeners/HandleOrderCompleted.php +31 -0
  290. package/server/src/Listeners/HandleOrderDispatched.php +34 -0
  291. package/server/src/Listeners/HandleOrderDriverAssigned.php +37 -0
  292. package/server/src/Listeners/HandleOrderStarted.php +27 -0
  293. package/server/src/Mail/StorefrontNetworkInvite.php +48 -0
  294. package/server/src/Models/AddonCategory.php +30 -0
  295. package/server/src/Models/Cart.php +691 -0
  296. package/server/src/Models/Checkout.php +166 -0
  297. package/server/src/Models/Customer.php +88 -0
  298. package/server/src/Models/Gateway.php +165 -0
  299. package/server/src/Models/Network.php +300 -0
  300. package/server/src/Models/NetworkStore.php +86 -0
  301. package/server/src/Models/NotificationChannel.php +147 -0
  302. package/server/src/Models/PaymentMethod.php +99 -0
  303. package/server/src/Models/Product.php +315 -0
  304. package/server/src/Models/ProductAddon.php +128 -0
  305. package/server/src/Models/ProductAddonCategory.php +90 -0
  306. package/server/src/Models/ProductHour.php +59 -0
  307. package/server/src/Models/ProductStoreLocation.php +77 -0
  308. package/server/src/Models/ProductVariant.php +125 -0
  309. package/server/src/Models/ProductVariantOption.php +86 -0
  310. package/server/src/Models/Review.php +127 -0
  311. package/server/src/Models/Store.php +478 -0
  312. package/server/src/Models/StoreHour.php +59 -0
  313. package/server/src/Models/StoreLocation.php +126 -0
  314. package/server/src/Models/StorefrontModel.php +22 -0
  315. package/server/src/Models/Vote.php +84 -0
  316. package/server/src/Notifications/StorefrontOrderCanceled.php +196 -0
  317. package/server/src/Notifications/StorefrontOrderCompleted.php +201 -0
  318. package/server/src/Notifications/StorefrontOrderCreated.php +157 -0
  319. package/server/src/Notifications/StorefrontOrderDriverAssigned.php +200 -0
  320. package/server/src/Notifications/StorefrontOrderEnroute.php +199 -0
  321. package/server/src/Notifications/StorefrontOrderNearby.php +201 -0
  322. package/server/src/Notifications/StorefrontOrderPreparing.php +202 -0
  323. package/server/src/Notifications/StorefrontOrderReadyForPickup.php +202 -0
  324. package/server/src/Observers/NetworkObserver.php +40 -0
  325. package/server/src/Observers/ProductObserver.php +118 -0
  326. package/server/src/Providers/EventServiceProvider.php +23 -0
  327. package/server/src/Providers/StorefrontServiceProvider.php +103 -0
  328. package/server/src/Support/Metrics.php +193 -0
  329. package/server/src/Support/OrderConfig.php +13 -0
  330. package/server/src/Support/QPay.php +208 -0
  331. package/server/src/Support/Storefront.php +201 -0
  332. package/server/src/routes.php +180 -0
  333. package/server/tests/Feature.php +5 -0
@@ -0,0 +1,202 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Notifications;
4
+
5
+ use Exception;
6
+ use Fleetbase\FleetOps\Models\Order;
7
+ use Fleetbase\Storefront\Models\NotificationChannel;
8
+ use Fleetbase\Storefront\Support\Storefront;
9
+ use Fleetbase\FleetOps\Support\Utils;
10
+ // use Fleetbase\FleetOps\Support\Utils;
11
+ use Illuminate\Bus\Queueable;
12
+ // use Illuminate\Contracts\Queue\ShouldQueue;
13
+ use Illuminate\Notifications\Messages\MailMessage;
14
+ use Illuminate\Notifications\Notification;
15
+ use NotificationChannels\Fcm\FcmChannel;
16
+ use NotificationChannels\Fcm\FcmMessage;
17
+ use NotificationChannels\Apn\ApnChannel;
18
+ use NotificationChannels\Apn\ApnMessage;
19
+ use NotificationChannels\Fcm\Resources\AndroidConfig;
20
+ use NotificationChannels\Fcm\Resources\AndroidFcmOptions;
21
+ use NotificationChannels\Fcm\Resources\AndroidNotification;
22
+ use NotificationChannels\Fcm\Resources\ApnsConfig;
23
+ use NotificationChannels\Fcm\Resources\ApnsFcmOptions;
24
+ use Pushok\Client as PushOkClient;
25
+ use Pushok\AuthProvider\Token as PuskOkToken;
26
+
27
+ class StorefrontOrderPreparing extends Notification
28
+ {
29
+ use Queueable;
30
+
31
+ /**
32
+ * Create a new notification instance.
33
+ *
34
+ * @return void
35
+ */
36
+ public function __construct(Order $order)
37
+ {
38
+ $this->order = $order->setRelations([]);
39
+ $this->storefront = Storefront::findAbout($this->order->getMeta('storefront_id'));
40
+ }
41
+
42
+ /**
43
+ * Get the notification's delivery channels.
44
+ *
45
+ * @param mixed $notifiable
46
+ * @return array
47
+ */
48
+ public function via($notifiable)
49
+ {
50
+ // $channels = ['mail'];
51
+ $channels = [];
52
+
53
+ if (!$this->storefront) {
54
+ return $channels;
55
+ }
56
+
57
+ $hasApnNotificationChannels = NotificationChannel::where(['owner_uuid' => $this->storefront->uuid, 'scheme' => 'apn'])->count();
58
+ $hasFcmNotificationChannels = NotificationChannel::where(['owner_uuid' => $this->storefront->uuid, 'scheme' => 'android'])->count();
59
+
60
+ if ($hasApnNotificationChannels) {
61
+ $channels[] = ApnChannel::class;
62
+ }
63
+ if ($hasFcmNotificationChannels) {
64
+ $channels[] = FcmChannel::class;
65
+ }
66
+
67
+ return $channels;
68
+ }
69
+
70
+ /**
71
+ * Get the mail representation of the notification.
72
+ *
73
+ * @param mixed $notifiable
74
+ * @return \Illuminate\Notifications\Messages\MailMessage
75
+ */
76
+ public function toMail($notifiable)
77
+ {
78
+ $message = (new MailMessage)
79
+ ->subject('Your order from ' . $this->storefront->name . ' is being prepared')
80
+ ->line('Your order is getting started.');
81
+
82
+ // $message->action('View Details', Utils::consoleUrl('', ['shift' => 'fleet-ops/orders/view/' . $this->order->public_id]));
83
+
84
+ return $message;
85
+ }
86
+
87
+ /**
88
+ * Get the firebase cloud message representation of the notification.
89
+ *
90
+ * @param mixed $notifiable
91
+ * @return array
92
+ */
93
+ public function toFcm($notifiable)
94
+ {
95
+ $notification = \NotificationChannels\Fcm\Resources\Notification::create()
96
+ ->setTitle('Your order from ' . $this->storefront->name . ' is being prepared')
97
+ ->setBody('Your order is getting started.');
98
+
99
+ $message = FcmMessage::create()
100
+ ->setData(['order' => $this->order->uuid, 'id' => $this->order->public_id, 'type' => 'order_preparing'])
101
+ ->setNotification($notification)
102
+ ->setAndroid(
103
+ AndroidConfig::create()
104
+ ->setFcmOptions(AndroidFcmOptions::create()->setAnalyticsLabel('analytics'))
105
+ ->setNotification(AndroidNotification::create()->setColor('#4391EA'))
106
+ )->setApns(
107
+ ApnsConfig::create()
108
+ ->setFcmOptions(ApnsFcmOptions::create()->setAnalyticsLabel('analytics_ios'))
109
+ );
110
+
111
+ return $message;
112
+ }
113
+
114
+ public function fcmProject($notifiable, $message)
115
+ {
116
+ $about = Storefront::findAbout($this->order->getMeta('storefront_id'));
117
+
118
+ if ($this->order->hasMeta('storefront_notification_channel')) {
119
+ $notificationChannel = NotificationChannel::where([
120
+ 'owner_uuid' => $about->uuid,
121
+ 'app_key' => $this->order->getMeta('storefront_notification_channel'),
122
+ 'scheme' => 'fcm'
123
+ ])->first();
124
+ } else {
125
+ $notificationChannel = NotificationChannel::where([
126
+ 'owner_uuid' => $about->uuid,
127
+ 'scheme' => 'fcm'
128
+ ])->first();
129
+ }
130
+
131
+ if (!$notificationChannel) {
132
+ return 'app';
133
+ }
134
+
135
+ $this->configureFcm($notificationChannel);
136
+
137
+ return $notificationChannel->app_key;
138
+ }
139
+
140
+ public function configureFcm($notificationChannel)
141
+ {
142
+ $config = (array) $notificationChannel->config;
143
+ $fcmConfig = config('firebase.projects.app');
144
+
145
+ // set credentials
146
+ Utils::set($fcmConfig, 'credentials.file', $config['firebase_credentials_json']);
147
+
148
+ // set db url
149
+ Utils::set($fcmConfig, 'database.url', $config['firebase_database_url']);
150
+
151
+ config('firebase.projects.' . $notificationChannel->app_key, $fcmConfig);
152
+
153
+ return $fcmConfig;
154
+ }
155
+
156
+ /**
157
+ * Get the apns message representation of the notification.
158
+ *
159
+ * @param mixed $notifiable
160
+ * @return array
161
+ */
162
+ public function toApn($notifiable)
163
+ {
164
+ $about = Storefront::findAbout($this->order->getMeta('storefront_id'));
165
+
166
+ if ($this->order->hasMeta('storefront_notification_channel')) {
167
+ $notificationChannel = NotificationChannel::where([
168
+ 'owner_uuid' => $about->uuid,
169
+ 'app_key' => $this->order->getMeta('storefront_notification_channel'),
170
+ 'scheme' => 'apn'
171
+ ])->first();
172
+ } else {
173
+ $notificationChannel = NotificationChannel::where([
174
+ 'owner_uuid' => $about->uuid,
175
+ 'scheme' => 'apn'
176
+ ])->first();
177
+ }
178
+
179
+ $config = (array) $notificationChannel->config;
180
+
181
+ $message = ApnMessage::create()
182
+ ->badge(1)
183
+ ->title('Your order from ' . $this->storefront->name . ' is being prepared')
184
+ ->body('Your order is getting started.')
185
+ ->custom('type', 'order_preparing')
186
+ ->custom('order', $this->order->uuid)
187
+ ->custom('id', $this->order->public_id);
188
+
189
+ try {
190
+ $channelClient = new PushOkClient(PuskOkToken::create($config));
191
+ } catch (Exception $e) {
192
+ // report to sentry the exception
193
+ app('sentry')->captureException($e);
194
+ // return the apn message to be sent by fleetbase defaults anyway -- backup
195
+ return;
196
+ }
197
+
198
+ $message = $message->via($channelClient);
199
+
200
+ return $message;
201
+ }
202
+ }
@@ -0,0 +1,202 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Notifications;
4
+
5
+ use Exception;
6
+ use Fleetbase\FleetOps\Models\Order;
7
+ use Fleetbase\Storefront\Models\NotificationChannel;
8
+ use Fleetbase\Storefront\Support\Storefront;
9
+ use Fleetbase\FleetOps\Support\Utils;
10
+ // use Fleetbase\FleetOps\Support\Utils;
11
+ use Illuminate\Bus\Queueable;
12
+ // use Illuminate\Contracts\Queue\ShouldQueue;
13
+ use Illuminate\Notifications\Messages\MailMessage;
14
+ use Illuminate\Notifications\Notification;
15
+ use NotificationChannels\Fcm\FcmChannel;
16
+ use NotificationChannels\Fcm\FcmMessage;
17
+ use NotificationChannels\Apn\ApnChannel;
18
+ use NotificationChannels\Apn\ApnMessage;
19
+ use NotificationChannels\Fcm\Resources\AndroidConfig;
20
+ use NotificationChannels\Fcm\Resources\AndroidFcmOptions;
21
+ use NotificationChannels\Fcm\Resources\AndroidNotification;
22
+ use NotificationChannels\Fcm\Resources\ApnsConfig;
23
+ use NotificationChannels\Fcm\Resources\ApnsFcmOptions;
24
+ use Pushok\Client as PushOkClient;
25
+ use Pushok\AuthProvider\Token as PuskOkToken;
26
+
27
+ class StorefrontOrderReadyForPickup extends Notification
28
+ {
29
+ use Queueable;
30
+
31
+ /**
32
+ * Create a new notification instance.
33
+ *
34
+ * @return void
35
+ */
36
+ public function __construct(Order $order)
37
+ {
38
+ $this->order = $order->setRelations([]);
39
+ $this->storefront = Storefront::findAbout($this->order->getMeta('storefront_id'));
40
+ }
41
+
42
+ /**
43
+ * Get the notification's delivery channels.
44
+ *
45
+ * @param mixed $notifiable
46
+ * @return array
47
+ */
48
+ public function via($notifiable)
49
+ {
50
+ // $channels = ['mail'];
51
+ $channels = [];
52
+
53
+ if (!$this->storefront) {
54
+ return $channels;
55
+ }
56
+
57
+ $hasApnNotificationChannels = NotificationChannel::where(['owner_uuid' => $this->storefront->uuid, 'scheme' => 'apn'])->count();
58
+ $hasFcmNotificationChannels = NotificationChannel::where(['owner_uuid' => $this->storefront->uuid, 'scheme' => 'android'])->count();
59
+
60
+ if ($hasApnNotificationChannels) {
61
+ $channels[] = ApnChannel::class;
62
+ }
63
+ if ($hasFcmNotificationChannels) {
64
+ $channels[] = FcmChannel::class;
65
+ }
66
+
67
+ return $channels;
68
+ }
69
+
70
+ /**
71
+ * Get the mail representation of the notification.
72
+ *
73
+ * @param mixed $notifiable
74
+ * @return \Illuminate\Notifications\Messages\MailMessage
75
+ */
76
+ public function toMail($notifiable)
77
+ {
78
+ $message = (new MailMessage)
79
+ ->subject('Your order from ' . $this->storefront->name . ' is ready for pickup!')
80
+ ->line('You can proceed to pickup your order.');
81
+
82
+ // $message->action('View Details', Utils::consoleUrl('', ['shift' => 'fleet-ops/orders/view/' . $this->order->public_id]));
83
+
84
+ return $message;
85
+ }
86
+
87
+ /**
88
+ * Get the firebase cloud message representation of the notification.
89
+ *
90
+ * @param mixed $notifiable
91
+ * @return array
92
+ */
93
+ public function toFcm($notifiable)
94
+ {
95
+ $notification = \NotificationChannels\Fcm\Resources\Notification::create()
96
+ ->setTitle('Your order from ' . $this->storefront->name . ' is ready for pickup!')
97
+ ->setBody('You can proceed to pickup your order.');
98
+
99
+ $message = FcmMessage::create()
100
+ ->setData(['order' => $this->order->uuid, 'id' => $this->order->public_id, 'type' => 'order_ready'])
101
+ ->setNotification($notification)
102
+ ->setAndroid(
103
+ AndroidConfig::create()
104
+ ->setFcmOptions(AndroidFcmOptions::create()->setAnalyticsLabel('analytics'))
105
+ ->setNotification(AndroidNotification::create()->setColor('#4391EA'))
106
+ )->setApns(
107
+ ApnsConfig::create()
108
+ ->setFcmOptions(ApnsFcmOptions::create()->setAnalyticsLabel('analytics_ios'))
109
+ );
110
+
111
+ return $message;
112
+ }
113
+
114
+ public function fcmProject($notifiable, $message)
115
+ {
116
+ $about = Storefront::findAbout($this->order->getMeta('storefront_id'));
117
+
118
+ if ($this->order->hasMeta('storefront_notification_channel')) {
119
+ $notificationChannel = NotificationChannel::where([
120
+ 'owner_uuid' => $about->uuid,
121
+ 'app_key' => $this->order->getMeta('storefront_notification_channel'),
122
+ 'scheme' => 'fcm'
123
+ ])->first();
124
+ } else {
125
+ $notificationChannel = NotificationChannel::where([
126
+ 'owner_uuid' => $about->uuid,
127
+ 'scheme' => 'fcm'
128
+ ])->first();
129
+ }
130
+
131
+ if (!$notificationChannel) {
132
+ return 'app';
133
+ }
134
+
135
+ $this->configureFcm($notificationChannel);
136
+
137
+ return $notificationChannel->app_key;
138
+ }
139
+
140
+ public function configureFcm($notificationChannel)
141
+ {
142
+ $config = (array) $notificationChannel->config;
143
+ $fcmConfig = config('firebase.projects.app');
144
+
145
+ // set credentials
146
+ Utils::set($fcmConfig, 'credentials.file', $config['firebase_credentials_json']);
147
+
148
+ // set db url
149
+ Utils::set($fcmConfig, 'database.url', $config['firebase_database_url']);
150
+
151
+ config('firebase.projects.' . $notificationChannel->app_key, $fcmConfig);
152
+
153
+ return $fcmConfig;
154
+ }
155
+
156
+ /**
157
+ * Get the apns message representation of the notification.
158
+ *
159
+ * @param mixed $notifiable
160
+ * @return array
161
+ */
162
+ public function toApn($notifiable)
163
+ {
164
+ $about = Storefront::findAbout($this->order->getMeta('storefront_id'));
165
+
166
+ if ($this->order->hasMeta('storefront_notification_channel')) {
167
+ $notificationChannel = NotificationChannel::where([
168
+ 'owner_uuid' => $about->uuid,
169
+ 'app_key' => $this->order->getMeta('storefront_notification_channel'),
170
+ 'scheme' => 'apn'
171
+ ])->first();
172
+ } else {
173
+ $notificationChannel = NotificationChannel::where([
174
+ 'owner_uuid' => $about->uuid,
175
+ 'scheme' => 'apn'
176
+ ])->first();
177
+ }
178
+
179
+ $config = (array) $notificationChannel->config;
180
+
181
+ $message = ApnMessage::create()
182
+ ->badge(1)
183
+ ->title('Your order from ' . $this->storefront->name . ' is ready for pickup!')
184
+ ->body('You can proceed to pickup your order.')
185
+ ->custom('type', 'order_ready')
186
+ ->custom('order', $this->order->uuid)
187
+ ->custom('id', $this->order->public_id);
188
+
189
+ try {
190
+ $channelClient = new PushOkClient(PuskOkToken::create($config));
191
+ } catch (Exception $e) {
192
+ // report to sentry the exception
193
+ app('sentry')->captureException($e);
194
+ // return the apn message to be sent by fleetbase defaults anyway -- backup
195
+ return;
196
+ }
197
+
198
+ $message = $message->via($channelClient);
199
+
200
+ return $message;
201
+ }
202
+ }
@@ -0,0 +1,40 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Observers;
4
+
5
+ use Fleetbase\Storefront\Models\Network;
6
+ use Illuminate\Support\Facades\Request;
7
+
8
+ class NetworkObserver
9
+ {
10
+ /**
11
+ * Handle the Network "updated" event.
12
+ *
13
+ * @param \Fleetbase\Storefront\Models\Network $network The Network that is updating.
14
+ * @return void
15
+ */
16
+ public function updating(Network $network): void
17
+ {
18
+ $network->flushAttributesCache();
19
+ $alertable = Request::array('network.alertable');
20
+
21
+ // set alertables to public_id
22
+ $network->alertable = collect($alertable)->mapWithKeys(
23
+ function ($alertables, $key) {
24
+ if (!is_array($alertables)) {
25
+ return [];
26
+ }
27
+
28
+ return [
29
+ $key => collect($alertables)->map(
30
+ function ($user) {
31
+ return data_get($user, 'public_id');
32
+ }
33
+ )
34
+ ->values()
35
+ ->toArray()
36
+ ];
37
+ }
38
+ )->toArray();
39
+ }
40
+ }
@@ -0,0 +1,118 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Observers;
4
+
5
+ use Fleetbase\Models\File;
6
+ use Fleetbase\FleetOps\Support\Utils;
7
+ use Fleetbase\Storefront\Models\Product;
8
+ use Fleetbase\Storefront\Models\ProductAddonCategory;
9
+ use Fleetbase\Storefront\Models\ProductVariant;
10
+ use Fleetbase\Storefront\Models\ProductVariantOption;
11
+ use Illuminate\Support\Facades\Request;
12
+ use Illuminate\Support\Arr;
13
+
14
+ class ProductObserver
15
+ {
16
+ /**
17
+ * Handle the Product "created" event.
18
+ *
19
+ * @param \Fleetbase\Storefront\Models\Product $product The Product that was created.
20
+ * @return void
21
+ */
22
+ public function created(Product $product): void
23
+ {
24
+ $addonCategories = Request::input('product.addon_categories');
25
+ $variants = Request::input('product.variants');
26
+ $files = Request::input('product.files');
27
+
28
+ // save addon categories
29
+ foreach ($addonCategories as $addonCategory) {
30
+ $addonCategory['product_uuid'] = $product->uuid;
31
+
32
+ ProductAddonCategory::create(Arr::except($addonCategory, ['category']));
33
+ }
34
+
35
+ // save product variants
36
+ foreach ($variants as $variant) {
37
+ $variant['created_by_uuid'] = Request::session()->get('user');
38
+ $variant['company_uuid'] = Request::session()->get('company');
39
+ $variant['product_uuid'] = $product->uuid;
40
+
41
+ $productVariant = ProductVariant::create(Arr::except($variant, ['options']));
42
+
43
+ foreach ($variant['options'] as $option) {
44
+ $option['product_variant_uuid'] = $productVariant->uuid;
45
+ ProductVariantOption::create($option);
46
+ }
47
+ }
48
+
49
+ // set keys on files
50
+ foreach ($files as $file) {
51
+ $fileRecord = File::where('uuid', $file['uuid'])->first();
52
+ $fileRecord->setKey($product);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Handle the Product "updated" event.
58
+ *
59
+ * @param \Fleetbase\Storefront\Models\Product $product The Product that was created.
60
+ * @return void
61
+ */
62
+ public function updated(Product $product): void
63
+ {
64
+ $productAddonCategories = Request::input('product.addon_categories');
65
+ $variants = Request::input('product.variants');
66
+
67
+ // update addon categories
68
+ foreach ($productAddonCategories as $productAddonCategory) {
69
+ if (!empty($productAddonCategory['uuid'])) {
70
+ ProductAddonCategory::where('uuid', $productAddonCategory['uuid'])->update(Arr::except($productAddonCategory, ['uuid', 'name', 'category']));
71
+ continue;
72
+ }
73
+
74
+ // add new addon category
75
+ $productAddonCategory['product_uuid'] = $product->uuid;
76
+ ProductAddonCategory::create(Arr::except($productAddonCategory, ['category']));
77
+ }
78
+
79
+ // update product variants
80
+ foreach ($variants as $variant) {
81
+ if (!empty($variant['uuid'])) {
82
+ // update product variante
83
+ ProductVariant::where('uuid', $variant['uuid'])->update(Arr::except($variant, ['uuid', 'options']));
84
+
85
+ // update product variant options
86
+ foreach ($variant['options'] as $option) {
87
+ if (!empty($option['uuid'])) {
88
+ // make sure additional cost is always numbers only
89
+ if (isset($option['additional_cost'])) {
90
+ $option['additional_cost'] = Utils::numbersOnly($option['additional_cost']);
91
+ }
92
+
93
+ $updateAttrs = Arr::except($option, ['uuid']);
94
+
95
+ ProductVariantOption::where('uuid', $option['uuid'])->update($updateAttrs);
96
+ continue;
97
+ }
98
+
99
+ $option['product_variant_uuid'] = $variant['uuid'];
100
+ ProductVariantOption::create($option);
101
+ }
102
+ continue;
103
+ }
104
+
105
+ // create new variant
106
+ $variant['created_by_uuid'] = Request::session()->get('user');
107
+ $variant['company_uuid'] = Request::session()->get('company');
108
+ $variant['product_uuid'] = $product->uuid;
109
+
110
+ $productVariant = ProductVariant::create(Arr::except($variant, ['options']));
111
+
112
+ foreach ($variant['options'] as $option) {
113
+ $option['product_variant_uuid'] = $productVariant->uuid;
114
+ ProductVariantOption::create($option);
115
+ }
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,23 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Providers;
4
+
5
+ use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
6
+
7
+ class EventServiceProvider extends ServiceProvider
8
+ {
9
+ /**
10
+ * The event listener mappings for the application.
11
+ *
12
+ * @var array
13
+ */
14
+ protected $listen = [
15
+ /**
16
+ * Order Events
17
+ */
18
+ \Fleetbase\FleetOps\Events\OrderStarted::class => [\Fleetbase\Storefront\Listeners\HandleOrderStarted::class],
19
+ \Fleetbase\FleetOps\Events\OrderDispatched::class => [\Fleetbase\Storefront\Listeners\HandleOrderDispatched::class],
20
+ \Fleetbase\FleetOps\Events\OrderCompleted::class => [\Fleetbase\Storefront\Listeners\HandleOrderCompleted::class],
21
+ \Fleetbase\FleetOps\Events\OrderDriverAssigned::class => [\Fleetbase\Storefront\Listeners\HandleOrderDriverAssigned::class],
22
+ ];
23
+ }
@@ -0,0 +1,103 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Providers;
4
+
5
+ use Fleetbase\Providers\CoreServiceProvider;
6
+ use Fleetbase\FleetOps\Providers\FleetOpsServiceProvider;
7
+
8
+ if (!class_exists(CoreServiceProvider::class)) {
9
+ throw new \Exception('Storefront cannot be loaded without `fleetbase/core-api` installed!');
10
+ }
11
+
12
+ if (!class_exists(FleetOpsServiceProvider::class)) {
13
+ throw new \Exception('Storefront cannot be loaded without `fleetbase/fleetops-api` installed!');
14
+ }
15
+
16
+ /**
17
+ * Storefront service provider.
18
+ *
19
+ * @package \Fleetbase\Storefront\Providers
20
+ */
21
+ class StorefrontServiceProvider extends CoreServiceProvider
22
+ {
23
+ /**
24
+ * The observers registered with the service provider.
25
+ *
26
+ * @var array
27
+ */
28
+ public $observers = [
29
+ \Fleetbase\Storefront\Models\Product::class => \Fleetbase\Storefront\Observers\ProductObserver::class,
30
+ \Fleetbase\Storefront\Models\Network::class => \Fleetbase\Storefront\Observers\NetworkObserver::class,
31
+ ];
32
+
33
+ /**
34
+ * The middleware groups registered with the service provider.
35
+ *
36
+ * @var array
37
+ */
38
+ public $middleware = [
39
+ 'storefront.api' => [
40
+ 'throttle:60,1',
41
+ \Illuminate\Session\Middleware\StartSession::class,
42
+ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
43
+ \Fleetbase\Storefront\Http\Middleware\SetStorefrontSession::class,
44
+ \Fleetbase\Http\Middleware\ConvertStringBooleans::class,
45
+ \Fleetbase\Http\Middleware\SetGlobalHeaders::class,
46
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
47
+ \Fleetbase\Http\Middleware\LogApiRequests::class,
48
+ ],
49
+ ];
50
+
51
+ /**
52
+ * The console commands registered with the service provider.
53
+ *
54
+ * @var array
55
+ */
56
+ public $commands = [
57
+ \Fleetbase\Storefront\Console\Commands\NotifyStorefrontOrderNearby::class
58
+ ];
59
+
60
+ /**
61
+ * Register any application services.
62
+ *
63
+ * Within the register method, you should only bind things into the
64
+ * service container. You should never attempt to register any event
65
+ * listeners, routes, or any other piece of functionality within the
66
+ * register method.
67
+ *
68
+ * More information on this can be found in the Laravel documentation:
69
+ * https://laravel.com/docs/8.x/providers
70
+ *
71
+ * @return void
72
+ */
73
+ public function register()
74
+ {
75
+ $this->app->register(CoreServiceProvider::class);
76
+ $this->app->register(FleetOpsServiceProvider::class);
77
+ }
78
+
79
+ /**
80
+ * Bootstrap any package services.
81
+ *
82
+ * @return void
83
+ *
84
+ * @throws \Exception If the `fleetbase/core-api` package is not installed.
85
+ * @throws \Exception If the `fleetbase/fleetops-api` package is not installed.
86
+ */
87
+ public function boot()
88
+ {
89
+ $this->registerCommands();
90
+ $this->scheduleCommands(function ($schedule) {
91
+ $schedule->command('storefront:notify-order-nearby')->everyMinute();
92
+ });
93
+ $this->registerObservers();
94
+ $this->registerMiddleware();
95
+ $this->registerExpansionsFrom(__DIR__ . '/../Expansions');
96
+ $this->loadRoutesFrom(__DIR__ . '/../routes.php');
97
+ $this->loadMigrationsFrom(__DIR__ . '/../../migrations');
98
+ $this->mergeConfigFrom(__DIR__ . '/../../config/database.connections.php', 'database.connections');
99
+ $this->mergeConfigFrom(__DIR__ . '/../../config/storefront.php', 'storefront');
100
+ $this->mergeConfigFrom(__DIR__ . '/../../config/api.php', 'storefront.api');
101
+ $this->mergeConfigFrom(__DIR__ . '/../../config/twilio-notification-channel.php', 'twilio-notification-channel');
102
+ }
103
+ }