dm_event 4.2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|