stay_commerce 0.1.0

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 (496) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +53 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/stay_manifest.js +1 -0
  6. data/app/assets/fonts/bootstrap-icons.woff +0 -0
  7. data/app/assets/fonts/bootstrap-icons.woff2 +0 -0
  8. data/app/assets/images/chevron-down.svg +3 -0
  9. data/app/assets/images/circle-fill.svg +3 -0
  10. data/app/assets/images/circle.svg +3 -0
  11. data/app/assets/images/plus-circle.svg +4 -0
  12. data/app/assets/images/save.svg +3 -0
  13. data/app/assets/images/store.svg +3 -0
  14. data/app/assets/javascripts/stay/admin/address.js +165 -0
  15. data/app/assets/javascripts/stay/admin/ckeditor.min.js +7 -0
  16. data/app/assets/javascripts/stay/admin/flatpickr.min.js +2 -0
  17. data/app/assets/javascripts/stay/admin/index.js +28 -0
  18. data/app/assets/javascripts/stay/admin/property.js +37 -0
  19. data/app/assets/javascripts/stay/admin/select2.min.js +2 -0
  20. data/app/assets/javascripts/stay/application.js +14 -0
  21. data/app/assets/javascripts/stay/bootstrap.bundle.min.js +7 -0
  22. data/app/assets/javascripts/stay/ckeditor.js +11 -0
  23. data/app/assets/javascripts/stay/custom.js +56 -0
  24. data/app/assets/javascripts/stay/jquery-ui.min.js +13 -0
  25. data/app/assets/javascripts/stay/jquery.min.js +2 -0
  26. data/app/assets/stylesheets/stay/admin/flatpickr.min.css +13 -0
  27. data/app/assets/stylesheets/stay/admin/index.css +100 -0
  28. data/app/assets/stylesheets/stay/admin/select2.min.css +1 -0
  29. data/app/assets/stylesheets/stay/application.css +24 -0
  30. data/app/assets/stylesheets/stay/badges.css +72 -0
  31. data/app/assets/stylesheets/stay/bootstrap-icons.min.css +5 -0
  32. data/app/assets/stylesheets/stay/bootstrap-icons_fix.css +5 -0
  33. data/app/assets/stylesheets/stay/common.css +283 -0
  34. data/app/assets/stylesheets/stay/jquery-ui.css +1311 -0
  35. data/app/assets/stylesheets/stay/webpixels.css +7 -0
  36. data/app/channels/chat_channel.rb +13 -0
  37. data/app/controllers/concerns/stay/booking_validations.rb +86 -0
  38. data/app/controllers/concerns/stay/stripe_concern.rb +72 -0
  39. data/app/controllers/stay/admin/addresses_controller.rb +76 -0
  40. data/app/controllers/stay/admin/amenities_controller.rb +52 -0
  41. data/app/controllers/stay/admin/amenity_categories_controller.rb +52 -0
  42. data/app/controllers/stay/admin/base_controller.rb +38 -0
  43. data/app/controllers/stay/admin/bed_types_controller.rb +51 -0
  44. data/app/controllers/stay/admin/bookings_controller.rb +54 -0
  45. data/app/controllers/stay/admin/cancellation_policies_controller.rb +51 -0
  46. data/app/controllers/stay/admin/chats_controller.rb +53 -0
  47. data/app/controllers/stay/admin/cities_controller.rb +66 -0
  48. data/app/controllers/stay/admin/confirmations_controller.rb +30 -0
  49. data/app/controllers/stay/admin/countries_controller.rb +53 -0
  50. data/app/controllers/stay/admin/dashboard_controller.rb +8 -0
  51. data/app/controllers/stay/admin/features_controller.rb +54 -0
  52. data/app/controllers/stay/admin/home_controller.rb +15 -0
  53. data/app/controllers/stay/admin/house_rules_controller.rb +51 -0
  54. data/app/controllers/stay/admin/messages_controller.rb +53 -0
  55. data/app/controllers/stay/admin/passwords_controller.rb +32 -0
  56. data/app/controllers/stay/admin/payment_methods_controller.rb +53 -0
  57. data/app/controllers/stay/admin/payments_controller.rb +63 -0
  58. data/app/controllers/stay/admin/properties_controller.rb +118 -0
  59. data/app/controllers/stay/admin/property_categories_controller.rb +51 -0
  60. data/app/controllers/stay/admin/property_types_controller.rb +51 -0
  61. data/app/controllers/stay/admin/registrations_controller.rb +19 -0
  62. data/app/controllers/stay/admin/reviews_Controller.rb +63 -0
  63. data/app/controllers/stay/admin/roles_controller.rb +54 -0
  64. data/app/controllers/stay/admin/room_amenities_controller.rb +43 -0
  65. data/app/controllers/stay/admin/room_types_controller.rb +58 -0
  66. data/app/controllers/stay/admin/rooms_controller.rb +62 -0
  67. data/app/controllers/stay/admin/sessions_controller.rb +29 -0
  68. data/app/controllers/stay/admin/states_controller.rb +68 -0
  69. data/app/controllers/stay/admin/stores_controller.rb +132 -0
  70. data/app/controllers/stay/admin/taxes_controller.rb +51 -0
  71. data/app/controllers/stay/admin/users_controller.rb +85 -0
  72. data/app/controllers/stay/api/v1/amenities_controller.rb +13 -0
  73. data/app/controllers/stay/api/v1/amenity_categories_controller.rb +13 -0
  74. data/app/controllers/stay/api/v1/bed_types_controller.rb +9 -0
  75. data/app/controllers/stay/api/v1/booking_queries_controller.rb +205 -0
  76. data/app/controllers/stay/api/v1/bookings_controller.rb +299 -0
  77. data/app/controllers/stay/api/v1/cancellation_policies_controller.rb +10 -0
  78. data/app/controllers/stay/api/v1/chats_controller.rb +116 -0
  79. data/app/controllers/stay/api/v1/credit_cards_controller.rb +62 -0
  80. data/app/controllers/stay/api/v1/features_controller.rb +21 -0
  81. data/app/controllers/stay/api/v1/house_rules_controller.rb +9 -0
  82. data/app/controllers/stay/api/v1/invoices_controller.rb +47 -0
  83. data/app/controllers/stay/api/v1/messages_controller.rb +144 -0
  84. data/app/controllers/stay/api/v1/payments_controller.rb +61 -0
  85. data/app/controllers/stay/api/v1/profiles_controller.rb +23 -0
  86. data/app/controllers/stay/api/v1/properties_controller.rb +233 -0
  87. data/app/controllers/stay/api/v1/property_categories_controller.rb +14 -0
  88. data/app/controllers/stay/api/v1/property_types_controller.rb +14 -0
  89. data/app/controllers/stay/api/v1/room_types_controller.rb +14 -0
  90. data/app/controllers/stay/api/v1/rooms_controller.rb +23 -0
  91. data/app/controllers/stay/api/v1/user_paypal_controller.rb +53 -0
  92. data/app/controllers/stay/api/v1/users_controller.rb +17 -0
  93. data/app/controllers/stay/application_controller.rb +44 -0
  94. data/app/controllers/stay/base_api_controller.rb +14 -0
  95. data/app/controllers/stay/bookings_controller.rb +29 -0
  96. data/app/controllers/stay/payments_controller.rb +50 -0
  97. data/app/controllers/stay/profiles_controller.rb +26 -0
  98. data/app/controllers/stay/properties_controller.rb +15 -0
  99. data/app/controllers/stay/rooms_controller.rb +22 -0
  100. data/app/controllers/stay/users/confirmations_controller.rb +30 -0
  101. data/app/controllers/stay/users/omniauth_callbacks_controller.rb +30 -0
  102. data/app/controllers/stay/users/passwords_controller.rb +51 -0
  103. data/app/controllers/stay/users/registrations_controller.rb +62 -0
  104. data/app/controllers/stay/users/sessions_controller.rb +27 -0
  105. data/app/controllers/stay/users/unlocks_controller.rb +30 -0
  106. data/app/helpers/stay/admin/cities_helper.rb +4 -0
  107. data/app/helpers/stay/admin/countries_helper.rb +4 -0
  108. data/app/helpers/stay/admin/states_helper.rb +4 -0
  109. data/app/helpers/stay/admin/stores_helper.rb +25 -0
  110. data/app/helpers/stay/application_helper.rb +63 -0
  111. data/app/helpers/stay/currency_helper.rb +34 -0
  112. data/app/helpers/stay/locale_helper.rb +107 -0
  113. data/app/jobs/stay/application_job.rb +4 -0
  114. data/app/mailers/stay/application_mailer.rb +6 -0
  115. data/app/mailers/stay/user_mailer.rb +13 -0
  116. data/app/models/concerns/stay/translatable_resource.rb +25 -0
  117. data/app/models/stay/additional_rule.rb +5 -0
  118. data/app/models/stay/address.rb +40 -0
  119. data/app/models/stay/amenity.rb +13 -0
  120. data/app/models/stay/amenity_category.rb +6 -0
  121. data/app/models/stay/application_record.rb +5 -0
  122. data/app/models/stay/bed_type.rb +7 -0
  123. data/app/models/stay/booking.rb +199 -0
  124. data/app/models/stay/booking_query.rb +23 -0
  125. data/app/models/stay/cancellation_policy.rb +6 -0
  126. data/app/models/stay/chat.rb +23 -0
  127. data/app/models/stay/city.rb +9 -0
  128. data/app/models/stay/country.rb +23 -0
  129. data/app/models/stay/credit_card.rb +6 -0
  130. data/app/models/stay/discount.rb +4 -0
  131. data/app/models/stay/expense.rb +4 -0
  132. data/app/models/stay/feature.rb +11 -0
  133. data/app/models/stay/house_rule.rb +4 -0
  134. data/app/models/stay/invoice.rb +23 -0
  135. data/app/models/stay/line_item.rb +7 -0
  136. data/app/models/stay/message.rb +30 -0
  137. data/app/models/stay/payment.rb +54 -0
  138. data/app/models/stay/payment_method.rb +13 -0
  139. data/app/models/stay/price.rb +18 -0
  140. data/app/models/stay/property.rb +243 -0
  141. data/app/models/stay/property_amenity.rb +6 -0
  142. data/app/models/stay/property_category.rb +6 -0
  143. data/app/models/stay/property_feature.rb +6 -0
  144. data/app/models/stay/property_house_rule.rb +6 -0
  145. data/app/models/stay/property_tax.rb +6 -0
  146. data/app/models/stay/property_type.rb +15 -0
  147. data/app/models/stay/review.rb +5 -0
  148. data/app/models/stay/role.rb +11 -0
  149. data/app/models/stay/role_user.rb +6 -0
  150. data/app/models/stay/room.rb +93 -0
  151. data/app/models/stay/room_amenity.rb +6 -0
  152. data/app/models/stay/room_feature.rb +6 -0
  153. data/app/models/stay/room_type.rb +7 -0
  154. data/app/models/stay/state.rb +13 -0
  155. data/app/models/stay/store.rb +139 -0
  156. data/app/models/stay/store_property.rb +9 -0
  157. data/app/models/stay/tax.rb +8 -0
  158. data/app/models/stay/user.rb +111 -0
  159. data/app/models/stay/user_paypal.rb +5 -0
  160. data/app/serializers/amenity_category_serializer.rb +13 -0
  161. data/app/serializers/amenity_serializer.rb +3 -0
  162. data/app/serializers/bed_type_serializer.rb +5 -0
  163. data/app/serializers/booking_query_serializer.rb +6 -0
  164. data/app/serializers/booking_serializer.rb +68 -0
  165. data/app/serializers/cancellation_policy_serializer.rb +3 -0
  166. data/app/serializers/chat_serializer.rb +63 -0
  167. data/app/serializers/credit_card_serializer.rb +7 -0
  168. data/app/serializers/house_rules_serializer.rb +3 -0
  169. data/app/serializers/invoice_serializer.rb +41 -0
  170. data/app/serializers/last_5_messages_serializer.rb +7 -0
  171. data/app/serializers/line_item_serializer.rb +6 -0
  172. data/app/serializers/message_serializer.rb +44 -0
  173. data/app/serializers/property_category_serializer.rb +7 -0
  174. data/app/serializers/property_feature_serializer.rb +5 -0
  175. data/app/serializers/property_listing_serializer.rb +13 -0
  176. data/app/serializers/property_serializer.rb +77 -0
  177. data/app/serializers/property_type_serializer.rb +7 -0
  178. data/app/serializers/room_serializer.rb +36 -0
  179. data/app/serializers/room_type_serializer.rb +5 -0
  180. data/app/serializers/user_listing_serializer.rb +23 -0
  181. data/app/serializers/user_serializer.rb +41 -0
  182. data/app/services/stay/bookings/create_booking_service.rb +49 -0
  183. data/app/services/stay/bookings/create_line_item_service.rb +15 -0
  184. data/app/services/stay/bookings/existing_booking_service.rb +50 -0
  185. data/app/services/stay/chat/chat_messaging_service.rb +82 -0
  186. data/app/services/stay/chat/query_messaging_service.rb +56 -0
  187. data/app/services/stay/seeds/all.rb +27 -0
  188. data/app/services/stay/seeds/cancellation_policy.rb +21 -0
  189. data/app/services/stay/seeds/countries.rb +33 -0
  190. data/app/services/stay/seeds/roles.rb +9 -0
  191. data/app/services/stay/seeds/states.rb +63 -0
  192. data/app/services/stay/seeds/stores.rb +34 -0
  193. data/app/views/devise/mailer/reset_password_instructions.html.rb +10 -0
  194. data/app/views/kaminari/_first_page.html.erb +3 -0
  195. data/app/views/kaminari/_gap.html.erb +3 -0
  196. data/app/views/kaminari/_last_page.html.erb +3 -0
  197. data/app/views/kaminari/_next_page.html.erb +3 -0
  198. data/app/views/kaminari/_page.html.erb +9 -0
  199. data/app/views/kaminari/_paginator.html.erb +17 -0
  200. data/app/views/kaminari/_prev_page.html.erb +3 -0
  201. data/app/views/layouts/stay/admin.html.erb +45 -0
  202. data/app/views/layouts/stay/application.html.erb +34 -0
  203. data/app/views/stay/admin/addresses/_form.html.erb +49 -0
  204. data/app/views/stay/admin/addresses/edit.html.erb +5 -0
  205. data/app/views/stay/admin/addresses/index.html.erb +68 -0
  206. data/app/views/stay/admin/addresses/new.html.erb +5 -0
  207. data/app/views/stay/admin/addresses/show.html.erb +40 -0
  208. data/app/views/stay/admin/amenities/_form.html.erb +38 -0
  209. data/app/views/stay/admin/amenities/edit.html.erb +14 -0
  210. data/app/views/stay/admin/amenities/index.html.erb +58 -0
  211. data/app/views/stay/admin/amenities/new.html.erb +14 -0
  212. data/app/views/stay/admin/amenities/show.html.erb +19 -0
  213. data/app/views/stay/admin/amenity_categories/_form.html.erb +29 -0
  214. data/app/views/stay/admin/amenity_categories/edit.html.erb +14 -0
  215. data/app/views/stay/admin/amenity_categories/index.html.erb +53 -0
  216. data/app/views/stay/admin/amenity_categories/new.html.erb +14 -0
  217. data/app/views/stay/admin/amenity_categories/show.html.erb +18 -0
  218. data/app/views/stay/admin/bed_types/_form.html.erb +29 -0
  219. data/app/views/stay/admin/bed_types/edit.html.erb +14 -0
  220. data/app/views/stay/admin/bed_types/index.html.erb +56 -0
  221. data/app/views/stay/admin/bed_types/new.html.erb +14 -0
  222. data/app/views/stay/admin/bed_types/show.html.erb +0 -0
  223. data/app/views/stay/admin/bookings/_booking_invoice.html.erb +37 -0
  224. data/app/views/stay/admin/bookings/_booking_summary.html.erb +61 -0
  225. data/app/views/stay/admin/bookings/_form.html.erb +59 -0
  226. data/app/views/stay/admin/bookings/edit.html.erb +14 -0
  227. data/app/views/stay/admin/bookings/index.html.erb +74 -0
  228. data/app/views/stay/admin/bookings/new.html.erb +14 -0
  229. data/app/views/stay/admin/bookings/show.html.erb +80 -0
  230. data/app/views/stay/admin/cancellation_policies/_form.html.erb +38 -0
  231. data/app/views/stay/admin/cancellation_policies/edit.html.erb +14 -0
  232. data/app/views/stay/admin/cancellation_policies/index.html.erb +58 -0
  233. data/app/views/stay/admin/cancellation_policies/new.html.erb +14 -0
  234. data/app/views/stay/admin/cancellation_policies/show.html.erb +0 -0
  235. data/app/views/stay/admin/chats/_form.html.erb +31 -0
  236. data/app/views/stay/admin/chats/edit.html.erb +5 -0
  237. data/app/views/stay/admin/chats/index.html.erb +58 -0
  238. data/app/views/stay/admin/chats/new.html.erb +5 -0
  239. data/app/views/stay/admin/chats/show.html.erb +19 -0
  240. data/app/views/stay/admin/cities/_form.html.erb +30 -0
  241. data/app/views/stay/admin/cities/edit.html.erb +14 -0
  242. data/app/views/stay/admin/cities/index.html.erb +62 -0
  243. data/app/views/stay/admin/cities/new.html.erb +14 -0
  244. data/app/views/stay/admin/countries/_form.html.erb +64 -0
  245. data/app/views/stay/admin/countries/edit.html.erb +14 -0
  246. data/app/views/stay/admin/countries/index.html.erb +62 -0
  247. data/app/views/stay/admin/countries/new.html.erb +14 -0
  248. data/app/views/stay/admin/countries/show.html.erb +23 -0
  249. data/app/views/stay/admin/dashboard/index.html.erb +1 -0
  250. data/app/views/stay/admin/features/_form.html.erb +38 -0
  251. data/app/views/stay/admin/features/edit.html.erb +14 -0
  252. data/app/views/stay/admin/features/index.html.erb +58 -0
  253. data/app/views/stay/admin/features/new.html.erb +14 -0
  254. data/app/views/stay/admin/features/show.html.erb +19 -0
  255. data/app/views/stay/admin/house_rules/_form.html.erb +29 -0
  256. data/app/views/stay/admin/house_rules/edit.html.erb +14 -0
  257. data/app/views/stay/admin/house_rules/index.html.erb +54 -0
  258. data/app/views/stay/admin/house_rules/new.html.erb +14 -0
  259. data/app/views/stay/admin/house_rules/show.html.erb +18 -0
  260. data/app/views/stay/admin/mailer/confirmation_instructions.html.erb +5 -0
  261. data/app/views/stay/admin/mailer/email_changed.html.erb +7 -0
  262. data/app/views/stay/admin/mailer/password_change.html.erb +3 -0
  263. data/app/views/stay/admin/mailer/reset_password_instructions.html.erb +7 -0
  264. data/app/views/stay/admin/messages/_form.html.erb +36 -0
  265. data/app/views/stay/admin/messages/edit.html.erb +5 -0
  266. data/app/views/stay/admin/messages/index.html.erb +60 -0
  267. data/app/views/stay/admin/messages/new.html.erb +5 -0
  268. data/app/views/stay/admin/messages/show.html.erb +24 -0
  269. data/app/views/stay/admin/passwords/edit.html.erb +37 -0
  270. data/app/views/stay/admin/passwords/new.html.erb +28 -0
  271. data/app/views/stay/admin/payment_methods/_form.html.erb +22 -0
  272. data/app/views/stay/admin/payment_methods/edit.html.erb +14 -0
  273. data/app/views/stay/admin/payment_methods/index.html.erb +54 -0
  274. data/app/views/stay/admin/payment_methods/new.html.erb +14 -0
  275. data/app/views/stay/admin/payment_methods/show.html.erb +18 -0
  276. data/app/views/stay/admin/payments/_form.html.erb +36 -0
  277. data/app/views/stay/admin/payments/edit.html.erb +5 -0
  278. data/app/views/stay/admin/payments/index.html.erb +60 -0
  279. data/app/views/stay/admin/payments/new.html.erb +5 -0
  280. data/app/views/stay/admin/payments/show.html.erb +24 -0
  281. data/app/views/stay/admin/properties/_amenities.html.erb +38 -0
  282. data/app/views/stay/admin/properties/_calendar.html.erb +37 -0
  283. data/app/views/stay/admin/properties/_description.html.erb +126 -0
  284. data/app/views/stay/admin/properties/_details.html.erb +75 -0
  285. data/app/views/stay/admin/properties/_features.html.erb +39 -0
  286. data/app/views/stay/admin/properties/_form.html.erb +118 -0
  287. data/app/views/stay/admin/properties/_images.html.erb +48 -0
  288. data/app/views/stay/admin/properties/_location.html.erb +55 -0
  289. data/app/views/stay/admin/properties/_price.html.erb +88 -0
  290. data/app/views/stay/admin/properties/_sidebar.html.erb +47 -0
  291. data/app/views/stay/admin/properties/edit.html.erb +17 -0
  292. data/app/views/stay/admin/properties/index.html.erb +82 -0
  293. data/app/views/stay/admin/properties/new.html.erb +14 -0
  294. data/app/views/stay/admin/properties/show.html.erb +149 -0
  295. data/app/views/stay/admin/property_categories/_form.html.erb +29 -0
  296. data/app/views/stay/admin/property_categories/edit.html.erb +14 -0
  297. data/app/views/stay/admin/property_categories/index.html.erb +57 -0
  298. data/app/views/stay/admin/property_categories/new.html.erb +14 -0
  299. data/app/views/stay/admin/property_categories/show.html.erb +18 -0
  300. data/app/views/stay/admin/property_types/_form.html.erb +29 -0
  301. data/app/views/stay/admin/property_types/edit.html.erb +14 -0
  302. data/app/views/stay/admin/property_types/index.html.erb +56 -0
  303. data/app/views/stay/admin/property_types/new.html.erb +14 -0
  304. data/app/views/stay/admin/property_types/show.html.erb +18 -0
  305. data/app/views/stay/admin/registrations/edit.html.erb +43 -0
  306. data/app/views/stay/admin/registrations/new.html.erb +35 -0
  307. data/app/views/stay/admin/reviews/_form.html.erb +37 -0
  308. data/app/views/stay/admin/reviews/edit.html.erb +14 -0
  309. data/app/views/stay/admin/reviews/index.html.erb +58 -0
  310. data/app/views/stay/admin/reviews/new.html.erb +14 -0
  311. data/app/views/stay/admin/reviews/show.html.erb +20 -0
  312. data/app/views/stay/admin/roles/_form.html.erb +24 -0
  313. data/app/views/stay/admin/roles/edit.html.erb +14 -0
  314. data/app/views/stay/admin/roles/index.html.erb +55 -0
  315. data/app/views/stay/admin/roles/new.html.erb +14 -0
  316. data/app/views/stay/admin/roles/show.html.erb +18 -0
  317. data/app/views/stay/admin/room_types/_form.html.erb +37 -0
  318. data/app/views/stay/admin/room_types/edit.html.erb +14 -0
  319. data/app/views/stay/admin/room_types/index.html.erb +58 -0
  320. data/app/views/stay/admin/room_types/new.html.erb +14 -0
  321. data/app/views/stay/admin/room_types/show.html.erb +19 -0
  322. data/app/views/stay/admin/rooms/_form.html.erb +136 -0
  323. data/app/views/stay/admin/rooms/edit.html.erb +14 -0
  324. data/app/views/stay/admin/rooms/index.html.erb +69 -0
  325. data/app/views/stay/admin/rooms/new.html.erb +14 -0
  326. data/app/views/stay/admin/rooms/show.html.erb +25 -0
  327. data/app/views/stay/admin/sessions/new.html.erb +36 -0
  328. data/app/views/stay/admin/shared/_common-table.html.erb +334 -0
  329. data/app/views/stay/admin/shared/_error_messages.html.erb +15 -0
  330. data/app/views/stay/admin/shared/_header.html.erb +55 -0
  331. data/app/views/stay/admin/shared/_links.html.erb +15 -0
  332. data/app/views/stay/admin/shared/_sidebar.html.erb +154 -0
  333. data/app/views/stay/admin/shared/_store_switcher.html.erb +30 -0
  334. data/app/views/stay/admin/states/_form.html.erb +39 -0
  335. data/app/views/stay/admin/states/edit.html.erb +14 -0
  336. data/app/views/stay/admin/states/index.html.erb +61 -0
  337. data/app/views/stay/admin/states/new.html.erb +14 -0
  338. data/app/views/stay/admin/states/show.html.erb +0 -0
  339. data/app/views/stay/admin/stores/_form.html.erb +30 -0
  340. data/app/views/stay/admin/stores/_tabs.html.erb +17 -0
  341. data/app/views/stay/admin/stores/edit.html.erb +14 -0
  342. data/app/views/stay/admin/stores/form/_basic.html.erb +35 -0
  343. data/app/views/stay/admin/stores/form/_emails.html.erb +34 -0
  344. data/app/views/stay/admin/stores/form/_footer.html.erb +28 -0
  345. data/app/views/stay/admin/stores/form/_internationalization.html.erb +52 -0
  346. data/app/views/stay/admin/stores/form/_seo.html.erb +42 -0
  347. data/app/views/stay/admin/stores/form/_social.html.erb +34 -0
  348. data/app/views/stay/admin/stores/index.html.erb +66 -0
  349. data/app/views/stay/admin/stores/new.html.erb +14 -0
  350. data/app/views/stay/admin/stores/translations.html.erb +8 -0
  351. data/app/views/stay/admin/taxes/_form.html.erb +29 -0
  352. data/app/views/stay/admin/taxes/edit.html.erb +14 -0
  353. data/app/views/stay/admin/taxes/index.html.erb +56 -0
  354. data/app/views/stay/admin/taxes/new.html.erb +14 -0
  355. data/app/views/stay/admin/taxes/show.html.erb +18 -0
  356. data/app/views/stay/admin/users/_address.html.erb +57 -0
  357. data/app/views/stay/admin/users/_address_form.html.erb +29 -0
  358. data/app/views/stay/admin/users/_form.html.erb +73 -0
  359. data/app/views/stay/admin/users/_sidebar.html.erb +12 -0
  360. data/app/views/stay/admin/users/addresses.html.erb +17 -0
  361. data/app/views/stay/admin/users/edit.html.erb +17 -0
  362. data/app/views/stay/admin/users/index.html.erb +74 -0
  363. data/app/views/stay/admin/users/new.html.erb +14 -0
  364. data/app/views/stay/admin/users/show.html.erb +23 -0
  365. data/app/views/stay/bookings/new.html.erb +92 -0
  366. data/app/views/stay/payments/_payment.js.erb +127 -0
  367. data/app/views/stay/payments/new.html.erb +187 -0
  368. data/app/views/stay/properties/index.html.erb +56 -0
  369. data/app/views/stay/properties/show.html.erb +137 -0
  370. data/app/views/stay/shared/_navbar.html.erb +35 -0
  371. data/app/views/stay/user_mailer/new_host_signup.html.erb +12 -0
  372. data/app/views/stay/user_mailer/welcome_email.html.erb +9 -0
  373. data/app/views/stay/users/confirmations/new.html.erb +16 -0
  374. data/app/views/stay/users/mailer/confirmation_instructions.html.erb +5 -0
  375. data/app/views/stay/users/mailer/email_changed.html.erb +7 -0
  376. data/app/views/stay/users/mailer/password_change.html.erb +3 -0
  377. data/app/views/stay/users/mailer/reset_password_instructions.html.erb +7 -0
  378. data/app/views/stay/users/mailer/unlock_instructions.html.erb +7 -0
  379. data/app/views/stay/users/passwords/edit.html.erb +37 -0
  380. data/app/views/stay/users/passwords/new.html.erb +28 -0
  381. data/app/views/stay/users/registrations/edit.html.erb +43 -0
  382. data/app/views/stay/users/registrations/new.html.erb +42 -0
  383. data/app/views/stay/users/sessions/new.html.erb +43 -0
  384. data/app/views/stay/users/shared/_error_messages.html.erb +15 -0
  385. data/app/views/stay/users/shared/_links.html.erb +25 -0
  386. data/app/views/stay/users/unlocks/new.html.erb +16 -0
  387. data/config/initializers/devise.rb +358 -0
  388. data/config/initializers/friendly_id.rb +107 -0
  389. data/config/initializers/geocoder.rb +4 -0
  390. data/config/initializers/kaminari_config.rb +14 -0
  391. data/config/initializers/mobility.rb +17 -0
  392. data/config/initializers/stripe.rb +8 -0
  393. data/config/locales/devise.en.yml +65 -0
  394. data/config/locales/devise_api.en.yml +23 -0
  395. data/config/locales/en.yml +38 -0
  396. data/config/routes.rb +187 -0
  397. data/db/migrate/20240912073149_devise_create_stay_users.rb +50 -0
  398. data/db/migrate/20240912073248_create_devise_api_tables.rb +29 -0
  399. data/db/migrate/20240912092842_create_stay_roles.rb +9 -0
  400. data/db/migrate/20240912093003_create_stay_role_users.rb +10 -0
  401. data/db/migrate/20240912093151_create_stay_properties.rb +16 -0
  402. data/db/migrate/20240912093213_create_stay_rooms.rb +12 -0
  403. data/db/migrate/20240912093240_create_stay_bookings.rb +15 -0
  404. data/db/migrate/20240912093346_create_stay_payment_methods.rb +9 -0
  405. data/db/migrate/20240912093408_create_stay_payments.rb +12 -0
  406. data/db/migrate/20240912093603_create_stay_reviews.rb +11 -0
  407. data/db/migrate/20240912093644_create_stay_chats.rb +11 -0
  408. data/db/migrate/20240912093717_create_stay_messages.rb +12 -0
  409. data/db/migrate/20240912093759_create_stay_countries.rb +14 -0
  410. data/db/migrate/20240912093824_create_stay_states.rb +11 -0
  411. data/db/migrate/20240912093903_create_stay_cities.rb +10 -0
  412. data/db/migrate/20240912094149_create_stay_addresses.rb +19 -0
  413. data/db/migrate/20240916071141_create_active_storage_tables.rb +57 -0
  414. data/db/migrate/20240918080551_add_columns_to_stay_bookings.rb +12 -0
  415. data/db/migrate/20240918101502_add_columns_to_stay_payments.rb +16 -0
  416. data/db/migrate/20240918110958_create_stay_prices.rb +13 -0
  417. data/db/migrate/20240918112045_add_columns_to_stay_rooms.rb +5 -0
  418. data/db/migrate/20240918114609_add_payment_intent_in_bookings.rb +6 -0
  419. data/db/migrate/20240919045205_add_user_to_stay_addresses.rb +5 -0
  420. data/db/migrate/20240919063400_create_stay_room_types.rb +10 -0
  421. data/db/migrate/20240919070423_rename_room_type_to_room_type_id_in_stay_rooms.rb +5 -0
  422. data/db/migrate/20240919071642_rename_status_to_state_in_stay_payments.rb +5 -0
  423. data/db/migrate/20240919100508_remove_room_id_from_stay_properties.rb +5 -0
  424. data/db/migrate/20240919111801_create_stay_line_items.rb +16 -0
  425. data/db/migrate/20240919124704_remove_room_id_from_stay_bookings.rb +5 -0
  426. data/db/migrate/20240924053658_add_is_master_in_stay_rooms.rb +5 -0
  427. data/db/migrate/20240925063733_add_zipcode_required_in_countries.rb +5 -0
  428. data/db/migrate/20240926055951_remove_city_reference_from_stay_addresses.rb +5 -0
  429. data/db/migrate/20240927055935_change_price_per_night_to_decimal_in_stay_rooms.rb +5 -0
  430. data/db/migrate/20240927063023_add_first_name_to_stay_addresses.rb +8 -0
  431. data/db/migrate/20240927070538_change_description_to_text_in_stay_properties.rb +6 -0
  432. data/db/migrate/20240927111110_create_stay_stores.rb +33 -0
  433. data/db/migrate/20240930095921_create_stay_cancellation_policies.rb +9 -0
  434. data/db/migrate/20240930100157_add_cancellation_id_to_stay_properties.rb +5 -0
  435. data/db/migrate/20240930152331_create_stay_store_translations.rb +22 -0
  436. data/db/migrate/20240930162337_create_stay_store_properties.rb +10 -0
  437. data/db/migrate/20241001101816_create_stay_property_categories.rb +8 -0
  438. data/db/migrate/20241001102646_add_property_category_id_to_property.rb +5 -0
  439. data/db/migrate/20241001103758_create_stay_property_types.rb +9 -0
  440. data/db/migrate/20241001111306_add_store_id_to_stay_bookings.rb +5 -0
  441. data/db/migrate/20241001160330_add_deleted_at_to_stay_store_translations.rb +5 -0
  442. data/db/migrate/20241004061647_add_field_to_stay_properties.rb +20 -0
  443. data/db/migrate/20241004062057_create_stay_bed_types.rb +9 -0
  444. data/db/migrate/20241004062251_add_details_to_stay_rooms.rb +9 -0
  445. data/db/migrate/20241004070456_add_property_type_id_to_stay_property_categories.rb +6 -0
  446. data/db/migrate/20241004105821_create_stay_amenity_categories.rb +8 -0
  447. data/db/migrate/20241004112600_create_stay_amenities.rb +9 -0
  448. data/db/migrate/20241004115207_create_stay_house_rules.rb +8 -0
  449. data/db/migrate/20241004115317_create_stay_additional_rules.rb +9 -0
  450. data/db/migrate/20241004115608_create_stay_property_amenities.rb +9 -0
  451. data/db/migrate/20241004115615_create_stay_room_amenities.rb +9 -0
  452. data/db/migrate/20241007051646_add_preferences_to_stay_users.rb +5 -0
  453. data/db/migrate/20241007165530_create_stay_creates.rb +10 -0
  454. data/db/migrate/20241008111346_add_deleted_at_to_stay_users.rb +7 -0
  455. data/db/migrate/20241008151958_add_price_per_night_in_stay_properties.rb +5 -0
  456. data/db/migrate/20241010071855_add_zipcode_to_stay_property.rb +8 -0
  457. data/db/migrate/20241014085441_add_amenity_type_in_amenities.rb +5 -0
  458. data/db/migrate/20241015055940_create_stay_features.rb +10 -0
  459. data/db/migrate/20241015060804_create_stay_property_features.rb +9 -0
  460. data/db/migrate/20241015060854_create_stay_room_features.rb +9 -0
  461. data/db/migrate/20241016055948_create_stay_tax.rb +9 -0
  462. data/db/migrate/20241016060354_create_stay_property_tax.rb +11 -0
  463. data/db/migrate/20241016091120_remove_column_from_stay_properties_categories.rb +6 -0
  464. data/db/migrate/20241016091829_add_column_to_stay_property.rb +5 -0
  465. data/db/migrate/20241016112707_add_country_to_stay_properties.rb +6 -0
  466. data/db/migrate/20241016153847_add_state_to_properties.rb +7 -0
  467. data/db/migrate/20241016170528_up_date_price_column_in_stay_property.rb +5 -0
  468. data/db/migrate/20241017031520_add_new_field_to_stay_properties.rb +8 -0
  469. data/db/migrate/20241017051450_add_column_to_stay_chat.rb +7 -0
  470. data/db/migrate/20241018090158_create_stay_stay_credit_cards.rb +16 -0
  471. data/db/migrate/20241021090444_create_stay_stay_user_paypals.rb +9 -0
  472. data/db/migrate/20241024065949_add_stay_property_to_stay_b0okings.rb +5 -0
  473. data/db/migrate/20241027062126_add_events_to_stay_messages.rb +8 -0
  474. data/db/migrate/20241028105007_create_stay_invoices.rb +15 -0
  475. data/db/migrate/20241028105323_create_stay_expenses.rb +10 -0
  476. data/db/migrate/20241028105331_create_stay_discounts.rb +10 -0
  477. data/db/migrate/20241101044618_create_stay_booking_queries.rb +17 -0
  478. data/db/migrate/20241101093057_add_read_at_to_stay_messages.rb +5 -0
  479. data/db/migrate/20241113061248_modify_cancellation_policy_on_properties.rb +7 -0
  480. data/db/migrate/20241118064033_rename_columns_in_stay_properties.rb +7 -0
  481. data/db/migrate/20241127060541_add_new_fields_to_property.rb +11 -0
  482. data/db/migrate/20241128111832_add_name_to_stay_rooms.rb +5 -0
  483. data/db/migrate/20241129063922_change_amenity_category_null_on_stay_amenities.rb +5 -0
  484. data/db/migrate/20241205092935_add_slug_to_stay_properties.rb +6 -0
  485. data/db/migrate/20241205092943_create_friendly_id_slugs.rb +21 -0
  486. data/db/migrate/20241217044817_add_deleted_atto_stay_property.rb +6 -0
  487. data/db/migrate/20241230064432_modify_property_category_on_properties.rb +7 -0
  488. data/db/seeds.rb +1 -0
  489. data/lib/stay/controller_helpers/currency.rb +54 -0
  490. data/lib/stay/controller_helpers/store.rb +37 -0
  491. data/lib/stay/engine.rb +16 -0
  492. data/lib/stay/money.rb +77 -0
  493. data/lib/stay/version.rb +3 -0
  494. data/lib/stay.rb +16 -0
  495. data/lib/tasks/stay_tasks.rake +4 -0
  496. metadata +967 -0
