dm_event 4.2.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (337) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +23 -0
  3. data/README.md +33 -0
  4. data/Rakefile +36 -0
  5. data/app/assets/images/dm_event/payment_logos/sofort/at/100x38.png +0 -0
  6. data/app/assets/images/dm_event/payment_logos/sofort/at/200x75.png +0 -0
  7. data/app/assets/images/dm_event/payment_logos/sofort/at/250x94.png +0 -0
  8. data/app/assets/images/dm_event/payment_logos/sofort/at/28x7.png +0 -0
  9. data/app/assets/images/dm_event/payment_logos/sofort/at/320x120.png +0 -0
  10. data/app/assets/images/dm_event/payment_logos/sofort/at/42x11.png +0 -0
  11. data/app/assets/images/dm_event/payment_logos/sofort/at/75x28.png +0 -0
  12. data/app/assets/images/dm_event/payment_logos/sofort/at/icon-32x22.png +0 -0
  13. data/app/assets/images/dm_event/payment_logos/sofort/at/icon-32x24.png +0 -0
  14. data/app/assets/images/dm_event/payment_logos/sofort/at/icon-48x32.png +0 -0
  15. data/app/assets/images/dm_event/payment_logos/sofort/at/icon-48x34.png +0 -0
  16. data/app/assets/images/dm_event/payment_logos/sofort/at/icon-64x42.png +0 -0
  17. data/app/assets/images/dm_event/payment_logos/sofort/at/icon-64x44.png +0 -0
  18. data/app/assets/images/dm_event/payment_logos/sofort/be/100x38.png +0 -0
  19. data/app/assets/images/dm_event/payment_logos/sofort/be/200x75.png +0 -0
  20. data/app/assets/images/dm_event/payment_logos/sofort/be/250x94.png +0 -0
  21. data/app/assets/images/dm_event/payment_logos/sofort/be/28x7.png +0 -0
  22. data/app/assets/images/dm_event/payment_logos/sofort/be/320x120.png +0 -0
  23. data/app/assets/images/dm_event/payment_logos/sofort/be/42x11.png +0 -0
  24. data/app/assets/images/dm_event/payment_logos/sofort/be/75x28.png +0 -0
  25. data/app/assets/images/dm_event/payment_logos/sofort/be/icon-32x22.png +0 -0
  26. data/app/assets/images/dm_event/payment_logos/sofort/be/icon-32x24.png +0 -0
  27. data/app/assets/images/dm_event/payment_logos/sofort/be/icon-48x32.png +0 -0
  28. data/app/assets/images/dm_event/payment_logos/sofort/be/icon-48x34.png +0 -0
  29. data/app/assets/images/dm_event/payment_logos/sofort/be/icon-64x42.png +0 -0
  30. data/app/assets/images/dm_event/payment_logos/sofort/be/icon-64x44.png +0 -0
  31. data/app/assets/images/dm_event/payment_logos/sofort/ch/100x38.png +0 -0
  32. data/app/assets/images/dm_event/payment_logos/sofort/ch/200x75.png +0 -0
  33. data/app/assets/images/dm_event/payment_logos/sofort/ch/250x94.png +0 -0
  34. data/app/assets/images/dm_event/payment_logos/sofort/ch/28x7.png +0 -0
  35. data/app/assets/images/dm_event/payment_logos/sofort/ch/320x120.png +0 -0
  36. data/app/assets/images/dm_event/payment_logos/sofort/ch/42x11.png +0 -0
  37. data/app/assets/images/dm_event/payment_logos/sofort/ch/75x28.png +0 -0
  38. data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-32x22.png +0 -0
  39. data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-32x24.png +0 -0
  40. data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-48x32.png +0 -0
  41. data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-48x34.png +0 -0
  42. data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-64x42.png +0 -0
  43. data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-64x44.png +0 -0
  44. data/app/assets/images/dm_event/payment_logos/sofort/de/100x38.png +0 -0
  45. data/app/assets/images/dm_event/payment_logos/sofort/de/200x75.png +0 -0
  46. data/app/assets/images/dm_event/payment_logos/sofort/de/250x94.png +0 -0
  47. data/app/assets/images/dm_event/payment_logos/sofort/de/28x7.png +0 -0
  48. data/app/assets/images/dm_event/payment_logos/sofort/de/320x120.png +0 -0
  49. data/app/assets/images/dm_event/payment_logos/sofort/de/42x11.png +0 -0
  50. data/app/assets/images/dm_event/payment_logos/sofort/de/75x28.png +0 -0
  51. data/app/assets/images/dm_event/payment_logos/sofort/de/icon-32x22.png +0 -0
  52. data/app/assets/images/dm_event/payment_logos/sofort/de/icon-32x24.png +0 -0
  53. data/app/assets/images/dm_event/payment_logos/sofort/de/icon-48x32.png +0 -0
  54. data/app/assets/images/dm_event/payment_logos/sofort/de/icon-48x34.png +0 -0
  55. data/app/assets/images/dm_event/payment_logos/sofort/de/icon-64x42.png +0 -0
  56. data/app/assets/images/dm_event/payment_logos/sofort/de/icon-64x44.png +0 -0
  57. data/app/assets/images/dm_event/payment_logos/sofort/es/100x38.png +0 -0
  58. data/app/assets/images/dm_event/payment_logos/sofort/es/200x75.png +0 -0
  59. data/app/assets/images/dm_event/payment_logos/sofort/es/250x94.png +0 -0
  60. data/app/assets/images/dm_event/payment_logos/sofort/es/28x7.png +0 -0
  61. data/app/assets/images/dm_event/payment_logos/sofort/es/320x120.png +0 -0
  62. data/app/assets/images/dm_event/payment_logos/sofort/es/42x11.png +0 -0
  63. data/app/assets/images/dm_event/payment_logos/sofort/es/75x28.png +0 -0
  64. data/app/assets/images/dm_event/payment_logos/sofort/es/icon-32x22.png +0 -0
  65. data/app/assets/images/dm_event/payment_logos/sofort/es/icon-32x24.png +0 -0
  66. data/app/assets/images/dm_event/payment_logos/sofort/es/icon-48x32.png +0 -0
  67. data/app/assets/images/dm_event/payment_logos/sofort/es/icon-48x34.png +0 -0
  68. data/app/assets/images/dm_event/payment_logos/sofort/es/icon-64x42.png +0 -0
  69. data/app/assets/images/dm_event/payment_logos/sofort/es/icon-64x44.png +0 -0
  70. data/app/assets/images/dm_event/payment_logos/sofort/fr/100x38.png +0 -0
  71. data/app/assets/images/dm_event/payment_logos/sofort/fr/200x75.png +0 -0
  72. data/app/assets/images/dm_event/payment_logos/sofort/fr/250x94.png +0 -0
  73. data/app/assets/images/dm_event/payment_logos/sofort/fr/28x7.png +0 -0
  74. data/app/assets/images/dm_event/payment_logos/sofort/fr/320x120.png +0 -0
  75. data/app/assets/images/dm_event/payment_logos/sofort/fr/42x11.png +0 -0
  76. data/app/assets/images/dm_event/payment_logos/sofort/fr/75x28.png +0 -0
  77. data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-32x22.png +0 -0
  78. data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-32x24.png +0 -0
  79. data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-48x32.png +0 -0
  80. data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-48x34.png +0 -0
  81. data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-64x42.png +0 -0
  82. data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-64x44.png +0 -0
  83. data/app/assets/images/dm_event/payment_logos/sofort/it/100x38.png +0 -0
  84. data/app/assets/images/dm_event/payment_logos/sofort/it/200x75.png +0 -0
  85. data/app/assets/images/dm_event/payment_logos/sofort/it/250x94.png +0 -0
  86. data/app/assets/images/dm_event/payment_logos/sofort/it/28x7.png +0 -0
  87. data/app/assets/images/dm_event/payment_logos/sofort/it/320x120.png +0 -0
  88. data/app/assets/images/dm_event/payment_logos/sofort/it/42x11.png +0 -0
  89. data/app/assets/images/dm_event/payment_logos/sofort/it/75x28.png +0 -0
  90. data/app/assets/images/dm_event/payment_logos/sofort/it/icon-32x22.png +0 -0
  91. data/app/assets/images/dm_event/payment_logos/sofort/it/icon-32x24.png +0 -0
  92. data/app/assets/images/dm_event/payment_logos/sofort/it/icon-48x32.png +0 -0
  93. data/app/assets/images/dm_event/payment_logos/sofort/it/icon-48x34.png +0 -0
  94. data/app/assets/images/dm_event/payment_logos/sofort/it/icon-64x42.png +0 -0
  95. data/app/assets/images/dm_event/payment_logos/sofort/it/icon-64x44.png +0 -0
  96. data/app/assets/images/dm_event/payment_logos/sofort/nl/100x38.png +0 -0
  97. data/app/assets/images/dm_event/payment_logos/sofort/nl/200x75.png +0 -0
  98. data/app/assets/images/dm_event/payment_logos/sofort/nl/250x94.png +0 -0
  99. data/app/assets/images/dm_event/payment_logos/sofort/nl/28x7.png +0 -0
  100. data/app/assets/images/dm_event/payment_logos/sofort/nl/320x120.png +0 -0
  101. data/app/assets/images/dm_event/payment_logos/sofort/nl/42x11.png +0 -0
  102. data/app/assets/images/dm_event/payment_logos/sofort/nl/75x28.png +0 -0
  103. data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-32x22.png +0 -0
  104. data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-32x24.png +0 -0
  105. data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-48x32.png +0 -0
  106. data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-48x34.png +0 -0
  107. data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-64x42.png +0 -0
  108. data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-64x44.png +0 -0
  109. data/app/assets/images/dm_event/payment_logos/sofort/pl/100x38.png +0 -0
  110. data/app/assets/images/dm_event/payment_logos/sofort/pl/200x75.png +0 -0
  111. data/app/assets/images/dm_event/payment_logos/sofort/pl/250x94.png +0 -0
  112. data/app/assets/images/dm_event/payment_logos/sofort/pl/28x7.png +0 -0
  113. data/app/assets/images/dm_event/payment_logos/sofort/pl/320x120.png +0 -0
  114. data/app/assets/images/dm_event/payment_logos/sofort/pl/42x11.png +0 -0
  115. data/app/assets/images/dm_event/payment_logos/sofort/pl/75x28.png +0 -0
  116. data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-32x22.png +0 -0
  117. data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-32x24.png +0 -0
  118. data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-48x32.png +0 -0
  119. data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-48x34.png +0 -0
  120. data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-64x42.png +0 -0
  121. data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-64x44.png +0 -0
  122. data/app/assets/images/dm_event/payment_logos/sofort/uk/100x38.png +0 -0
  123. data/app/assets/images/dm_event/payment_logos/sofort/uk/200x75.png +0 -0
  124. data/app/assets/images/dm_event/payment_logos/sofort/uk/250x94.png +0 -0
  125. data/app/assets/images/dm_event/payment_logos/sofort/uk/28x7.png +0 -0
  126. data/app/assets/images/dm_event/payment_logos/sofort/uk/320x120.png +0 -0
  127. data/app/assets/images/dm_event/payment_logos/sofort/uk/42x11.png +0 -0
  128. data/app/assets/images/dm_event/payment_logos/sofort/uk/75x28.png +0 -0
  129. data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-32x22.png +0 -0
  130. data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-32x24.png +0 -0
  131. data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-48x32.png +0 -0
  132. data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-48x34.png +0 -0
  133. data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-64x42.png +0 -0
  134. data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-64x44.png +0 -0
  135. data/app/assets/javascripts/dm_event/admin.js +94 -0
  136. data/app/assets/stylesheets/dm_event/admin.css +152 -0
  137. data/app/assets/stylesheets/dm_event/application.css +65 -0
  138. data/app/controllers/dm_event/admin/admin_controller.rb +13 -0
  139. data/app/controllers/dm_event/admin/registrations_controller.rb +411 -0
  140. data/app/controllers/dm_event/admin/workshop_prices_controller.rb +73 -0
  141. data/app/controllers/dm_event/admin/workshops_controller.rb +147 -0
  142. data/app/controllers/dm_event/application_controller.rb +23 -0
  143. data/app/controllers/dm_event/payments_controller.rb +83 -0
  144. data/app/controllers/dm_event/registrations_controller.rb +368 -0
  145. data/app/datatables/registration_datatable.rb +117 -0
  146. data/app/helpers/dm_event/application_helper.rb +4 -0
  147. data/app/helpers/dm_event/registrations_helper.rb +14 -0
  148. data/app/helpers/dm_event/workshops_helper.rb +41 -0
  149. data/app/mailers/payment_reminder_mailer.rb +25 -0
  150. data/app/mailers/registration_notify_mailer.rb +31 -0
  151. data/app/models/dm_event/concerns/ability.rb +38 -0
  152. data/app/models/dm_event/concerns/registration_state_email.rb +80 -0
  153. data/app/models/dm_event/concerns/registration_state_machine.rb +218 -0
  154. data/app/models/dm_event/concerns/user_profile.rb +20 -0
  155. data/app/models/dm_event/model_decorators.rb +6 -0
  156. data/app/models/dm_event/permitted_params.rb +35 -0
  157. data/app/models/payment.rb +48 -0
  158. data/app/models/registration.rb +297 -0
  159. data/app/models/workshop.rb +247 -0
  160. data/app/models/workshop_price.rb +118 -0
  161. data/app/presenters/event_common_presenter.rb +31 -0
  162. data/app/presenters/registration_presenter.rb +34 -0
  163. data/app/presenters/workshop_presenter.rb +22 -0
  164. data/app/views/dm_event/admin/registrations/_form.html.erb +297 -0
  165. data/app/views/dm_event/admin/registrations/action_state.js.erb +1 -0
  166. data/app/views/dm_event/admin/registrations/edit.html.erb +1 -0
  167. data/app/views/dm_event/admin/registrations/index.html.erb +0 -0
  168. data/app/views/dm_event/admin/workshop_prices/_form.html.erb +62 -0
  169. data/app/views/dm_event/admin/workshop_prices/edit.html.erb +2 -0
  170. data/app/views/dm_event/admin/workshop_prices/index.html.erb +31 -0
  171. data/app/views/dm_event/admin/workshop_prices/new.html.erb +2 -0
  172. data/app/views/dm_event/admin/workshops/_form.html.erb +61 -0
  173. data/app/views/dm_event/admin/workshops/_header_menu.html.erb +21 -0
  174. data/app/views/dm_event/admin/workshops/_registration_stats.html.erb +58 -0
  175. data/app/views/dm_event/admin/workshops/additional_configuration.html.erb +47 -0
  176. data/app/views/dm_event/admin/workshops/edit.html.erb +2 -0
  177. data/app/views/dm_event/admin/workshops/edit_system_email.html.erb +75 -0
  178. data/app/views/dm_event/admin/workshops/financials.html.erb +179 -0
  179. data/app/views/dm_event/admin/workshops/index.html.erb +100 -0
  180. data/app/views/dm_event/admin/workshops/lost_users.html.erb +44 -0
  181. data/app/views/dm_event/admin/workshops/new.html.erb +2 -0
  182. data/app/views/dm_event/admin/workshops/show.html.erb +47 -0
  183. data/app/views/dm_event/admin/workshops/user_outstanding_balances.html.erb +29 -0
  184. data/app/views/dm_event/liquid_tags/_funding_project_status.html.erb +27 -0
  185. data/app/views/dm_event/payments/_registrations_paypal_standard.html.erb +23 -0
  186. data/app/views/dm_event/payments/_registrations_sofort.html.erb +18 -0
  187. data/app/views/dm_event/registrations/_section_address.html.erb +30 -0
  188. data/app/views/dm_event/registrations/_section_custom_fields.html.erb +7 -0
  189. data/app/views/dm_event/registrations/_section_prices.html.erb +33 -0
  190. data/app/views/dm_event/registrations/_workshop_price.html.erb +18 -0
  191. data/app/views/dm_event/registrations/choose_payment.html.erb +5 -0
  192. data/app/views/dm_event/registrations/closed.html.erb +14 -0
  193. data/app/views/dm_event/registrations/new.html.erb +30 -0
  194. data/app/views/dm_event/registrations/success.html.erb +15 -0
  195. data/app/views/layouts/email_templates/dm_event_email_layout.html.erb +318 -0
  196. data/app/views/layouts/email_templates/dm_event_email_layout.text.erb +1 -0
  197. data/app/views/layouts/email_templates/dm_event_payment_reminder.html.erb +22 -0
  198. data/app/views/layouts/email_templates/dm_event_payment_reminder.text.erb +19 -0
  199. data/app/views/layouts/email_templates/dm_event_registration_notify.html.erb +21 -0
  200. data/app/views/layouts/email_templates/dm_event_registration_notify.text.erb +13 -0
  201. data/config/initializers/dm_event.rb +1 -0
  202. data/config/initializers/liquid_init.rb +2 -0
  203. data/config/locales/ems.cs.yml +51 -0
  204. data/config/locales/ems.de.yml +51 -0
  205. data/config/locales/ems.en.yml +51 -0
  206. data/config/locales/ems.fi.yml +51 -0
  207. data/config/locales/ems.ja.yml +51 -0
  208. data/config/routes.rb +40 -0
  209. data/db/migrate/20130510155617_create_events.rb +98 -0
  210. data/db/migrate/20130516124327_add_locale_to_registration.rb +5 -0
  211. data/db/migrate/20130516204454_add_user_profile_to_registration.rb +11 -0
  212. data/db/migrate/20130518092324_add_require_address.rb +7 -0
  213. data/db/migrate/20130609145812_add_workshop_published.rb +5 -0
  214. data/db/migrate/20130624134506_add_amount_currency.rb +16 -0
  215. data/db/migrate/20130703131515_add_workshop_currency.rb +5 -0
  216. data/db/migrate/20130913083529_add_event_funding_type.rb +6 -0
  217. data/db/migrate/20130914105736_add_enable_payments.rb +5 -0
  218. data/db/migrate/20130916160033_add_sidebar_to_workshop.rb +5 -0
  219. data/db/migrate/20130917161700_add_show_address.rb +5 -0
  220. data/db/migrate/20131116180719_add_default_funding_goal.rb +8 -0
  221. data/db/migrate/20131127165445_add_payment_reminder.rb +5 -0
  222. data/db/migrate/20140122204702_add_event_image.rb +5 -0
  223. data/db/migrate/20140523165342_add_workshop_summary.rb +5 -0
  224. data/db/migrate/20140605052208_add_uuid_to_registration.rb +10 -0
  225. data/db/migrate/20140707152124_add_registration_comment_count.rb +5 -0
  226. data/db/migrate/20140708142312_add_payment_comment.rb +20 -0
  227. data/db/migrate/20150709125105_add_payment_reminder_history.rb +16 -0
  228. data/db/migrate/20160810081542_add_bcc_contact_email.rb +5 -0
  229. data/db/migrate/20160821150120_index_foreign_keys_in_ems_registrations.rb +9 -0
  230. data/db/migrate/20160821150121_index_foreign_keys_in_ems_workshop_price_translations.rb +5 -0
  231. data/db/migrate/20160821150122_index_foreign_keys_in_ems_workshop_prices.rb +6 -0
  232. data/db/migrate/20160821150123_index_foreign_keys_in_ems_workshop_translations.rb +5 -0
  233. data/db/migrate/20160821150124_index_foreign_keys_in_ems_workshops.rb +6 -0
  234. data/lib/dm_event/engine.rb +20 -0
  235. data/lib/dm_event/liquid/tags/funding_project_status.rb +55 -0
  236. data/lib/dm_event.rb +4 -0
  237. data/lib/tasks/dm_event_tasks.rake +4 -0
  238. data/spec/controllers/registrations_controller_spec.rb +152 -0
  239. data/spec/dummy/README.rdoc +28 -0
  240. data/spec/dummy/Rakefile +6 -0
  241. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  242. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  243. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  244. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  245. data/spec/dummy/app/models/ability.rb +10 -0
  246. data/spec/dummy/app/models/user.rb +6 -0
  247. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  248. data/spec/dummy/bin/bundle +3 -0
  249. data/spec/dummy/bin/rails +4 -0
  250. data/spec/dummy/bin/rake +4 -0
  251. data/spec/dummy/config/application.rb +26 -0
  252. data/spec/dummy/config/boot.rb +5 -0
  253. data/spec/dummy/config/database.yml +25 -0
  254. data/spec/dummy/config/environment.rb +5 -0
  255. data/spec/dummy/config/environments/development.rb +37 -0
  256. data/spec/dummy/config/environments/production.rb +82 -0
  257. data/spec/dummy/config/environments/test.rb +39 -0
  258. data/spec/dummy/config/initializers/assets.rb +8 -0
  259. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  260. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  261. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  262. data/spec/dummy/config/initializers/inflections.rb +16 -0
  263. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  264. data/spec/dummy/config/initializers/session_store.rb +3 -0
  265. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  266. data/spec/dummy/config/locales/en.yml +23 -0
  267. data/spec/dummy/config/routes.rb +11 -0
  268. data/spec/dummy/config/secrets.yml +22 -0
  269. data/spec/dummy/config.ru +4 -0
  270. data/spec/dummy/db/development.sqlite3 +0 -0
  271. data/spec/dummy/db/migrate/20141114170927_add_globalize_countries.dm_core.rb +50 -0
  272. data/spec/dummy/db/migrate/20141114170928_devise_create_users.dm_core.rb +46 -0
  273. data/spec/dummy/db/migrate/20141114170929_add_user_fields.dm_core.rb +14 -0
  274. data/spec/dummy/db/migrate/20141114170930_rolify_create_roles.dm_core.rb +20 -0
  275. data/spec/dummy/db/migrate/20141114170931_add_last_access.dm_core.rb +10 -0
  276. data/spec/dummy/db/migrate/20141114170932_create_versions.dm_core.rb +19 -0
  277. data/spec/dummy/db/migrate/20141114170933_add_object_changes_column_to_versions.dm_core.rb +12 -0
  278. data/spec/dummy/db/migrate/20141114170934_create_dm_core_accounts.dm_core.rb +13 -0
  279. data/spec/dummy/db/migrate/20141114170935_add_account_to_users.dm_core.rb +9 -0
  280. data/spec/dummy/db/migrate/20141114170936_create_preferences.dm_core.rb +13 -0
  281. data/spec/dummy/db/migrate/20141114170937_create_comments.dm_core.rb +22 -0
  282. data/spec/dummy/db/migrate/20141114170938_add_activity.dm_core.rb +21 -0
  283. data/spec/dummy/db/migrate/20141114170939_add_type_to_comments.dm_core.rb +9 -0
  284. data/spec/dummy/db/migrate/20141114170940_add_category.dm_core.rb +28 -0
  285. data/spec/dummy/db/migrate/20141114170941_create_email_table.dm_core.rb +26 -0
  286. data/spec/dummy/db/migrate/20141114170942_add_user_profile.dm_core.rb +46 -0
  287. data/spec/dummy/db/migrate/20141114170943_add_profile_email.dm_core.rb +14 -0
  288. data/spec/dummy/db/migrate/20141114170944_create_payment_history.dm_core.rb +37 -0
  289. data/spec/dummy/db/migrate/20141114170945_change_anchor_field.dm_core.rb +10 -0
  290. data/spec/dummy/db/migrate/20141114170946_create_user_site_profile.dm_core.rb +27 -0
  291. data/spec/dummy/db/migrate/20141114170947_add_avatar.dm_core.rb +12 -0
  292. data/spec/dummy/db/migrate/20141114170948_add_notify_to_payment_history.dm_core.rb +8 -0
  293. data/spec/dummy/db/migrate/20141114170949_acts_as_votable_migration.dm_core.rb +28 -0
  294. data/spec/dummy/db/migrate/20141114170950_add_user_site_profile_uuid.dm_core.rb +19 -0
  295. data/spec/dummy/db/migrate/20141114170951_add_invoice_id.dm_core.rb +7 -0
  296. data/spec/dummy/db/migrate/20141114170952_acts_as_follower_migration.dm_core.rb +18 -0
  297. data/spec/dummy/db/migrate/20141114170953_rename_invoice_id.dm_core.rb +12 -0
  298. data/spec/dummy/db/migrate/20141114170954_add_core_addresses.dm_core.rb +18 -0
  299. data/spec/dummy/db/migrate/20141114170955_papertrail_increase_column.dm_core.rb +9 -0
  300. data/spec/dummy/db/migrate/20141114170956_acts_as_taggable_on_migration.dm_core.rb +32 -0
  301. data/spec/dummy/db/migrate/20141114170957_add_missing_unique_indices.dm_core.rb +23 -0
  302. data/spec/dummy/db/migrate/20141114170958_add_taggings_counter_cache_to_tags.dm_core.rb +16 -0
  303. data/spec/dummy/db/migrate/20141114170959_create_custom_fields.dm_core.rb +40 -0
  304. data/spec/dummy/db/migrate/20141114170960_add_missing_taggable_index.dm_core.rb +11 -0
  305. data/spec/dummy/db/migrate/20141119103740_create_cms.dm_cms.rb +92 -0
  306. data/spec/dummy/db/migrate/20141119103741_add_account_to_cms.dm_cms.rb +11 -0
  307. data/spec/dummy/db/migrate/20141119103742_create_blog.dm_cms.rb +62 -0
  308. data/spec/dummy/db/migrate/20141119103743_add_notification_sent.dm_cms.rb +6 -0
  309. data/spec/dummy/db/migrate/20141119103744_add_blog_comment.dm_cms.rb +8 -0
  310. data/spec/dummy/db/migrate/20141119103745_add_blog_image.dm_cms.rb +6 -0
  311. data/spec/dummy/db/migrate/20141119103746_rename_snippet_slug.dm_cms.rb +14 -0
  312. data/spec/dummy/db/migrate/20141119103747_add_requires_subscription_blog.dm_cms.rb +8 -0
  313. data/spec/dummy/db/migrate/20141119103748_add_pages_ranked_model.dm_cms.rb +23 -0
  314. data/spec/dummy/db/migrate/20141119103749_add_blog_owner.dm_cms.rb +7 -0
  315. data/spec/dummy/db/migrate/20141119103750_create_media_files.dm_cms.rb +30 -0
  316. data/spec/dummy/db/migrate/20141119103751_add_cmspage_summary.dm_cms.rb +7 -0
  317. data/spec/dummy/db/migrate/20141119103752_add_blog_image_email_header.dm_cms.rb +6 -0
  318. data/spec/dummy/db/migrate/20141119103753_add_header_image.dm_cms.rb +10 -0
  319. data/spec/dummy/db/migrate/20160128154135_add_favored_locale.dm_core.rb +17 -0
  320. data/spec/dummy/db/migrate/20160128154136_update_papertrail_v4.dm_core.rb +41 -0
  321. data/spec/dummy/db/schema.rb +677 -0
  322. data/spec/dummy/public/404.html +67 -0
  323. data/spec/dummy/public/422.html +67 -0
  324. data/spec/dummy/public/500.html +66 -0
  325. data/spec/dummy/public/favicon.ico +0 -0
  326. data/spec/factories/account_factory.rb +7 -0
  327. data/spec/factories/registration_factory.rb +13 -0
  328. data/spec/factories/workshop_factory.rb +25 -0
  329. data/spec/factories/workshop_price_factory.rb +19 -0
  330. data/spec/models/registration_spec.rb +83 -0
  331. data/spec/models/workshop_spec.rb +26 -0
  332. data/spec/rails_helper.rb +70 -0
  333. data/spec/spec_helper.rb +85 -0
  334. data/spec/support/accounts.rb +17 -0
  335. data/spec/support/devise.rb +44 -0
  336. data/spec/support/fix_locale.rb +57 -0
  337. metadata +520 -0
