dm_event 4.2.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +23 -0
- data/README.md +33 -0
- data/Rakefile +36 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/at/icon-64x44.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/be/icon-64x44.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/ch/icon-64x44.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/de/icon-64x44.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/es/icon-64x44.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/fr/icon-64x44.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/it/icon-64x44.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/nl/icon-64x44.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/pl/icon-64x44.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/100x38.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/200x75.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/250x94.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/28x7.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/320x120.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/42x11.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/75x28.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-32x22.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-32x24.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-48x32.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-48x34.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-64x42.png +0 -0
- data/app/assets/images/dm_event/payment_logos/sofort/uk/icon-64x44.png +0 -0
- data/app/assets/javascripts/dm_event/admin.js +94 -0
- data/app/assets/stylesheets/dm_event/admin.css +152 -0
- data/app/assets/stylesheets/dm_event/application.css +65 -0
- data/app/controllers/dm_event/admin/admin_controller.rb +13 -0
- data/app/controllers/dm_event/admin/registrations_controller.rb +411 -0
- data/app/controllers/dm_event/admin/workshop_prices_controller.rb +73 -0
- data/app/controllers/dm_event/admin/workshops_controller.rb +147 -0
- data/app/controllers/dm_event/application_controller.rb +23 -0
- data/app/controllers/dm_event/payments_controller.rb +83 -0
- data/app/controllers/dm_event/registrations_controller.rb +368 -0
- data/app/datatables/registration_datatable.rb +117 -0
- data/app/helpers/dm_event/application_helper.rb +4 -0
- data/app/helpers/dm_event/registrations_helper.rb +14 -0
- data/app/helpers/dm_event/workshops_helper.rb +41 -0
- data/app/mailers/payment_reminder_mailer.rb +25 -0
- data/app/mailers/registration_notify_mailer.rb +31 -0
- data/app/models/dm_event/concerns/ability.rb +38 -0
- data/app/models/dm_event/concerns/registration_state_email.rb +80 -0
- data/app/models/dm_event/concerns/registration_state_machine.rb +218 -0
- data/app/models/dm_event/concerns/user_profile.rb +20 -0
- data/app/models/dm_event/model_decorators.rb +6 -0
- data/app/models/dm_event/permitted_params.rb +35 -0
- data/app/models/payment.rb +48 -0
- data/app/models/registration.rb +297 -0
- data/app/models/workshop.rb +247 -0
- data/app/models/workshop_price.rb +118 -0
- data/app/presenters/event_common_presenter.rb +31 -0
- data/app/presenters/registration_presenter.rb +34 -0
- data/app/presenters/workshop_presenter.rb +22 -0
- data/app/views/dm_event/admin/registrations/_form.html.erb +297 -0
- data/app/views/dm_event/admin/registrations/action_state.js.erb +1 -0
- data/app/views/dm_event/admin/registrations/edit.html.erb +1 -0
- data/app/views/dm_event/admin/registrations/index.html.erb +0 -0
- data/app/views/dm_event/admin/workshop_prices/_form.html.erb +62 -0
- data/app/views/dm_event/admin/workshop_prices/edit.html.erb +2 -0
- data/app/views/dm_event/admin/workshop_prices/index.html.erb +31 -0
- data/app/views/dm_event/admin/workshop_prices/new.html.erb +2 -0
- data/app/views/dm_event/admin/workshops/_form.html.erb +61 -0
- data/app/views/dm_event/admin/workshops/_header_menu.html.erb +21 -0
- data/app/views/dm_event/admin/workshops/_registration_stats.html.erb +58 -0
- data/app/views/dm_event/admin/workshops/additional_configuration.html.erb +47 -0
- data/app/views/dm_event/admin/workshops/edit.html.erb +2 -0
- data/app/views/dm_event/admin/workshops/edit_system_email.html.erb +75 -0
- data/app/views/dm_event/admin/workshops/financials.html.erb +179 -0
- data/app/views/dm_event/admin/workshops/index.html.erb +100 -0
- data/app/views/dm_event/admin/workshops/lost_users.html.erb +44 -0
- data/app/views/dm_event/admin/workshops/new.html.erb +2 -0
- data/app/views/dm_event/admin/workshops/show.html.erb +47 -0
- data/app/views/dm_event/admin/workshops/user_outstanding_balances.html.erb +29 -0
- data/app/views/dm_event/liquid_tags/_funding_project_status.html.erb +27 -0
- data/app/views/dm_event/payments/_registrations_paypal_standard.html.erb +23 -0
- data/app/views/dm_event/payments/_registrations_sofort.html.erb +18 -0
- data/app/views/dm_event/registrations/_section_address.html.erb +30 -0
- data/app/views/dm_event/registrations/_section_custom_fields.html.erb +7 -0
- data/app/views/dm_event/registrations/_section_prices.html.erb +33 -0
- data/app/views/dm_event/registrations/_workshop_price.html.erb +18 -0
- data/app/views/dm_event/registrations/choose_payment.html.erb +5 -0
- data/app/views/dm_event/registrations/closed.html.erb +14 -0
- data/app/views/dm_event/registrations/new.html.erb +30 -0
- data/app/views/dm_event/registrations/success.html.erb +15 -0
- data/app/views/layouts/email_templates/dm_event_email_layout.html.erb +318 -0
- data/app/views/layouts/email_templates/dm_event_email_layout.text.erb +1 -0
- data/app/views/layouts/email_templates/dm_event_payment_reminder.html.erb +22 -0
- data/app/views/layouts/email_templates/dm_event_payment_reminder.text.erb +19 -0
- data/app/views/layouts/email_templates/dm_event_registration_notify.html.erb +21 -0
- data/app/views/layouts/email_templates/dm_event_registration_notify.text.erb +13 -0
- data/config/initializers/dm_event.rb +1 -0
- data/config/initializers/liquid_init.rb +2 -0
- data/config/locales/ems.cs.yml +51 -0
- data/config/locales/ems.de.yml +51 -0
- data/config/locales/ems.en.yml +51 -0
- data/config/locales/ems.fi.yml +51 -0
- data/config/locales/ems.ja.yml +51 -0
- data/config/routes.rb +40 -0
- data/db/migrate/20130510155617_create_events.rb +98 -0
- data/db/migrate/20130516124327_add_locale_to_registration.rb +5 -0
- data/db/migrate/20130516204454_add_user_profile_to_registration.rb +11 -0
- data/db/migrate/20130518092324_add_require_address.rb +7 -0
- data/db/migrate/20130609145812_add_workshop_published.rb +5 -0
- data/db/migrate/20130624134506_add_amount_currency.rb +16 -0
- data/db/migrate/20130703131515_add_workshop_currency.rb +5 -0
- data/db/migrate/20130913083529_add_event_funding_type.rb +6 -0
- data/db/migrate/20130914105736_add_enable_payments.rb +5 -0
- data/db/migrate/20130916160033_add_sidebar_to_workshop.rb +5 -0
- data/db/migrate/20130917161700_add_show_address.rb +5 -0
- data/db/migrate/20131116180719_add_default_funding_goal.rb +8 -0
- data/db/migrate/20131127165445_add_payment_reminder.rb +5 -0
- data/db/migrate/20140122204702_add_event_image.rb +5 -0
- data/db/migrate/20140523165342_add_workshop_summary.rb +5 -0
- data/db/migrate/20140605052208_add_uuid_to_registration.rb +10 -0
- data/db/migrate/20140707152124_add_registration_comment_count.rb +5 -0
- data/db/migrate/20140708142312_add_payment_comment.rb +20 -0
- data/db/migrate/20150709125105_add_payment_reminder_history.rb +16 -0
- data/db/migrate/20160810081542_add_bcc_contact_email.rb +5 -0
- data/db/migrate/20160821150120_index_foreign_keys_in_ems_registrations.rb +9 -0
- data/db/migrate/20160821150121_index_foreign_keys_in_ems_workshop_price_translations.rb +5 -0
- data/db/migrate/20160821150122_index_foreign_keys_in_ems_workshop_prices.rb +6 -0
- data/db/migrate/20160821150123_index_foreign_keys_in_ems_workshop_translations.rb +5 -0
- data/db/migrate/20160821150124_index_foreign_keys_in_ems_workshops.rb +6 -0
- data/lib/dm_event/engine.rb +20 -0
- data/lib/dm_event/liquid/tags/funding_project_status.rb +55 -0
- data/lib/dm_event.rb +4 -0
- data/lib/tasks/dm_event_tasks.rake +4 -0
- data/spec/controllers/registrations_controller_spec.rb +152 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/ability.rb +10 -0
- data/spec/dummy/app/models/user.rb +6 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +26 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +82 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +11 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20141114170927_add_globalize_countries.dm_core.rb +50 -0
- data/spec/dummy/db/migrate/20141114170928_devise_create_users.dm_core.rb +46 -0
- data/spec/dummy/db/migrate/20141114170929_add_user_fields.dm_core.rb +14 -0
- data/spec/dummy/db/migrate/20141114170930_rolify_create_roles.dm_core.rb +20 -0
- data/spec/dummy/db/migrate/20141114170931_add_last_access.dm_core.rb +10 -0
- data/spec/dummy/db/migrate/20141114170932_create_versions.dm_core.rb +19 -0
- data/spec/dummy/db/migrate/20141114170933_add_object_changes_column_to_versions.dm_core.rb +12 -0
- data/spec/dummy/db/migrate/20141114170934_create_dm_core_accounts.dm_core.rb +13 -0
- data/spec/dummy/db/migrate/20141114170935_add_account_to_users.dm_core.rb +9 -0
- data/spec/dummy/db/migrate/20141114170936_create_preferences.dm_core.rb +13 -0
- data/spec/dummy/db/migrate/20141114170937_create_comments.dm_core.rb +22 -0
- data/spec/dummy/db/migrate/20141114170938_add_activity.dm_core.rb +21 -0
- data/spec/dummy/db/migrate/20141114170939_add_type_to_comments.dm_core.rb +9 -0
- data/spec/dummy/db/migrate/20141114170940_add_category.dm_core.rb +28 -0
- data/spec/dummy/db/migrate/20141114170941_create_email_table.dm_core.rb +26 -0
- data/spec/dummy/db/migrate/20141114170942_add_user_profile.dm_core.rb +46 -0
- data/spec/dummy/db/migrate/20141114170943_add_profile_email.dm_core.rb +14 -0
- data/spec/dummy/db/migrate/20141114170944_create_payment_history.dm_core.rb +37 -0
- data/spec/dummy/db/migrate/20141114170945_change_anchor_field.dm_core.rb +10 -0
- data/spec/dummy/db/migrate/20141114170946_create_user_site_profile.dm_core.rb +27 -0
- data/spec/dummy/db/migrate/20141114170947_add_avatar.dm_core.rb +12 -0
- data/spec/dummy/db/migrate/20141114170948_add_notify_to_payment_history.dm_core.rb +8 -0
- data/spec/dummy/db/migrate/20141114170949_acts_as_votable_migration.dm_core.rb +28 -0
- data/spec/dummy/db/migrate/20141114170950_add_user_site_profile_uuid.dm_core.rb +19 -0
- data/spec/dummy/db/migrate/20141114170951_add_invoice_id.dm_core.rb +7 -0
- data/spec/dummy/db/migrate/20141114170952_acts_as_follower_migration.dm_core.rb +18 -0
- data/spec/dummy/db/migrate/20141114170953_rename_invoice_id.dm_core.rb +12 -0
- data/spec/dummy/db/migrate/20141114170954_add_core_addresses.dm_core.rb +18 -0
- data/spec/dummy/db/migrate/20141114170955_papertrail_increase_column.dm_core.rb +9 -0
- data/spec/dummy/db/migrate/20141114170956_acts_as_taggable_on_migration.dm_core.rb +32 -0
- data/spec/dummy/db/migrate/20141114170957_add_missing_unique_indices.dm_core.rb +23 -0
- data/spec/dummy/db/migrate/20141114170958_add_taggings_counter_cache_to_tags.dm_core.rb +16 -0
- data/spec/dummy/db/migrate/20141114170959_create_custom_fields.dm_core.rb +40 -0
- data/spec/dummy/db/migrate/20141114170960_add_missing_taggable_index.dm_core.rb +11 -0
- data/spec/dummy/db/migrate/20141119103740_create_cms.dm_cms.rb +92 -0
- data/spec/dummy/db/migrate/20141119103741_add_account_to_cms.dm_cms.rb +11 -0
- data/spec/dummy/db/migrate/20141119103742_create_blog.dm_cms.rb +62 -0
- data/spec/dummy/db/migrate/20141119103743_add_notification_sent.dm_cms.rb +6 -0
- data/spec/dummy/db/migrate/20141119103744_add_blog_comment.dm_cms.rb +8 -0
- data/spec/dummy/db/migrate/20141119103745_add_blog_image.dm_cms.rb +6 -0
- data/spec/dummy/db/migrate/20141119103746_rename_snippet_slug.dm_cms.rb +14 -0
- data/spec/dummy/db/migrate/20141119103747_add_requires_subscription_blog.dm_cms.rb +8 -0
- data/spec/dummy/db/migrate/20141119103748_add_pages_ranked_model.dm_cms.rb +23 -0
- data/spec/dummy/db/migrate/20141119103749_add_blog_owner.dm_cms.rb +7 -0
- data/spec/dummy/db/migrate/20141119103750_create_media_files.dm_cms.rb +30 -0
- data/spec/dummy/db/migrate/20141119103751_add_cmspage_summary.dm_cms.rb +7 -0
- data/spec/dummy/db/migrate/20141119103752_add_blog_image_email_header.dm_cms.rb +6 -0
- data/spec/dummy/db/migrate/20141119103753_add_header_image.dm_cms.rb +10 -0
- data/spec/dummy/db/migrate/20160128154135_add_favored_locale.dm_core.rb +17 -0
- data/spec/dummy/db/migrate/20160128154136_update_papertrail_v4.dm_core.rb +41 -0
- data/spec/dummy/db/schema.rb +677 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/account_factory.rb +7 -0
- data/spec/factories/registration_factory.rb +13 -0
- data/spec/factories/workshop_factory.rb +25 -0
- data/spec/factories/workshop_price_factory.rb +19 -0
- data/spec/models/registration_spec.rb +83 -0
- data/spec/models/workshop_spec.rb +26 -0
- data/spec/rails_helper.rb +70 -0
- data/spec/spec_helper.rb +85 -0
- data/spec/support/accounts.rb +17 -0
- data/spec/support/devise.rb +44 -0
- data/spec/support/fix_locale.rb +57 -0
- 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
|