@@ -0,0 +1,54 @@
1
+ module Stay
2
+ class Payment < ApplicationRecord
3
+ PAYMENT_STATES = %w(failed paid)
4
+
5
+ belongs_to :booking, class_name: 'Stay::Booking'
6
+ belongs_to :payment_method, class_name: 'Stay::PaymentMethod'
7
+
8
+ validates :payment_method, presence: true
9
+ validates :amount, numericality: true
10
+
11
+ scope :with_state, ->(s) { where(state: s.to_s) }
12
+ scope :completed, -> { with_state('completed') }
13
+ scope :pending, -> { with_state('pending') }
14
+ scope :processing, -> { with_state('processing') }
15
+ scope :failed, -> { with_state('failed') }
16
+
17
+ # state_machine initial: :checkout do
18
+ # # to complete
19
+ # event :started_processing do
20
+ # transition from: [:checkout, :pending, :completed, :processing], to: :processing
21
+ # end
22
+ # # When processing during checkout fails
23
+ # event :failure do
24
+ # transition from: [:pending, :processing], to: :failed
25
+ # end
26
+ # # With card payments this represents authorizing the payment
27
+ # event :pend do
28
+ # transition from: [:checkout, :processing], to: :pending
29
+ # end
30
+ # # With card payments this represents completing a purchase or capture transaction
31
+ # event :complete do
32
+ # transition from: [:processing, :pending, :checkout], to: :completed
33
+ # end
34
+ # after_transition to: :completed, do: :after_completed
35
+ # event :void do
36
+ # transition from: [:pending, :processing, :completed, :checkout], to: :void
37
+ # end
38
+ # after_transition to: :void, do: :after_void
39
+ # # when the card brand isn't supported
40
+ # event :invalidate do
41
+ # transition from: [:checkout], to: :invalid
42
+ # end
43
+
44
+ # after_transition do |payment, transition|
45
+ # payment.state_changes.create!(
46
+ # previous_state: transition.from,
47
+ # next_state: transition.to,
48
+ # name: 'payment'
49
+ # )
50
+ # end
51
+ # end
52
+
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ module Stay
2
+ class PaymentMethod < ApplicationRecord
3
+ DISPLAY = [ :both, :front_end, :back_end ].freeze
4
+
5
+ has_many :payments, class_name: "Stay::Payment", dependent: :destroy
6
+ validates :name, presence: true, uniqueness: { case_sensitive: false }
7
+
8
+ scope :active, -> { where(active: true).order(position: :asc) }
9
+ scope :available, -> { active.where(display_on: [ :front_end, :back_end, :both ]) }
10
+ scope :available_on_front_end, -> { active.where(display_on: [ :front_end, :both ]) }
11
+ scope :available_on_back_end, -> { active.where(display_on: [ :back_end, :both ]) }
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module Stay
2
+ class Price < ApplicationRecord
3
+ MAXIMUM_AMOUNT = BigDecimal('99_999_999.99')
4
+
5
+ belongs_to :room, class_name: 'Stay::Room'
6
+ before_validation :ensure_currency
7
+ validates :amount, allow_nil: true, numericality: {
8
+ greater_than_or_equal_to: 0,
9
+ less_than_or_equal_to: MAXIMUM_AMOUNT
10
+ }
11
+
12
+ private
13
+
14
+ def ensure_currency
15
+ self.currency ||= Stay::Store.default.default_currency
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,243 @@
1
+ module Stay
2
+ class Property < ApplicationRecord
3
+ ACTIVE_STATUS = "active".freeze
4
+ APPROVED = "approved".freeze
5
+ include Rails.application.routes.url_helpers
6
+ include CurrencyHelper
7
+ include Stay::ControllerHelpers::Currency
8
+ include Stay::ControllerHelpers::Store
9
+ extend FriendlyId
10
+ acts_as_paranoid
11
+
12
+ friendly_id :title, use: :slugged
13
+
14
+ validates :title, presence: true, uniqueness: { case_sensitive: false, error: "Title has already been taken" }
15
+ validate :availability_dates_are_valid
16
+ has_one :master, -> { where is_master: true }, class_name: "Stay::Room", dependent: :destroy
17
+ has_many :rooms, -> { where(status: ACTIVE_STATUS) }, class_name: "Stay::Room", dependent: :destroy
18
+ has_many :rooms_including_master,
19
+ inverse_of: :property,
20
+ class_name: "Stay::Room",
21
+ dependent: :destroy
22
+
23
+ belongs_to :cancellation_policy, class_name: "Stay::CancellationPolicy", optional: :true
24
+ has_many :chats, class_name: "Stay::Chat", dependent: :destroy
25
+
26
+ belongs_to :user, class_name: "Stay::User", optional: true
27
+ # belongs_to :address, class_name: 'Stay::Address', optional: true
28
+
29
+ has_many :prices, through: :rooms
30
+ has_many :line_items, through: :variants_including_master
31
+ has_many :bookings
32
+ belongs_to :property_category, class_name: "Stay::PropertyCategory", optional: true
33
+ belongs_to :property_type, class_name: "Stay::PropertyType"
34
+ has_many :property_amenities, class_name: "Stay::PropertyAmenity", dependent: :destroy
35
+ has_many :amenities, through: :property_amenities, class_name: "Stay::Amenity"
36
+ has_many :additional_rules, class_name: "Stay::AdditionalRule", dependent: :destroy
37
+ has_many :property_house_rules, class_name: "Stay::PropertyHouseRule", dependent: :destroy
38
+ has_many :house_rules, through: :property_house_rules, class_name: "Stay::HouseRule"
39
+ has_many :property_features, class_name: "Stay::PropertyFeature", dependent: :destroy
40
+ has_many :features, through: :property_features, class_name: "Stay::Feature"
41
+ has_many :property_taxes, class_name: "Stay::PropertyTax", dependent: :destroy
42
+ has_many :taxes, through: :property_taxes, class_name: "Stay::Tax"
43
+ has_many :store_properties, class_name: "Stay::StoreProperty", dependent: :destroy
44
+ has_many :stores, through: :store_properties, class_name: "Stay::Store"
45
+
46
+ # images
47
+ has_one_attached :cover_image
48
+ has_many_attached :place_images
49
+
50
+ # nested_attributes
51
+ accepts_nested_attributes_for :property_amenities, allow_destroy: true
52
+ accepts_nested_attributes_for :additional_rules, allow_destroy: true
53
+ accepts_nested_attributes_for :rooms, allow_destroy: true
54
+ accepts_nested_attributes_for :property_house_rules, allow_destroy: true
55
+ accepts_nested_attributes_for :property_features, allow_destroy: true
56
+ accepts_nested_attributes_for :property_taxes, allow_destroy: true
57
+
58
+ after_restore :restore_associated_rooms
59
+ after_restore :restore_active_storage_files
60
+ after_create :create_default_room
61
+ after_update :update_prices
62
+ after_create :create_store_property
63
+ geocoded_by :combine_address
64
+ after_validation :geocode
65
+
66
+ # scopes
67
+ scope :approved, -> { where(property_state: APPROVED) }
68
+ scope :active, -> { where(active: true) }
69
+ scope :similar_properties, ->(type_id, category_id, property_id) {
70
+ where(property_type_id: type_id, property_category_id: category_id)
71
+ .where.not(id: property_id)
72
+ }
73
+
74
+ scope :with_features, ->(feature_ids) {
75
+ joins(:features).where(stay_features: { id: feature_ids }).distinct
76
+ }
77
+
78
+ scope :with_amenities, ->(amenity_ids) {
79
+ joins(:amenities).where(stay_amenities: { id: amenity_ids }).distinct
80
+ }
81
+
82
+ scope :nearby, ->(latitude, longitude, distance) {
83
+ near([ latitude, longitude ], distance)
84
+ }
85
+
86
+ scope :by_property_type, ->(property_type_id) {
87
+ joins(:property_type).where(property_type: { id: property_type_id })
88
+ }
89
+
90
+ scope :price_filter, ->(min_price, max_price) {
91
+ joins(:rooms).where(stay_rooms: { price_per_month: min_price..max_price })
92
+ }
93
+
94
+ scope :by_property_category, ->(property_category_id) {
95
+ joins(:property_category).where(property_category: { id: property_category_id })
96
+ }
97
+
98
+ def id_with_title
99
+ [
100
+ truncated_title,
101
+ [ truncated_title, truncated_description ]
102
+ ]
103
+ end
104
+
105
+ def truncated_title
106
+ title.split[0..9].join(" ")
107
+ end
108
+
109
+ def truncated_description
110
+ description.split[0..5].join(" ")
111
+ end
112
+
113
+ def should_generate_new_friendly_id?
114
+ title_changed?
115
+ end
116
+
117
+ def combine_address
118
+ [ address, city, state, country ].compact.join(" ")
119
+ end
120
+
121
+ state_machine :property_state, initial: :waiting_for_approval do
122
+ state :waiting_for_approval
123
+ state :approved
124
+ state :rejected
125
+
126
+ event :approve do
127
+ transition waiting_for_approval: :approved
128
+ end
129
+
130
+ event :reject do
131
+ transition waiting_for_approval: :rejected
132
+ end
133
+
134
+ event :resubmit do
135
+ transition rejected: :waiting_for_approval
136
+ end
137
+ end
138
+
139
+ def self.ransackable_scopes(auth_object = nil)
140
+ %i[with_amenities nearby by_property_type]
141
+ end
142
+
143
+ def self.ransackable_attributes(auth_object = nil)
144
+ %w[active address availability_end availability_start title extra_guest total_rooms total_bathrooms latitude longitude total_bedrooms guest_number]
145
+ end
146
+
147
+ def self.ransackable_associations(auth_object = nil)
148
+ %w[rooms amenities property_type]
149
+ end
150
+
151
+ def shared_property
152
+ property_type.present? ? property_type.name.casecmp?("shared property") : false
153
+ end
154
+
155
+ def has_rooms?
156
+ rooms.any?
157
+ end
158
+
159
+ def deleted?
160
+ !!deleted_at
161
+ end
162
+
163
+ def average_rating
164
+ all_reviews = Stay::Review.joins(booking: { rooms: :property }).where(stay_properties: { id: self.id })
165
+
166
+ return 0 if all_reviews.empty?
167
+ total_rating = all_reviews.sum(:rating)
168
+ review_count = all_reviews.count
169
+
170
+ (total_rating.to_f / review_count).round(2)
171
+ end
172
+
173
+ def reviews_count
174
+ Stay::Review.joins(booking: { rooms: :property }).where(stay_properties: { id: self.id }).count
175
+ end
176
+
177
+ def default_room
178
+ master
179
+ end
180
+
181
+ def price
182
+ master&.price_per_month.to_f || 0
183
+ end
184
+
185
+ def place_images_urls
186
+ place_images.map { |image| image.url }
187
+ end
188
+
189
+ def cover_image_url
190
+ cover_image.attached? ? cover_image.url : nil
191
+ end
192
+
193
+ def approved
194
+ transaction do
195
+ approve!
196
+ end
197
+ end
198
+
199
+ def rejected
200
+ transaction do
201
+ reject!
202
+ end
203
+ end
204
+
205
+ def resubmited
206
+ transaction do
207
+ resubmit!
208
+ end
209
+ end
210
+
211
+ private
212
+
213
+ def restore_active_storage_files
214
+ place_images.each do |image|
215
+ Rails.logger.error "Missing file for #{image.filename}" unless File.exist?(ActiveStorage::Blob.service.path_for(image.blob.key))
216
+ end
217
+ end
218
+
219
+ def restore_associated_rooms
220
+ rooms.only_deleted.each(&:restore)
221
+ end
222
+
223
+ def availability_dates_are_valid
224
+ if availability_start && availability_end && availability_start >= availability_end
225
+ errors.add(:availability_end, "must be after availability start date")
226
+ end
227
+ end
228
+
229
+ def create_store_property
230
+ StoreProperty.create(store_id: current_store.id, property_id: self.id)
231
+ end
232
+
233
+ def create_default_room
234
+ return unless Stay::RoomType.first.present?
235
+ master_room = rooms.create!(is_master: true, property_id: self.id, max_guests: 2, price_per_month: price_per_month, room_type_id: Stay::RoomType.first&.id, status: "active")
236
+ master_room.prices.create(amount: master_room.price_per_month, currency: Stay::Store.default.default_currency)
237
+ end
238
+
239
+ def update_prices
240
+ master&.prices&.update(amount: master.price_per_month, currency: Stay::Store.default.default_currency)
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,6 @@
1
+ module Stay
2
+ class PropertyAmenity < ApplicationRecord
3
+ belongs_to :property, class_name: "Stay::Property"
4
+ belongs_to :amenity, class_name: "Stay::Amenity"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Stay
2
+ class PropertyCategory < ApplicationRecord
3
+ has_many :properties, class_name: "Stay::Property", dependent: :nullify
4
+ validates :name, presence: true, uniqueness: { case_sensitive: false }, format: { without: /\s/, message: "must contain no spaces" }
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Stay
2
+ class PropertyFeature < ApplicationRecord
3
+ belongs_to :property, class_name: "Stay::Property"
4
+ belongs_to :feature, class_name: "Stay::Feature"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Stay
2
+ class PropertyHouseRule < ApplicationRecord
3
+ belongs_to :property
4
+ belongs_to :house_rule
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Stay
2
+ class PropertyTax < ApplicationRecord
3
+ belongs_to :tax, class_name: 'Stay::Tax', touch: true
4
+ belongs_to :property, class_name: 'Stay::Property', touch: true
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ module Stay
2
+ class PropertyType < ApplicationRecord
3
+ validates :name, presence: true, uniqueness: { case_sensitive: false }, format: { without: /\s/, error: "must contain no spaces" }
4
+
5
+
6
+ has_many :properties
7
+ def self.ransackable_attributes(auth_object = nil)
8
+ %w[id name created_at updated_at]
9
+ end
10
+
11
+ def self.ransackable_associations(auth_object = nil)
12
+ []
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Stay
2
+ class Review < ApplicationRecord
3
+ belongs_to :booking
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Stay
2
+ class Role < ApplicationRecord
3
+ has_many :role_users, class_name: "Stay::RoleUser", dependent: :destroy
4
+ has_many :users, through: :role_users, class_name: "Stay::User"
5
+ validates :name, presence: true, uniqueness: { case_sensitive: false }
6
+
7
+ USER = "student".freeze
8
+ ADMIN = "admin".freeze
9
+ HOST = "host".freeze
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module Stay
2
+ class RoleUser < ApplicationRecord
3
+ belongs_to :user, class_name: 'Stay::User'
4
+ belongs_to :role, class_name: 'Stay::Role'
5
+ end
6
+ end
@@ -0,0 +1,93 @@
1
+ module Stay
2
+ class Room < ApplicationRecord
3
+ include CurrencyHelper
4
+ STATUSES = %w[active inactive].freeze
5
+ acts_as_paranoid
6
+ after_initialize :set_default_price, if: :new_record?
7
+
8
+ # validations
9
+ validates :status, presence: true, inclusion: { in: STATUSES, message: "%{value} is not a valid status" }
10
+ validate :booking_dates_are_valid
11
+ validate :room_count_limit, on: :create
12
+
13
+ # associations
14
+ belongs_to :property, class_name: "Stay::Property"
15
+ belongs_to :room_type, class_name: "Stay::RoomType"
16
+ belongs_to :bed_type, class_name: "Stay::BedType", optional: :true
17
+ has_many :bookings, class_name: "Stay::Booking"
18
+ has_many_attached :room_images
19
+ has_many :line_items, class_name: "Stay::LineItem"
20
+ has_many :prices,
21
+ class_name: "Stay::Price",
22
+ dependent: :destroy
23
+
24
+ has_many :room_features, class_name: "Stay::RoomFeature", dependent: :destroy
25
+ has_many :features, through: :room_features, class_name: "Stay::Feature"
26
+
27
+ has_many :room_amenities, class_name: "Stay::RoomAmenity", dependent: :destroy
28
+ has_many :amenities, through: :room_amenities, class_name: "Stay::Amenity"
29
+
30
+ # nested attributes
31
+ accepts_nested_attributes_for :room_features, allow_destroy: true
32
+ accepts_nested_attributes_for :room_amenities, allow_destroy: true
33
+
34
+ after_create :set_price
35
+ after_update :update_price, if: :saved_change_to_price_per_month?
36
+
37
+ # state_machine :status, initial: :active do
38
+ # state :active
39
+ # state :booked
40
+ # state :inactive
41
+
42
+ # event :book do
43
+ # transition active: :booked
44
+ # end
45
+
46
+ # event :deactivate do
47
+ # transition [:booked, :active] => :inactive
48
+ # end
49
+
50
+ # event :activate do
51
+ # transition inactive: :active
52
+ # end
53
+ # end
54
+
55
+
56
+ def price
57
+ price_per_month
58
+ end
59
+
60
+ def images_urls
61
+ room_images.map { |image| image.url }
62
+ end
63
+
64
+ def set_default_price
65
+ self.price_per_month ||= 0
66
+ end
67
+
68
+ private
69
+
70
+ def booking_dates_are_valid
71
+ if booking_start && booking_end && booking_start >= booking_end
72
+ errors.add(:booking_end, "must be after start date")
73
+ end
74
+ end
75
+
76
+ def room_count_limit
77
+ return if property.rooms.count == 0
78
+ if property.rooms.count >= property.total_rooms
79
+ errors.add(:base, "You cannot create more rooms than the property's total room count.")
80
+ end
81
+ end
82
+
83
+ def set_price
84
+ prices.create(amount: price_per_month, currency: Stay::Store.default.default_currency) if price_per_month.present? && !is_master
85
+ end
86
+
87
+ def update_price
88
+ price = prices.find_or_initialize_by(currency: Stay::Store.default.default_currency)
89
+ price.amount = price_per_month
90
+ price.save
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,6 @@
1
+ module Stay
2
+ class RoomAmenity < ApplicationRecord
3
+ belongs_to :room, class_name: "Stay::Room"
4
+ belongs_to :amenity, class_name: "Stay::Amenity"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Stay
2
+ class RoomFeature < ApplicationRecord
3
+ belongs_to :room, class_name: "Stay::Room"
4
+ belongs_to :feature, class_name: "Stay::Feature"
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Stay
2
+ class RoomType < ApplicationRecord
3
+ has_many :rooms, class_name: "Stay::Room"
4
+
5
+ validates :name, presence: true, uniqueness: { case_sensitive: false }, format: { without: /\s/, message: "must contain no spaces" }
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Stay
2
+ class State < ApplicationRecord
3
+ belongs_to :country, class_name: "Stay::Country"
4
+ has_many :cities, class_name: "Stay::City"
5
+ has_many :addresses, dependent: :destroy
6
+ validates :name, presence: true
7
+
8
+ def self.ransackable_attributes(auth_object = nil)
9
+ ["name"]
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,139 @@
1
+ module Stay
2
+ class Store < ApplicationRecord
3
+ include TranslatableResource
4
+ # acts_as_paranoid
5
+
6
+ TRANSLATABLE_FIELDS = %i[meta_description meta_keywords seo_title facebook
7
+ twitter instagram customer_support_email description
8
+ address contact_phone new_order_notifications_email].freeze
9
+ translates(*TRANSLATABLE_FIELDS)
10
+
11
+ self::Translation.class_eval do
12
+ acts_as_paranoid
13
+ # deleted translation values still need to be accessible - remove deleted_at scope
14
+ default_scope { unscope(where: :deleted_at) }
15
+ end
16
+
17
+ acts_as_paranoid
18
+
19
+ has_many :bookings, class_name: 'Stay::Booking'
20
+ has_many :line_items, through: :bookings, class_name: 'Stay::LineItem'
21
+ has_many :payments, through: :bookings, class_name: 'Stay::Payment'
22
+ has_many :store_properties, class_name: 'Stay::StoreProperty'
23
+ has_many :properties, through: :store_properties, class_name: 'Stay::Property'
24
+ has_many :rooms, through: :properties, class_name: 'Stay::Room', source: :rooms_including_master
25
+
26
+ validates :code, uniqueness: { case_sensitive: false, conditions: -> { with_deleted }}
27
+
28
+ default_scope { order(created_at: :asc) }
29
+
30
+ before_save :ensure_default_exists_and_is_unique
31
+ before_save :ensure_supported_currencies, :ensure_supported_locales
32
+ before_destroy :pass_default_flag_to_other_store
33
+
34
+ scope :by_url, ->(url) { where('url like ?', "%#{url}%") }
35
+
36
+ after_commit :clear_cache
37
+
38
+ alias_attribute :contact_email, :customer_support_email
39
+
40
+ def self.default
41
+ Rails.cache.fetch('default_store') do
42
+ if where(default: true).any?
43
+ where(default: true).first
44
+ else
45
+ new(default: true)
46
+ end
47
+ end
48
+ end
49
+
50
+ def self.available_locales
51
+ Rails.cache.fetch('stores_available_locales') do
52
+ Stay::Store.all.map(&:supported_locales_list).flatten.uniq
53
+ end
54
+ end
55
+
56
+ def unique_name
57
+ @unique_name ||= "#{name} (#{code})"
58
+ end
59
+
60
+ def formatted_url
61
+ return if url.blank?
62
+
63
+ @formatted_url ||= if url.match(/http:\/\/|https:\/\//)
64
+ url
65
+ else
66
+ Rails.env.development? || Rails.env.test? ? "http://#{url}" : "https://#{url}"
67
+ end
68
+ end
69
+
70
+ def supported_locales_list
71
+ @supported_locales_list ||= (read_attribute(:supported_locales).to_s.split(',') << default_locale).compact.uniq.sort
72
+ end
73
+
74
+ def supported_currencies_list
75
+ @supported_currencies_list ||= (read_attribute(:supported_currencies).to_s.split(',') << default_currency).sort.map(&:to_s).map do |code|
76
+ ::Money::Currency.find(code.strip)
77
+ end.uniq.compact
78
+ end
79
+
80
+ def can_be_deleted?
81
+ self.class.where.not(id: id).any?
82
+ end
83
+
84
+ def seo_meta_description
85
+ if meta_description.present?
86
+ meta_description
87
+ elsif seo_title.present?
88
+ seo_title
89
+ else
90
+ name
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def ensure_supported_locales
97
+ return unless attributes.keys.include?('supported_locales')
98
+ return if supported_locales.present?
99
+ return if default_locale.blank?
100
+
101
+ self.supported_locales = default_locale
102
+ end
103
+
104
+ def ensure_default_exists_and_is_unique
105
+ if default
106
+ Store.where.not(id: id).update_all(default: false)
107
+ elsif Store.where(default: true).count.zero?
108
+ self.default = true
109
+ end
110
+ end
111
+
112
+ def ensure_supported_currencies
113
+ return unless attributes.keys.include?('supported_currencies')
114
+ return if supported_currencies.present?
115
+ return if default_currency.blank?
116
+
117
+ self.supported_currencies = default_currency
118
+ end
119
+
120
+ def validate_not_last
121
+ unless can_be_deleted?
122
+ errors.add(:base, :cannot_destroy_only_store)
123
+ throw(:abort)
124
+ end
125
+ end
126
+
127
+ def pass_default_flag_to_other_store
128
+ if default? && can_be_deleted?
129
+ self.class.where.not(id: id).first.update!(default: true)
130
+ self.default = false
131
+ end
132
+ end
133
+
134
+ def clear_cache
135
+ Rails.cache.delete('default_store')
136
+ Rails.cache.delete('stores_available_locales')
137
+ end
138
+ end
139
+ end