@@ -0,0 +1,297 @@
1
+ # Important: one reason to link off of the user_profile instead of the user
2
+ # is so that we can support registrations without requiring an account.
3
+ # We can create a "userless" profile, that has all the necessary information.
4
+ # This is instead of duplicating all those fields in the registration table.
5
+ #------------------------------------------------------------------------------
6
+ class Registration < ActiveRecord::Base
7
+ include DmEvent::Concerns::RegistrationStateMachine
8
+ include DmEvent::Concerns::RegistrationStateEmail
9
+ include DmCore::Concerns::HasCustomFields
10
+ include ActiveMerchant::Billing::Integrations
11
+
12
+ self.table_name = 'ems_registrations'
13
+
14
+ belongs_to :workshop, counter_cache: true
15
+ belongs_to :workshop_price
16
+ belongs_to :user_profile
17
+ belongs_to :account
18
+ has_many :payment_histories, as: :owner, dependent: :destroy
19
+ belongs_to :payment_comment, class_name: 'Comment'
20
+ preference :payment_reminder_hold_until, :date
21
+ serialize :payment_reminder_history, Array
22
+ attr_accessor :payment_comment_text
23
+ acts_as_commentable :private
24
+
25
+ accepts_nested_attributes_for :user_profile
26
+
27
+ monetize :amount_paid_cents, with_model_currency: :amount_paid_currency, allow_nil: true
28
+
29
+ default_scope { where(account_id: Account.current.id) }
30
+ scope :attending, -> { where("(aasm_state = 'accepted' OR aasm_state = 'paid') AND archived_on IS NULL") }
31
+ scope :accepted, -> { where("aasm_state = 'accepted' AND archived_on IS NULL") }
32
+ scope :paid, -> { where("aasm_state = 'paid' AND archived_on IS NULL") }
33
+ scope :unpaid, -> { where("aasm_state = 'accepted' AND archived_on IS NULL") } # same as accepted
34
+ scope :discounted,-> { where("discount_value > 0") } # use like registrations.attending.discounted
35
+
36
+ after_initialize :create_uuid
37
+ before_create :set_currency
38
+ after_create :set_receipt_code
39
+
40
+ validates_uniqueness_of :uuid
41
+ validates_presence_of :workshop_price_id, if: Proc.new { |reg| reg.workshop.workshop_prices.size > 0}
42
+ validates_presence_of :workshop_price_id, if: Proc.new { |reg| reg.workshop.workshop_prices.size > 0}
43
+ validates_numericality_of :discount_value, allow_nil: true
44
+ validates_length_of :payment_comment, maximum: 255
45
+
46
+ delegate :first_name, :last_name, :full_name, :email, :address, :address2,
47
+ :city, :state, :country, :zipcode, :phone, to: :user_profile
48
+
49
+ private
50
+
51
+ # the uuid is used to provide a private url to a customer so that they can access
52
+ # their registration if not logged in. This is particularly important when
53
+ # a customer registers without having a user account.
54
+ #------------------------------------------------------------------------------
55
+ def create_uuid
56
+ self.uuid = SecureRandom.uuid if self.new_record?
57
+ end
58
+
59
+ # The amount_paid currency should match the workshop base currency
60
+ #------------------------------------------------------------------------------
61
+ def set_currency
62
+ self[:amount_paid_currency] = workshop.base_currency
63
+ end
64
+
65
+ # Receipt code: (workshop.id)-(registration.id). eg. 003-101
66
+ #------------------------------------------------------------------------------
67
+ def set_receipt_code
68
+ receipt_code = ("%03d" % workshop.id) + '-' + ("%03d" % self[:id])
69
+ update_attribute(:receipt_code, receipt_code)
70
+ end
71
+
72
+ public
73
+
74
+ # receipt code is simply the record id + 1100
75
+ #------------------------------------------------------------------------------
76
+ def self.receiptcode_to_id(receiptcode)
77
+ return receipt_code.split('-')[1].to_i
78
+ end
79
+
80
+ # Price of this registration (without discount)
81
+ #------------------------------------------------------------------------------
82
+ def price
83
+ (workshop_price && workshop_price.price) ? workshop_price.price : Money.new(0, workshop.base_currency)
84
+ end
85
+
86
+ # Price with discount
87
+ #------------------------------------------------------------------------------
88
+ def discounted_price
89
+ price - discount
90
+ end
91
+
92
+ #------------------------------------------------------------------------------
93
+ def discount
94
+ return Money.new(0, workshop.base_currency) if workshop_price.nil? || workshop_price.price.nil?
95
+
96
+ unless discount_value.blank?
97
+ cents = (discount_use_percent ? (workshop_price.price.cents * discount_value / 100) : (discount_value * 100))
98
+ else
99
+ cents = 0
100
+ end
101
+ Money.new(cents, workshop_price.price.currency)
102
+ end
103
+
104
+ # Return the amount still owed, based on the current payments made.
105
+ # balance_owed is positive if payment is still required. Negative if there
106
+ # has been an overpayment
107
+ #------------------------------------------------------------------------------
108
+ def balance_owed
109
+ discounted_price - amount_paid
110
+ end
111
+
112
+ # suggested amount of next payment.
113
+ # if a payment is within 20 of the balance_owed, then they should pay the balance
114
+ #------------------------------------------------------------------------------
115
+ def payment_owed
116
+ if workshop_price
117
+ amount_20 = Money.new(2000, workshop_price.price.currency)
118
+ (workshop_price.payment_price + amount_20) > balance_owed ? balance_owed : workshop_price.payment_price
119
+ else
120
+ balance_owed
121
+ end
122
+ end
123
+
124
+ # Return the number of items specified, in particular the number of items in
125
+ # a particular state
126
+ #------------------------------------------------------------------------------
127
+ def self.number_of(state, options = {})
128
+ query = (options[:only_confirmed] ? where.not(confirmed_on: nil) : all)
129
+ case state
130
+ when :attending
131
+ attending.count
132
+ when :unpaid
133
+ #--- the number of unpaid is the same as the number of accepted
134
+ number_of(:accepted)
135
+ when :checkedin
136
+ query.where.not(checkin_at: 0).where(archived_on: nil).count
137
+ when :archived
138
+ query.where.not(archived_on: nil).count
139
+ when :registrations
140
+ #--- don't count any canceled
141
+ query.where(archived_on: nil).where.not(aasm_state: 'canceled').where.not(aasm_state: 'refunded').count
142
+ when :at_price
143
+ #--- number of registrations for a particular price
144
+ query.where(archived_on: nil).where("(aasm_state = 'paid' OR aasm_state = 'accepted')").where(workshop_price_id: options[:price_id]).count
145
+ when :for_all_prices
146
+ #--- array of counts per price
147
+ query.where(archived_on: nil).where("(aasm_state = 'paid' OR aasm_state = 'accepted')").group(:workshop_price_id).count
148
+ when :discounted
149
+ attending.discounted.count
150
+ when :discounted_total
151
+ total = attending.discounted.to_a.sum(&:discount)
152
+ (total == 0) ? Money.new(0) : total
153
+ when :user_updated
154
+ #--- how many users updated their record
155
+ query.where(archived_on: nil).where("(aasm_state = 'paid' OR aasm_state = 'accepted')").where.not(user_updated_at: nil).count
156
+ when :confirmed
157
+ #--- how many users confirmed their attendance
158
+ where.not(confirmed_on: nil).where(archived_on: nil).where("(aasm_state = 'paid' OR aasm_state = 'accepted')").count
159
+ else
160
+ #--- must be wanting to count the process states
161
+ query.where(archived_on: nil, aasm_state: state).count
162
+ end
163
+ end
164
+
165
+ # check if the regsitration is unpaid
166
+ #------------------------------------------------------------------------------
167
+ def unpaid?
168
+ self.accepted? && self.archived_on == nil
169
+ end
170
+
171
+ # Is it time to send a payment reminder?
172
+ # Due first 7 days after inital registration. Then every 14 days after that
173
+ #------------------------------------------------------------------------------
174
+ def payment_reminder_due?
175
+ if preferred_payment_reminder_hold_until.nil? || preferred_payment_reminder_hold_until < Time.now
176
+ time_period = self.payment_reminder_sent_on.nil? ? (self.created_at + 7.days) : (self.payment_reminder_sent_on + 14.days)
177
+ self.balance_owed > Money.new(0, workshop.base_currency) && time_period < Time.now
178
+ end
179
+
180
+ # if recurring_period since last paymnet
181
+ # if 7 days after registration and no payment
182
+ # if 14 days since last reminder and no payment
183
+ # if resume_reminders is past
184
+ # last_payment = payment_histories.order('created_on').last
185
+ # if workshop_price.recurring?
186
+ # if
187
+ end
188
+
189
+ # Setup the columns for exporting data as csv.
190
+ #------------------------------------------------------------------------------
191
+ def self.csv_columns(workshop)
192
+ column_definitions = []
193
+ column_definitions << ["Receipt Code", "'R-' + item.receipt_code", 75] # 'R-' makes sure Numbers treats as a String, not a Date
194
+ column_definitions << ['Process State', 'item.aasm_state', 100]
195
+ column_definitions << ["Full Name", "item.full_name", 100]
196
+ column_definitions << ["Last Name", "item.last_name.capitalize", 100]
197
+ column_definitions << ["First Name", "item.first_name.capitalize", 100]
198
+ column_definitions << ["Email", "item.email.downcase", 150]
199
+ column_definitions << ["Address", "item.address", 150]
200
+ column_definitions << ["Address2", "item.address2"]
201
+ column_definitions << ["City", "item.city.capitalize", 100]
202
+ column_definitions << ["State", "item.state.capitalize"]
203
+ column_definitions << ["Zipcode", "item.zipcode"]
204
+ column_definitions << ["Country", "item.country.code"]
205
+
206
+ column_definitions << ['Registered on', 'item.created_at.to_date', 75, {type: 'DateTime', numberformat: 'd mmm, yyyy'}]
207
+
208
+ column_definitions << ["Price", "item.workshop_price.price.to_f", nil, {type: 'Number', numberformat: '#,##0.00'}]
209
+ column_definitions << ["Price Description", "item.workshop_price.price_description"]
210
+ column_definitions << ["Price Sub Descr", "item.workshop_price.sub_description"]
211
+ column_definitions << ["Discount", "item.discount.to_f", nil, {type: 'Number', numberformat: '#,##0.00'}]
212
+ column_definitions << ["Paid", "item.amount_paid.to_f", nil, {type: 'Number', numberformat: '#,##0.00'}]
213
+ column_definitions << ["Balance", "item.balance_owed.to_f", nil, {type: 'Number', numberformat: '#,##0.00'}]
214
+
215
+ # ---- add the extra fields defined in the workshop record
216
+ workshop.custom_field_defs.each_with_index do | x, index |
217
+ case x.field_type
218
+ when 'check_box_collection'
219
+ column_definitions << [ "#{x.column_name}", "(z = item.custom_fields.detect { |y| y.custom_field_def_id == #{x.id} }) ? z.value : ''", nil, {type: 'list', custom_field: true}]
220
+ when 'divider'
221
+ else
222
+ column_definitions << [ "#{x.column_name}", "(z = item.custom_fields.detect { |y| y.custom_field_def_id == #{x.id} }) ? z.value : ''", nil, {custom_field: true}]
223
+ end
224
+ end
225
+ return column_definitions
226
+ end
227
+
228
+ # Payment was entered manually, create the history record. You can tell it's
229
+ # a manual entry if the user_profile is filled in - means a human did it.
230
+ #------------------------------------------------------------------------------
231
+ def manual_payment(payment_history, cost, total_currency, user_profile,
232
+ options = { item_ref: '', payment_method: 'cash', bill_to_name: '', payment_date: Time.now,
233
+ notify_data: nil, transaction_id: nil, status: '' } )
234
+ amount = Monetize.parse(cost, total_currency)
235
+
236
+ if payment_history
237
+ new_amount_paid = self.amount_paid - self.workshop_price.to_base_currency(payment_history.total) + self.workshop_price.to_base_currency(amount)
238
+ payment_history.update_attributes(
239
+ item_ref: options[:item_ref],
240
+ cost: cost,
241
+ total_cents: amount.cents,
242
+ total_currency: amount.currency.iso_code,
243
+ payment_method: options[:payment_method],
244
+ bill_to_name: options[:bill_to_name],
245
+ payment_date: options[:payment_date],
246
+ user_profile_id: user_profile.id)
247
+ else
248
+ new_amount_paid = self.amount_paid + self.workshop_price.to_base_currency(amount)
249
+ payment_history = self.payment_histories.create(
250
+ anchor_id: receipt_code,
251
+ item_ref: options[:item_ref],
252
+ cost: cost,
253
+ quantity: 1,
254
+ discount: 0,
255
+ total_cents: amount.cents,
256
+ total_currency: amount.currency.iso_code,
257
+ payment_method: options[:payment_method],
258
+ bill_to_name: options[:bill_to_name],
259
+ payment_date: options[:payment_date],
260
+ user_profile_id: (user_profile ? user_profile.id : nil),
261
+ notify_data: options[:notify_data],
262
+ transaction_id: options[:transaction_id],
263
+ status: (user_profile ? "Completed" : options[:status])
264
+ )
265
+ end
266
+
267
+ if payment_history.errors.empty?
268
+ self.update_attribute(:amount_paid_cents, new_amount_paid.cents)
269
+ self.reload
270
+ self.send('paid!') if balance_owed.cents <= 0 && self.accepted?
271
+ else
272
+ logger.error("===> Error: Registration.manual_payment: #{payment_history.errors.inspect}")
273
+ end
274
+ return payment_history
275
+ end
276
+
277
+ # delete a payment and update the registrations total amount paid
278
+ #------------------------------------------------------------------------------
279
+ def delete_payment(payment_id)
280
+ payment = PaymentHistory.find(payment_id)
281
+ if payment
282
+ self.update_attribute(:amount_paid_cents, (self.amount_paid - self.workshop_price.to_base_currency(payment.total)).cents)
283
+ payment.destroy
284
+ suppress_transition_email
285
+ self.send('accept!') if balance_owed.positive? && self.paid?
286
+ return true
287
+ end
288
+ return false
289
+ end
290
+
291
+ # Return the payment page url, so that it can be used in emails
292
+ #------------------------------------------------------------------------------
293
+ def payment_url
294
+ DmEvent::Engine.routes.url_helpers.register_choose_payment_url(self.uuid, host: Account.current.url_host, locale: I18n.locale)
295
+ end
296
+
297
+ end
@@ -0,0 +1,247 @@
1
+ class Workshop < ActiveRecord::Base
2
+ include DmCore::Concerns::DefinesCustomFields
3
+
4
+ self.table_name = 'ems_workshops'
5
+
6
+ belongs_to :country, class_name: 'DmCore::Country'
7
+ has_many :registrations, dependent: :destroy
8
+ has_many :workshop_prices, dependent: :destroy
9
+ has_many :system_emails, {as: :emailable, dependent: :destroy}
10
+ has_one :pending_email, -> { where("email_type LIKE 'pending'") }, class_name: 'SystemEmail', as: :emailable
11
+ has_one :accepted_email, -> { where("email_type LIKE 'accepted'") }, class_name: 'SystemEmail', as: :emailable
12
+ has_one :rejected_email, -> { where("email_type LIKE 'rejected'") }, class_name: 'SystemEmail', as: :emailable
13
+ has_one :paid_email, -> { where("email_type LIKE 'paid'") }, class_name: 'SystemEmail', as: :emailable
14
+ has_one :waitlisted_email, -> { where("email_type LIKE 'waitlisted'") }, class_name: 'SystemEmail', as: :emailable
15
+ has_one :reviewing_email, -> { where("email_type LIKE 'reviewing'") }, class_name: 'SystemEmail', as: :emailable
16
+ has_one :canceled_email, -> { where("email_type LIKE 'canceled'") }, class_name: 'SystemEmail', as: :emailable
17
+ has_one :refunded_email, -> { where("email_type LIKE 'refunded'") }, class_name: 'SystemEmail', as: :emailable
18
+ has_one :noshow_email, -> { where("email_type LIKE 'noshow'") }, class_name: 'SystemEmail', as: :emailable
19
+ has_one :cms_blog, as: :owner
20
+ has_one :forum, as: :owner
21
+
22
+ # --- globalize
23
+ translates :title, :description, :summary, :sidebar, fallbacks_for_empty_translations: true
24
+ globalize_accessors locales: DmCore::Language.language_array
25
+
26
+ # --- FriendlyId
27
+ extend FriendlyId
28
+ include DmCore::Concerns::FriendlyId
29
+
30
+ resourcify
31
+
32
+ preference :show_social_buttons, :boolean, default: false
33
+ preference :header_accent_color, :string
34
+
35
+ # --- validations
36
+ validates_presence_of :country_id
37
+ validates_presence_of :base_currency
38
+ validates_presence_of :starting_on
39
+ validates_presence_of :ending_on
40
+ validates_presence_of :contact_email
41
+ validates_presence_of :event_style
42
+ validates :title, presence_default_locale: true
43
+ validates :description, liquid: { locales: true }, presence_default_locale: true
44
+ validates :sidebar, liquid: { locales: true }
45
+
46
+ # validates_presence_of :deadline_on
47
+
48
+ default_scope { where(account_id: Account.current.id) }
49
+
50
+ #--- upcoming and past are used in the admin, so should be published and non-published
51
+ scope :upcoming, -> { where('ending_on > ? AND archived_on IS NULL', (Date.today - 1).to_s).order('starting_on DESC').includes(:translations) }
52
+ scope :past, -> { where('ending_on <= ? AND archived_on IS NULL', (Date.today - 1).to_s).order('starting_on DESC').includes(:translations) }
53
+
54
+ #--- available is list of published and registration open and not ended
55
+ scope :available, -> { where(published: true).where('ending_on > ? AND deadline_on > ? AND archived_on IS NULL',
56
+ (Date.today - 1).to_s, (Date.today - 1).to_s).order('starting_on ASC') }
57
+
58
+ scope :published, -> { where(published: true).where('archived_on IS NULL') }
59
+
60
+ #--- don't use allow_nil, as this will erase the base_currency field if no funding_goal is set
61
+ monetize :funding_goal_cents, with_model_currency: :base_currency
62
+
63
+ EVENT_STYLES = [['Workshop', 'workshop'], ['Crowdfunding', 'crowdfunding']]
64
+
65
+ #------------------------------------------------------------------------------
66
+ def model_slug
67
+ send("title_#{Account.current.preferred_default_locale}")
68
+ end
69
+
70
+ # If the total_available is nil, then there are unlimited tickets to be sold.
71
+ # Otherwise, check if we have sold out
72
+ #------------------------------------------------------------------------------
73
+ def price_sold_out?(workshop_price)
74
+ # p.sold_out?(@workshop.event_registration.number_of(:registrations_by_paymenttype, payment_id: p.id)
75
+ false # TODO
76
+ end
77
+
78
+ # Is this workshop in the past?
79
+ #------------------------------------------------------------------------------
80
+ def past?
81
+ ending_on < Time.now
82
+ end
83
+
84
+ #------------------------------------------------------------------------------
85
+ def show_social_buttons?
86
+ preferred_show_social_buttons?
87
+ end
88
+
89
+ # Is the registration closed? If deadline is null, then registration is open ended
90
+ #------------------------------------------------------------------------------
91
+ def registration_closed?
92
+ !published? || (deadline_on ? (deadline_on < Time.now.to_date) : false)
93
+ end
94
+
95
+ #------------------------------------------------------------------------------
96
+ def published?
97
+ published
98
+ end
99
+
100
+ # toggle the archive state of the workshop
101
+ #------------------------------------------------------------------------------
102
+ def toggle_archive
103
+ archived_on ? update_attribute(:archived_on, nil) : update_attribute(:archived_on, Time.now)
104
+ end
105
+
106
+ #------------------------------------------------------------------------------
107
+ def archived?
108
+ self.archived_on ? true : false
109
+ end
110
+
111
+ #------------------------------------------------------------------------------
112
+ def crowdfunding?
113
+ self.event_style == 'crowdfunding'
114
+ end
115
+
116
+ # Return financial summary and details
117
+ # level => :summary or :detail
118
+ #------------------------------------------------------------------------------
119
+ def financial_details(level = :detail)
120
+ #--- pick currency of first price
121
+ financials = {summary: { total_possible: Money.new(0, base_currency), total_possible_worst: Money.new(0, base_currency),
122
+ total_paid: Money.new(0, base_currency), total_outstanding: Money.new(0, base_currency),
123
+ total_outstanding_worst: Money.new(0, base_currency), total_discounts: Money.new(0, base_currency),
124
+ total_paid_percent: 0},
125
+ collected: {},
126
+ collected_monthly: {},
127
+ payment_type: {},
128
+ projected: {}
129
+ }
130
+
131
+ registrations.attending.includes(:workshop_price).each do |registration|
132
+ if registration.workshop_price
133
+ #--- Calculate the summary values
134
+ financials[:summary][:total_possible] += registration.discounted_price
135
+ financials[:summary][:total_paid] += registration.amount_paid.nil? ? Money.new(0, base_currency) : registration.amount_paid
136
+ financials[:summary][:total_outstanding] += registration.balance_owed
137
+ financials[:summary][:total_discoutns] += registration.discount
138
+ end
139
+ end
140
+
141
+ if level == :detail
142
+ registrations.attending.includes(:workshop_price, :payment_histories).each do |registration|
143
+ if registration.workshop_price
144
+ #--- Calculate what has been collected, by payment method
145
+ registration.payment_histories.each do |payment_history|
146
+ payment_method = payment_history.payment_method.titlecase
147
+ financials[:collected]["#{payment_method}"] = Money.new(0, base_currency) if financials[:collected]["#{payment_method}"].nil?
148
+ financials[:collected]["#{payment_method}"] += payment_history.total
149
+
150
+ month = payment_history.payment_date.beginning_of_month
151
+ financials[:collected_monthly][month] = Money.new(0, base_currency) if financials[:collected_monthly][month].nil?
152
+ financials[:collected_monthly][month] += payment_history.total
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ #--- give a worst case value - reduce by 20%
159
+ financials[:summary][:total_possible_worst] = financials[:summary][:total_possible] * 0.80
160
+ financials[:summary][:total_outstanding_worst] = financials[:summary][:total_outstanding] * 0.80
161
+ financials[:summary][:total_paid_percent] = (100 * financials[:summary][:total_paid] / financials[:summary][:total_possible]).round if financials[:summary][:total_possible].positive?
162
+ return financials
163
+ end
164
+
165
+ #------------------------------------------------------------------------------
166
+ def total_paid
167
+ total_paid = Money.new(0, base_currency)
168
+ registrations.attending.each do |registration|
169
+ if registration.workshop_price
170
+ total_paid += registration.amount_paid.nil? ? Money.new(0, base_currency) : registration.amount_paid
171
+ end
172
+ end
173
+ return total_paid
174
+ end
175
+
176
+ # Send out payment reminder emails to unpaid attendees, or to a specific one.
177
+ # if a specific registration, then always send out the email
178
+ #------------------------------------------------------------------------------
179
+ def send_payment_reminder_emails(registration_id = 'all')
180
+ success = failed = 0
181
+ unpaid_list = ( registration_id == 'all' ? registrations.unpaid : registrations.unpaid.where(id: registration_id) )
182
+ unpaid_list.each do |registration|
183
+ if registration.payment_reminder_due? || registration_id != 'all'
184
+ email = PaymentReminderMailer.payment_reminder(registration).deliver_now
185
+ if email
186
+ registration.update_attribute(:payment_reminder_sent_on, Time.now)
187
+ registration.update_attribute(:payment_reminder_history, [Time.now] + registration.payment_reminder_history)
188
+ success += 1
189
+ else
190
+ failed += 1
191
+ end
192
+ end
193
+ end
194
+ return {success: success, failed: failed}
195
+ end
196
+
197
+
198
+ # is the passed in user attending? Used in some deep level authorization checks,
199
+ # which rely on the "member?" method.
200
+ # This does not consider a userless registration as a "member", since there is
201
+ # no way they can login
202
+ #------------------------------------------------------------------------------
203
+ def member?(user)
204
+ self.registrations.attending.where(user_profile_id: user.user_profile.id).count > 0
205
+ end
206
+
207
+ # Provide a list of users that are members (does not include userless registrations)
208
+ #------------------------------------------------------------------------------
209
+ def member_count
210
+ self.registrations.attending.joins(user_profile: [:user]).references(:user_profile).where('user_profiles.user_id IS NOT NULL').count
211
+ end
212
+
213
+ # Return list of Users that are attending (does not include userless registrations)
214
+ #------------------------------------------------------------------------------
215
+ def member_list
216
+ User.includes(:user_profile).references(:user_profile).where(user_profiles: { id: self.registrations.attending.map(&:user_profile_id) } )
217
+ end
218
+
219
+ #------------------------------------------------------------------------------
220
+ def header_image(default = nil)
221
+ self.image || default
222
+ end
223
+
224
+ #------------------------------------------------------------------------------
225
+ def header_accent_color(default = '')
226
+ self.preferred_header_accent_color || default
227
+ end
228
+
229
+ # Find list of newly createdusers that have not registered for any events, between
230
+ # the end of the workshop and up to 60 day before the start of the workshop.
231
+ # Not perfect, since people can register just to access special
232
+ # content. But gives rough idea of people creating an account but not realizing
233
+ # they need to register for the event they want to participate in.
234
+ #------------------------------------------------------------------------------
235
+ def lost_users(days_ago = 10)
236
+ lost = []
237
+ new_users = User.where(created_at: (self.starting_on - days_ago.day)..self.ending_on, account_id: Account.current.id)
238
+ new_users.each do |user|
239
+ if user.user_site_profiles.where(account_id: Account.current.id)
240
+ if user.user_profile.registrations.count == 0
241
+ lost << user
242
+ end
243
+ end
244
+ end
245
+ lost
246
+ end
247
+ end
@@ -0,0 +1,118 @@
1
+ # [todo] re-evaluate whether alternate currencies are really needed. I can't
2
+ # think of a real use case at the moment, and it adds unnecessary complexity.
3
+ #
4
+ # Note: The currency of a WorkshopPrice is the base currency of it's workshop.
5
+ # If they were different, we would need to always have an exchange rate available
6
+ # to convert to/from the workshop and the price currency.
7
+ # With the alternate price/currencies, the exchange rate is directly based on
8
+ # the prices in the two currencies.
9
+ #------------------------------------------------------------------------------
10
+ class WorkshopPrice < ActiveRecord::Base
11
+
12
+ self.table_name = 'ems_workshop_prices'
13
+
14
+ belongs_to :workshop
15
+ has_many :registrations
16
+
17
+ default_scope { where(account_id: Account.current.id).order('row_order ASC') }
18
+
19
+ # --- globalize
20
+ translates :price_description, :sub_description, :payment_details, fallbacks_for_empty_translations: true
21
+ globalize_accessors locales: DmCore::Language.language_array
22
+
23
+ monetize :price_cents, with_model_currency: :price_currency, allow_nil: true
24
+ monetize :alt1_price_cents, with_model_currency: :alt1_price_currency, allow_nil: true
25
+ monetize :alt2_price_cents, with_model_currency: :alt2_price_currency, allow_nil: true
26
+
27
+ include RankedModel
28
+ ranks :row_order, with_same: :workshop_id
29
+
30
+ validates :price_description, presence_default_locale: true
31
+ validates :payment_details, liquid: { locales: true }
32
+ validates_presence_of :price_currency, if: Proc.new { |w| w.price_cents }
33
+ validates_presence_of :alt1_price_currency, if: Proc.new { |w| w.alt1_price_cents }
34
+ validates_presence_of :alt2_price_currency, if: Proc.new { |w| w.alt2_price_cents }
35
+ validates_presence_of :recurring_period, if: Proc.new { |w| w.recurring_number }
36
+ validates_presence_of :recurring_number, if: Proc.new { |w| w.recurring_period }
37
+
38
+ PAYMENT_METHODS = ['Cash', 'Check', 'Credit Card', 'Money Order', 'PayPal', 'Wire Transfer']
39
+
40
+ # For some reason, the initial monetized price gets created with the default
41
+ # Money currency. Need to use the current currency, as the internal fractional
42
+ # value depends on it. For example,
43
+ # "15000".to_money('JPY').cents == 15000
44
+ # "15000".to_money('EUR').cents == 1500000
45
+ # Call this method on the attributes before passing into new() or update_attributes()
46
+ #------------------------------------------------------------------------------
47
+ def self.prepare_prices(attributes = {})
48
+ attributes['price'] = attributes['price'].to_money(attributes['price_currency']) if attributes['price'].present? && attributes['price_currency'].present?
49
+ attributes['alt1_price'] = attributes['alt1_price'].to_money(attributes['alt1_price_currency']) if attributes['alt1_price'].present? && attributes['alt1_price_currency'].present?
50
+ attributes['alt2_price'] = attributes['alt2_price'].to_money(attributes['alt2_price_currency']) if attributes['alt2_price'].present? && attributes['alt2_price_currency'].present?
51
+ return attributes
52
+ end
53
+
54
+ #------------------------------------------------------------------------------
55
+ def visible?
56
+ !(disabled? || (!valid_starting_on.nil? && valid_starting_on > Time.now.to_date) || (!valid_until.nil? && valid_until < Time.now.to_date))
57
+ end
58
+
59
+ # If the total_available is nil, then there are unlimited tickets to be sold.
60
+ # Otherwise, check if we have sold out
61
+ #------------------------------------------------------------------------------
62
+ def sold_out?(num_sold)
63
+ (total_available.blank? or total_available == 0) ? false : (num_sold >= total_available)
64
+ end
65
+
66
+ #------------------------------------------------------------------------------
67
+ def price_formatted
68
+ price.nil? ? '' : price.format(no_cents_if_whole: true, symbol: true)
69
+ end
70
+
71
+ # returns the amount of a payment
72
+ #------------------------------------------------------------------------------
73
+ def payment_price
74
+ recurring_payments? ? (price / recurring_number) : price
75
+ end
76
+
77
+ #------------------------------------------------------------------------------
78
+ def recurring_payments?
79
+ recurring_number.to_i > 1
80
+ end
81
+
82
+ #------------------------------------------------------------------------------
83
+ def currency_list
84
+ list = [[price_currency, price_currency]]
85
+ list << [alt1_price_currency, alt1_price_currency] unless alt1_price_currency.blank?
86
+ list << [alt2_price_currency, alt1_price_currency] unless alt1_price_currency.blank?
87
+ return list
88
+ end
89
+
90
+ # Convert an amount in an alternate currency into the base currency
91
+ #------------------------------------------------------------------------------
92
+ def to_base_currency(money)
93
+ bank.exchange_with(money, price_currency)
94
+ end
95
+
96
+ # return a bank object filled with the exchange rates, based on the prices.
97
+ # then you can do: bank.exchange_with(price, 'USD')
98
+ #------------------------------------------------------------------------------
99
+ def bank
100
+ unless @bank
101
+ @bank = Money::Bank::VariableExchange.new
102
+ unless alt1_price_currency.blank?
103
+ @bank.add_rate(price_currency, alt1_price_currency, alt1_price_cents.to_f / price_cents.to_f)
104
+ @bank.add_rate(alt1_price_currency, price_currency, price_cents.to_f / alt1_price_cents.to_f)
105
+ end
106
+ unless alt2_price_currency.blank?
107
+ @bank.add_rate(price_currency, alt2_price_currency, alt2_price_cents.to_f / price_cents.to_f)
108
+ @bank.add_rate(alt2_price_currency, price_currency, price_cents.to_f / alt2_price_cents.to_f)
109
+ end
110
+ unless alt1_price_currency.blank? && alt2_price_currency.blank?
111
+ @bank.add_rate(alt1_price_currency, alt2_price_currency, alt2_price_cents.to_f / alt1_price_cents.to_f)
112
+ @bank.add_rate(alt2_price_currency, alt1_price_currency, alt1_price_cents.to_f / alt2_price_cents.to_f)
113
+ end
114
+ end
115
+ return @bank
116
+ end
117
+
118
+ end