diff --git a/.travis.yml b/.travis.yml index 3929e02b..35ee3ee5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,4 +21,5 @@ deploy: run: - rake db:schema:load - rake db:seed + - rake db:populate_sample_data - restart diff --git a/Gemfile b/Gemfile index 5960abcf..5684c25b 100644 --- a/Gemfile +++ b/Gemfile @@ -80,6 +80,7 @@ gem 'coveralls', require: false # pdf generation gem 'prawn' +gem 'prawn-table' # Simple, Heroku-friendly Rails app configuration using ENV and a single YAML file gem 'figaro' @@ -119,7 +120,7 @@ group :development do # Add a comment summarizing the current schema for models and others # usage: annotate gem 'annotate' - + # opens sent emails in a new browser tab # gem "letter_opener" end @@ -134,8 +135,9 @@ group :test do gem 'parser', '~> 2.2.2.5' # Stubbing external calls by blocking traffic with WebMock.disable_net_connect! or allow: # gem 'webmock' - gem 'pdf-inspector', require: "pdf/inspector" -end + + # PDF testing + gem 'pdf-inspector', require: "pdf/inspector"end group :production do # Use Puma web server diff --git a/Gemfile.lock b/Gemfile.lock index ffd2319b..68e75cc3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -40,7 +40,8 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.4.0) + addressable (2.5.0) + public_suffix (~> 2.0, >= 2.0.2) afm (0.2.2) airbrake (5.6.1) airbrake-ruby (~> 1.6) @@ -65,7 +66,7 @@ GEM builder (3.2.2) byebug (9.0.6) cancancan (1.15.0) - capybara (2.10.1) + capybara (2.11.0) addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -73,14 +74,15 @@ GEM rack-test (>= 0.5.4) xpath (~> 2.0) cliver (0.3.2) - codeclimate-test-reporter (1.0.1) + codeclimate-test-reporter (1.0.3) + simplecov coderay (1.1.1) commonjs (0.2.7) concurrent-ruby (1.0.2) - coveralls (0.8.15) + coveralls (0.8.16) json (>= 1.8, < 3) simplecov (~> 0.12.0) - term-ansicolor (~> 1.3) + term-ansicolor (~> 1.3.0) thor (~> 0.19.1) tins (>= 1.6.0, < 2) database_cleaner (1.5.3) @@ -122,7 +124,7 @@ GEM jquery-turbolinks (2.1.0) railties (>= 3.1.0) turbolinks - jquery-ui-rails (5.0.5) + jquery-ui-rails (6.0.1) railties (>= 3.2.16) json (1.8.3) less (2.6.0) @@ -141,7 +143,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mini_portile2 (2.1.0) - minitest (5.9.1) + minitest (5.10.1) nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) orm_adapter (0.5.0) @@ -165,14 +167,17 @@ GEM prawn (2.1.0) pdf-core (~> 0.6.1) ttfunk (~> 1.4.0) + prawn-table (0.2.2) + prawn (>= 1.3.0, < 3.0.0) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) pry-rails (0.3.4) pry (>= 0.9.10) - puma (3.6.0) - rack (1.6.4) + public_suffix (2.0.4) + puma (3.6.2) + rack (1.6.5) rack-openid (1.4.2) rack (>= 1.1.0) ruby-openid (>= 2.1.8) @@ -253,12 +258,12 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.12) - term-ansicolor (1.4.0) + term-ansicolor (1.3.2) tins (~> 1.0) - thor (0.19.1) + thor (0.19.4) thread_safe (0.3.5) tilt (2.0.5) - tins (1.12.0) + tins (1.13.0) ttfunk (1.4.0) turbolinks (5.0.1) turbolinks-source (~> 5) @@ -315,6 +320,7 @@ DEPENDENCIES pg poltergeist prawn + prawn-table pry pry-rails puma diff --git a/app/controllers/application_letters_controller.rb b/app/controllers/application_letters_controller.rb index 084703de..040d0df3 100644 --- a/app/controllers/application_letters_controller.rb +++ b/app/controllers/application_letters_controller.rb @@ -6,7 +6,7 @@ class ApplicationLettersController < ApplicationController # GET /applications def index - @application_letters = ApplicationLetter.all + @application_letters = ApplicationLetter.where(user_id: current_user.id) end # GET /applications/1 @@ -43,7 +43,7 @@ def create @application_letter.user_id = current_user.id if @application_letter.save - redirect_to @application_letter, notice: 'Application was successfully created.' + redirect_to @application_letter, notice: I18n.t('application_letters.successful_creation') else render :new end @@ -52,7 +52,16 @@ def create # PATCH/PUT /applications/1 def update if @application_letter.update_attributes(application_params) - redirect_to :back, notice: 'Application was successfully updated.' rescue ActionController::RedirectBackError redirect_to root_path + redirect_to :back, notice: I18n.t('application_letters.successful_update') rescue ActionController::RedirectBackError redirect_to root_path + else + render :edit + end + end + + # PATCH/PUT /applications/1/status + def update_status + if @application_letter.update_attributes(application_status_param) + redirect_to :back, notice: I18n.t('application_letters.successful_update') rescue ActionController::RedirectBackError redirect_to root_path else render :edit end @@ -61,7 +70,7 @@ def update # DELETE /applications/1 def destroy @application_letter.destroy - redirect_to application_letters_url, notice: 'Application was successfully destroyed.' + redirect_to application_letters_url, notice: I18n.t('application_letters.successful_deletion') end private @@ -73,6 +82,11 @@ def set_application # Only allow a trusted parameter "white list" through. # Don't allow user_id as you shouldn't be able to set the user from outside of create/update. def application_params - params.require(:application_letter).permit(:grade, :experience, :motivation, :coding_skills, :emergency_number, :vegeterian, :vegan, :allergic, :allergies, :user_id, :event_id, :status) + params.require(:application_letter).permit(:grade, :experience, :motivation, :coding_skills, :emergency_number, :vegeterian, :vegan, :allergic, :allergies, :user_id, :event_id) + end + + # Only allow to update the status + def application_status_param + params.require(:application_letter).permit(:status) end end diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 87b05d4a..c4eb82fa 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -1,5 +1,7 @@ +require 'pdf_generation/applications_pdf' + class EventsController < ApplicationController - before_action :set_event, only: [:show, :edit, :update, :destroy] + before_action :set_event, only: [:show, :edit, :update, :destroy, :print_applications] # GET /events def index @@ -84,6 +86,12 @@ def participants @participants = @event.participants_by_agreement_letter end + # GET /events/1/print_applications + def print_applications + authorize! :print_applications, @event + pdf = ApplicationsPDF.generate(@event) + send_data pdf, filename: "applications_#{@event.name}_#{Date.today}.pdf", type: "application/pdf", disposition: "inline" + end # GET /events/1/send-acceptances-email def send_acceptance_emails event = Event.find(params[:id]) diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 97eb7d77..6d3ae0f2 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -29,9 +29,9 @@ def create if @profile.save if flash[:event_id] - redirect_to new_application_letter_path(:event_id => flash[:event_id]) + redirect_to new_application_letter_path(:event_id => flash[:event_id]), notice: I18n.t('profiles.successful_creation') else - redirect_to @profile + redirect_to @profile, notice: I18n.t('profiles.successful_creation') end else render :new @@ -41,7 +41,7 @@ def create # PATCH/PUT /profiles/1 def update if @profile.update(profile_params) - redirect_to @profile, notice: 'Profile was successfully updated.' + redirect_to @profile, notice: I18n.t('profiles.successful_update') else render :edit end @@ -50,7 +50,7 @@ def update # DELETE /profiles/1 def destroy @profile.destroy - redirect_to profiles_url, notice: 'Profile was successfully destroyed.' + redirect_to profiles_url, notice: I18n.t('profiles.successful_deletion') end private diff --git a/app/models/ability.rb b/app/models/ability.rb index 42e55e3f..68725950 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -42,17 +42,21 @@ def initialize(user) can [:index, :show, :edit, :update, :destroy], ApplicationLetter, user: { id: user.id } # Pupils can upload their letters of agreement can [:create], AgreementLetter + can [:new, :create], Request end if user.role? :coach # Coaches can view Applications and participants for Event can [:view_applicants, :view_participants], Event can [:view_and_add_notes, :show], ApplicationLetter + can [:print_applications], Event end if user.role? :organizer can [:index, :show], Profile - can [:index, :show, :view_and_add_notes], ApplicationLetter - # Organizers can view and edit Applications and view participants for Events - can [:view_applicants, :edit_applicants, :view_participants], Event + can [:index, :show, :view_and_add_notes, :update_status], ApplicationLetter + cannot :update, ApplicationLetter + # Organizers can view, edit and print Applications, view participants for and manage Events + can [:view_applicants, :edit_applicants, :view_participants, :print_applications, :manage], Event + can :manage, Request end if user.role? :admin can :manage, :all diff --git a/app/models/application_letter.rb b/app/models/application_letter.rb index da35c0b4..e124a4a2 100644 --- a/app/models/application_letter.rb +++ b/app/models/application_letter.rb @@ -27,10 +27,11 @@ class ApplicationLetter < ActiveRecord::Base validate :status_cannot_be_changed, :if => Proc.new { |letter| letter.status_changed?} enum status: {accepted: 1, rejected: 0, pending: 2, alternative: 3} + validates :status, inclusion: { in: statuses.keys } + # Checks if the deadline is over # additionally only return if event and event.application_deadline is present - # TODO: 'event.application_deadline' should never be nil, when #18 is finished. Please remove this in #18. # # @param none # @return [Boolean] true if deadline is over @@ -54,6 +55,27 @@ def deadline_cannot_be_in_the_past end end + # Chooses right status based on status and event deadline + # + # @param none + # @return [String] to display on the website + def status_type + case ApplicationLetter.statuses[status] + when ApplicationLetter.statuses[:accepted] + return I18n.t("application_status.accepted") + when ApplicationLetter.statuses[:rejected] + return I18n.t("application_status.rejected") + when ApplicationLetter.statuses[:pending] + if after_deadline? + return I18n.t("application_status.pending_after_deadline") + else + return I18n.t("application_status.pending_before_deadline") + end + else + return I18n.t("application_status.alternative") + end + end + # Validator for status_change_allowed? # Adds error def status_cannot_be_changed diff --git a/app/models/event.rb b/app/models/event.rb index 4e8e99dc..7b701c1f 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -167,7 +167,7 @@ def date_ranges_attributes=(*args) event.date_ranges.each do |date_range| next if date_range.valid? date_range.errors.full_messages.each do |msg| - errors.add :date_ranges, msg + errors.add :date_ranges, msg unless errors[:date_ranges].include? msg end end end diff --git a/app/views/application_letters/_application_selective.html.erb b/app/views/application_letters/_application_selective.html.erb index 6b770a64..c2c3a012 100644 --- a/app/views/application_letters/_application_selective.html.erb +++ b/app/views/application_letters/_application_selective.html.erb @@ -1,5 +1,5 @@ <% if can? :edit_applicants, Event and not (application_letter.event.application_status_locked == true) %> - <%= form_for :application_letter, url: application_letter_path(application_letter), html: {method: :put} do |f| %> + <%= form_for :application_letter, url: update_application_letter_status_path(application_letter), html: {method: :put} do |f| %>
<% ApplicationLetter.statuses.keys.each do |key| %> diff --git a/app/views/application_letters/index.html.erb b/app/views/application_letters/index.html.erb index 3794c676..6ea46399 100644 --- a/app/views/application_letters/index.html.erb +++ b/app/views/application_letters/index.html.erb @@ -1,38 +1,32 @@ <%- model_class = ApplicationLetter -%> - - - - - - - - + + + + + - <% @application_letters.each do |application| %> - <% if can? :destroy, application %> - - - - - - - - - <% end %> - <% end %> + <% @application_letters.each do |application| %> + + + + + + <% end %>
<%= model_class.human_attribute_name(:id) %><%= model_class.human_attribute_name(:motivation) %><%= model_class.human_attribute_name(:user_id) %><%= model_class.human_attribute_name(:event_id) %><%= model_class.human_attribute_name(:created_at) %><%=t '.actions', :default => t("helpers.actions") %>
<%= Event.model_name.human(:count => model_class.count).titleize %><%= model_class.human_attribute_name(:status) %><%= t '.actions', :default => t("helpers.actions") %>
<%= link_to application.id, application_letter_path(application) %><%= application.motivation %><%= application.user_id %><%= application.event_id %><%=l application.created_at %> - <%= link_to t('.edit', :default => t("helpers.links.edit")), - edit_application_letter_path(application), :class => 'btn btn-default btn-xs' %> - <%= link_to t('.destroy', :default => t("helpers.links.destroy")), - application_letter_path(application), - :method => :delete, - :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, - :class => 'btn btn-xs btn-danger' %> -
<%= link_to application.event.name, event_path(application.event) %><%= application.status_type %> + <% if not application.after_deadline? %> + <%= link_to t('.edit', :default => t("helpers.links.edit")), + edit_application_letter_path(application), :class => 'btn btn-default btn-xs' %> + <%= link_to t('.destroy', :default => t("helpers.links.destroy")), + application_letter_path(application), + :method => :delete, + :data => {:confirm => t('.confirm', :default => t("application_letters.confirm_deletion", :default => 'Are you sure?'))}, + :class => 'btn btn-xs btn-danger' %> + <% end %> +
diff --git a/app/views/application_letters/show.html.erb b/app/views/application_letters/show.html.erb index a2984dcf..57a7db70 100644 --- a/app/views/application_letters/show.html.erb +++ b/app/views/application_letters/show.html.erb @@ -25,7 +25,7 @@ <%= link_to t('.destroy', :default => t("helpers.links.destroy")), application_letter_path(@application_letter), :method => 'delete', - :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, + :data => { :confirm => t('.confirm', :default => t("application_letters.confirm_deletion", :default => 'Are you sure?')) }, :class => 'btn btn-danger' %>

<%= t('.application_title', title: @application_letter.event.name) %>

diff --git a/app/views/events/_applicants_overview.html.erb b/app/views/events/_applicants_overview.html.erb index b2a6d1af..be6bd247 100644 --- a/app/views/events/_applicants_overview.html.erb +++ b/app/views/events/_applicants_overview.html.erb @@ -55,11 +55,11 @@
- <%= link_to "Alle annehmen", + <%= link_to t('.accept_all'), events_path, :class => 'btn btn-default'%> - <%= link_to "Alle drucken", - events_path, :class => 'btn btn-default'%> + <%= link_to t('.print_all'), + print_applications_event_path(@event.id), :target => "_blank", :class => 'btn btn-default'%>
<% if not @event.applications_classified? %> diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index c335f382..4202142f 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -37,7 +37,7 @@
<%= f.label :date_ranges, :class => 'control-label col-lg-2' %> -
+
<% def date_picker_template(start_date = Date.current, end_date = Date.current) start_picker = text_field_tag "event[date_ranges_attributes][][start_date]", (I18n.l start_date), class: 'form-control', :"data-enable-datepicker" => 'true' diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index e7d71f97..9fc69024 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -1,6 +1,6 @@ <%- model_class = Event -%> @@ -22,13 +22,17 @@
<%= event.end_date %> <%= event.draft %> - <%= link_to t('.edit', :default => t('helpers.links.edit')), + <% if can? :edit, event %> + <%= link_to t('.edit', :default => t('helpers.links.edit')), edit_event_path(event), :class => 'btn btn-default btn-xs' %> - <%= link_to t('.destroy', :default => t('helpers.links.destroy')), + <% end %> + <% if can? :destroy, event %> + <%= link_to t('.destroy', :default => t('helpers.links.destroy')), event_path(event), :method => :delete, :data => { :confirm => t('events.confirmation_prompts.confirm_delete', :default => t("helpers.links.confirm", :default => 'Löschen kann nicht rückgängig gemacht werden!')) }, :class => 'btn btn-xs btn-danger' %> + <% end %> <%= link_to t('.new', :default => t('helpers.links.apply')), new_application_letter_path(:event_id => event.id), :class => 'btn btn-xs btn-primary' %> @@ -38,6 +42,8 @@
-<%= link_to t('.new', :default => t('helpers.links.new')), +<% if can? :new, Event %> + <%= link_to t('.new', :default => t('helpers.links.new')), new_event_path, :class => 'btn btn-primary' %> +<% end %> \ No newline at end of file diff --git a/app/views/profiles/index.html.erb b/app/views/profiles/index.html.erb index 6450f242..a6975569 100644 --- a/app/views/profiles/index.html.erb +++ b/app/views/profiles/index.html.erb @@ -24,7 +24,7 @@ <%= link_to t('.destroy', :default => t("helpers.links.destroy")), profile_path(profile), :method => :delete, - :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, + :data => { :confirm => t('.confirm', :default => t("profiles.confirm_deletion", :default => 'Are you sure?')) }, :class => 'btn btn-xs btn-danger' %> diff --git a/app/views/profiles/show.html.erb b/app/views/profiles/show.html.erb index 9c44d4ae..951085f0 100644 --- a/app/views/profiles/show.html.erb +++ b/app/views/profiles/show.html.erb @@ -21,7 +21,7 @@ <%= link_to t('.destroy', :default => t("helpers.links.destroy")), profile_path(@profile), :method => 'delete', - :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, + :data => { :confirm => t('.confirm', :default => t("profiles.confirm_deletion", :default => 'Are you sure?')) }, :class => 'btn btn-danger' %>

Events

diff --git a/app/views/requests/index.html.erb b/app/views/requests/index.html.erb index 1acb4c24..fabf72c2 100644 --- a/app/views/requests/index.html.erb +++ b/app/views/requests/index.html.erb @@ -20,19 +20,25 @@ <%= request.user_id %> <%=l request.created_at %> - <%= link_to t('.edit', :default => t("helpers.links.edit")), + <% if can? :edit, request %> + <%= link_to t('.edit', :default => t("helpers.links.edit")), edit_request_path(request), :class => 'btn btn-default btn-xs' %> - <%= link_to t('.destroy', :default => t("helpers.links.destroy")), + <% end %> + <% if can? :destroy, request %> + <%= link_to t('.destroy', :default => t("helpers.links.destroy")), request_path(request), :method => :delete, :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, :class => 'btn btn-xs btn-danger' %> + <% end %> <% end %> -<%= link_to t('.new', :default => t("helpers.links.new")), +<% if can? :new, Request %> + <%= link_to t('.new', :default => t("helpers.links.new")), new_request_path, :class => 'btn btn-primary' %> +<% end %> \ No newline at end of file diff --git a/app/views/requests/show.html.erb b/app/views/requests/show.html.erb index ffe12b8b..72b328a2 100644 --- a/app/views/requests/show.html.erb +++ b/app/views/requests/show.html.erb @@ -12,10 +12,14 @@ <%= link_to t('.back', :default => t("helpers.links.back")), requests_path, :class => 'btn btn-default' %> -<%= link_to t('.edit', :default => t("helpers.links.edit")), +<% if can? :edit, @request %> + <%= link_to t('.edit', :default => t("helpers.links.edit")), edit_request_path(@request), :class => 'btn btn-default' %> -<%= link_to t('.destroy', :default => t("helpers.links.destroy")), +<% end %> +<% if can? :destroy, @request %> + <%= link_to t('.destroy', :default => t("helpers.links.destroy")), request_path(@request), :method => 'delete', :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, :class => 'btn btn-danger' %> +<% end %> diff --git a/config/locales/de.application_letters.yml b/config/locales/de.application_letters.yml index 6c6e077a..4d9d25a6 100644 --- a/config/locales/de.application_letters.yml +++ b/config/locales/de.application_letters.yml @@ -14,7 +14,14 @@ de: Programmierkenntnisse sind keine Voraussetzung zur Teilnahme. Diese Information wird zur Gruppeneinteilung benötigt." login_before_creation: "Du musst angemeldet sein, um dich für Workshops bewerben zu können." + successful_update: "Bewerbung erfolgreich bearbeitet" + successful_deletion: "Bewerbung erfolgreich gelöscht" + successful_creation: "Bewerbung erfolgreich erstellt" fill_in_profile_before_creation: "Du musst erst dein Profil ausfüllen, um dich für Workshops bewerben zu können." + application_page: + title: "Bewerbung von %{name}" + for: "für %{event}" + confirm_deletion: "Willst du diese Bewerbung wirklich löschen?" activerecord: models: @@ -40,4 +47,6 @@ de: accepted: "Angenommen" rejected: "Abgelehnt" pending: "Ausstehend" + pending_before_deadline: "Beworben" + pending_after_deadline: "In Bearbeitung" alternative: "Nachrücker" diff --git a/config/locales/de.events.yml b/config/locales/de.events.yml index c25ceb60..5fbfa63a 100644 --- a/config/locales/de.events.yml +++ b/config/locales/de.events.yml @@ -16,6 +16,8 @@ de: occupied_places: one: '%{count} Platz belegt' other: '%{count} Plätze belegt' + print_all: "Alle ausdrucken" + accept_all: "Alle annehmen" unclassified_applications_left: 'Bewerbung(en) wurden noch nicht klassifiziert' maximum_number_of_participants_exeeded: 'Maximale Teilnehmeranzahl wurde überschritten' sending_acceptances: 'Zusagen verschicken' @@ -32,7 +34,6 @@ de: unnecessary: "nicht benötigt" available: "vorhanden" unavailable: "nicht vorhanden" - kinds: workshop: "Workshop" camp: "Camp" @@ -54,12 +55,18 @@ de: add_timespan: "Zeitspanne hinzufügen" email: templates: "Vorlagen" + applications_pdf: + free_places: "Freie Plätze" + occupied_places: "Belegte Plätze" + page: "Seite" errors: application_deadline_before_start_of_event: "Bewerbungsschluss muss vor Beginn der Veranstaltung liegen" activerecord: models: - event: "Veranstaltung" + event: + one: "Veranstaltung" + other: "Veranstaltungen" attributes: event: name: "Name" diff --git a/config/locales/de.profiles.yml b/config/locales/de.profiles.yml index b54b9827..4d0b5380 100644 --- a/config/locales/de.profiles.yml +++ b/config/locales/de.profiles.yml @@ -10,6 +10,10 @@ de: male: "Männlich" female: "Weiblich" other: "Andere" + confirm_deletion: "Willst du dieses Profil wirklich löschen?" + successful_update: "Profil erfolgreich aktualisiert" + successful_deletion: "Profil erfolgreich gelöscht" + successful_creation: "Profil erfolgreich erstellt" activerecord: models: diff --git a/config/locales/de.users.yml b/config/locales/de.users.yml index a6a7a6cb..49ad7922 100644 --- a/config/locales/de.users.yml +++ b/config/locales/de.users.yml @@ -4,5 +4,5 @@ de: activerecord: attributes: user: - accepted_application_count: "Anzahl der angenommenen Bewerbungen" - rejected_application_count: "Anzahl der abgelehnten Bewerbungen" + accepted_application_count: "Angenommene Bewerbungen" + rejected_application_count: "Abgelehnte Bewerbungen" diff --git a/config/routes.rb b/config/routes.rb index f5688550..b6cd569f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,12 +3,16 @@ get 'agreement_letters/show' resources :requests + + put 'applications/:id/status' => 'application_letters#update_status', as: :update_application_letter_status + resources :application_letters, path: 'applications' do resources :application_notes, only: :create end resources :events do resources :agreement_letters, only: [:create], shallow: true + get 'print_applications', on: :member get 'badges' post 'badges' => 'events#print_badges', as: :print_badges end diff --git a/db/sample_data.rb b/db/sample_data.rb new file mode 100644 index 00000000..5d2e0ea7 --- /dev/null +++ b/db/sample_data.rb @@ -0,0 +1,74 @@ +require './db/sample_data/agreement_letters' +require './db/sample_data/application_letters' +require './db/sample_data/events' +require './db/sample_data/profiles' +require './db/sample_data/requests' +require './db/sample_data/users' + +def add_sample_data + events = Hash.new + + events[:programmierkurs] = event_programmierkurs + events[:mintcamp] = event_mintcamp + events[:bechersaeuberungsevent] = event_bechersaeuberungsevent + events[:gongakrobatik] = event_gongakrobatik + events[:batterie_akustik] = event_batterie_akustik + events[:bachlorpodium] = event_bachlorpodium + events[:past_deadline_event] = event_gongakrobatik + + users = Hash.new + users[:pupil] = user_pupil + users[:teacher] = user_teacher + users[:applicant] = user_applicant + users[:tobi] = user_tobi + users[:lisa] = user_lisa + users[:max] = user_max + users[:organizer] = user_organizer + users[:coach] = user_coach + + profiles = Hash.new + profiles[:pupil] = profile_pupil(users[:pupil]) + profiles[:teacher] = profile_teacher(users[:teacher]) + profiles[:applicant] = profile_applicant(users[:applicant]) + + profiles[:tobi] = profile_tobi(users[:tobi]) + profiles[:tobi] = profile_tobi(users[:tobi]) + profiles[:lisa] = profile_lisa(users[:lisa]) + profiles[:max] = profile_pupil_max(users[:max]) + profiles[:organizer] = profile_pupil_max(users[:organizer]) + profiles[:coach] = profile_pupil_max(users[:coach]) + + application_letters = Hash.new + application_letters[:applicant_gongakrobatik] = application_letter_applicant_gongakrobatik(users[:applicant], events[:gongakrobatik]) + application_letters[:applicant_gongakrobatik_past_deadline] = application_letter_applicant_gongakrobatik(users[:applicant], events[:past_deadline_event]) + application_letters[:applicant_gongakrobatik_accepcted] = application_letter_applicant_gongakrobatik_accepted(users[:applicant], events[:past_deadline_event]) + application_letters[:applicant_gongakrobatik_rejected] = application_letter_applicant_gongakrobatik_rejected(users[:applicant], events[:past_deadline_event]) + application_letters[:applicant_programmierkurs_lisa] = application_letter_applicant_programmierkurs_1(users[:lisa], events[:programmierkurs]) + application_letters[:applicant_programmierkurs_max] = application_letter_applicant_programmierkurs_2(users[:max], events[:programmierkurs]) + application_letters[:applicant_programmierkurs_tobi] = application_letter_applicant_programmierkurs_3(users[:tobi], events[:programmierkurs]) + + application_letters[:applicant_mintcamp_lisa] = application_letter_applicant_programmierkurs_1(users[:lisa], events[:mintcamp]) + application_letters[:applicant_mintcamp_max] = application_letter_applicant_programmierkurs_2(users[:max], events[:mintcamp]) + application_letters[:applicant_mintcamp_tobi] = application_letter_applicant_programmierkurs_3(users[:tobi], events[:mintcamp]) + + requests = Hash.new + requests[:hardware_entwicklung] = request_hardware_entwicklung(users[:teacher]) + + agreement_letters = Hash.new + agreement_letters[:applicant_gongakrobatik] = agreement_letter_applicant_gongakrobatik(users[:applicant], events[:gongakrobatik]) + + [events, users, profiles, application_letters, requests, agreement_letters].each do |models| + save_models(models) + end + + # set deadline to past to work around validation of application letters + events[:past_deadline_event].application_deadline = Date.yesterday + events[:past_deadline_event].save! +end + +private + def save_models(models) + models.each do |key, model| + model.save! + end + end diff --git a/db/sample_data/agreement_letters.rb b/db/sample_data/agreement_letters.rb new file mode 100644 index 00000000..2f558914 --- /dev/null +++ b/db/sample_data/agreement_letters.rb @@ -0,0 +1,7 @@ +def agreement_letter_applicant_gongakrobatik(user, event) + AgreementLetter.new( + user: user, + event: event, + path: "/storage/agreement_letters/foo.pdf" + ) +end \ No newline at end of file diff --git a/db/sample_data/application_letters.rb b/db/sample_data/application_letters.rb new file mode 100644 index 00000000..51ee71cf --- /dev/null +++ b/db/sample_data/application_letters.rb @@ -0,0 +1,98 @@ +def application_letter_applicant_gongakrobatik(user, event) + ApplicationLetter.new( + motivation: "Ich habe vor kurzem davon erfahren und war direkt hellaufbegeistert. Gerne würde ich mich bei Ihnen näher über das Thema informieren", + grade: 10, + experience: "Über einen Facebookpost ihrer Seite bin ich auf das Angebot aufmerksam geworden", + coding_skills: "HTML", + emergency_number: "01234567891", + vegeterian: false, + vegan: false, + allergic: false, + allergies: "", + user: user, + event: event + ) +end + +def application_letter_applicant_gongakrobatik_rejected(user, event) + ApplicationLetter.new( + motivation: "Ich habe vor kurzem davon erfahren und war direkt hellaufbegeistert. Gerne würde ich mich bei Ihnen näher über das Thema informieren", + grade: 10, + experience: "Über einen Facebookpost ihrer Seite bin ich auf das Angebot aufmerksam geworden", + coding_skills: "HTML", + emergency_number: "01234567891", + vegeterian: false, + vegan: false, + allergic: false, + allergies: "", + user: user, + event: event, + status: ApplicationLetter.statuses[:rejected] + ) +end + +def application_letter_applicant_gongakrobatik_accepted(user, event) + ApplicationLetter.new( + motivation: "Den normalen Unterricht in der Schule finde ich ziemlich langweilig und würde mich darüber freuen, etwas über den Tellerrand zu schauen und spannende Dinge lernen. Ich arbeite sehr gerne im Team und freue mich darauf, Gleichgesinnte kennen zu lernen.", + grade: 9, + experience: "Über einen Zeitungsartikel", + coding_skills: "For, While und If-Schleifen in Java", + emergency_number: "01234567891", + vegeterian: false, + vegan: false, + allergic: true, + allergies: "Tomaten", + user: user, + event: event, + status: ApplicationLetter.statuses[:accepted] + ) +end + +def application_letter_applicant_programmierkurs_1(user, event) + ApplicationLetter.new( + motivation: "Den normalen Unterricht in der Schule finde ich ziemlich langweilig und würde mich darüber freuen, etwas über den Tellerrand zu schauen und spannende Dinge lernen. Ich arbeite sehr gerne im Team und freue mich darauf, Gleichgesinnte kennen zu lernen.", + grade: 9, + experience: "Über einen Zeitungsartikel", + coding_skills: "For, While und If-Schleifen in Java", + emergency_number: "01234567891", + vegeterian: false, + vegan: false, + allergic: true, + allergies: "Tomaten", + user: user, + event: event + ) +end + +def application_letter_applicant_programmierkurs_2(user, event) + ApplicationLetter.new( + motivation: "Ich habe vor kurzem davon erfahren und war direkt hellaufbegeistert. Gerne würde ich mich bei Ihnen näher über das Thema informieren", + grade: 10, + experience: "Suche im Internet", + coding_skills: "keine", + emergency_number: "01234567891", + vegeterian: true, + vegan: false, + allergic: false, + allergies: "", + user: user, + event: event + ) +end + +def application_letter_applicant_programmierkurs_3(user, event) + ApplicationLetter.new( + motivation: "Ich habe vor LANGEM davon erfahren und war direkt hellaufbegeistert. Gerne würde ich mich bei Ihnen +näher über das Thema informieren", + grade: 10, + experience: "Suche im Internetz", + coding_skills: "absolut keine", + emergency_number: "01234567819", + vegeterian: true, + vegan: false, + allergic: false, + allergies: "", + user: user, + event: event + ) +end \ No newline at end of file diff --git a/db/sample_data/events.rb b/db/sample_data/events.rb new file mode 100644 index 00000000..ca57bbae --- /dev/null +++ b/db/sample_data/events.rb @@ -0,0 +1,136 @@ +def event_programmierkurs + date_range_singleday = DateRange.create!( + start_date: Date.new(2017, 05, 04), + end_date: Date.new(2017, 05, 05) + ) + + Event.new( + name: 'Android Programmierkurs', + description: 'Ihr wolltet schon immer einmal eine eigene App programmieren? In diesem Workshop lernt ihr object-orientierte Programmierung am Beispiel von einer Android App.', + max_participants: 25, + organizer: 'HPI Schülerklub', + knowledge_level: 'Anfänger', + date_ranges: [date_range_singleday], + application_deadline: Date.tomorrow, + draft: false, + application_status_locked: false + ) +end + +def event_mintcamp + date_range_mint_camp = DateRange.create!( + start_date: Date.new(2017, 03, 30), + end_date: Date.new(2017, 04, 04) + ) + + Event.new( + name: 'MINT-Camp', + description: 'Wie soll die digitale Zukunft in den Schulen aussehen? In immer mehr Schulen kommen Smartboards zum Einsatz. Diese elektronischen Tafeln haben das Potenzial den Unterricht und das Lernen nachhaltig zu verbessern. Häufig wird das Gerät allerdings auf seinen älteren Verwandten reduziert und nur zum Schreiben, bestenfalls auch für die Medienwiedergabe genutzt. Der Grund dafür sind meist schwer verständliche Programme, die weder die Bedürfnisse der Schüler noch die der Lehrer erfüllen. Dabei sind weitaus interessantere und vor allem sinnvollere Anwendungen für die intelligenten Tafeln denkbar. In diesem MINT-Camp entwickeln wir in kleinen Teams zunächst mit Hilfe von Design Thinking spannende neuartige Ideen für Smartboards. Anschließend werden die Ideen mit Webtechnologien implementiert und können direkt ausprobiert werden. Zum Abschluss hat jedes Team die Möglichkeit seine fertig entwickelte Anwendung zu präsentieren. Dabei stehen euch die ganze Zeit HPI-Studenten zur Seite und helfen euch bei Problemen aller Art. Vorkenntnisse sind keine erforderlich. ', + max_participants: 25, + organizer: 'HPI Schülerklub', + knowledge_level: 'Fortgeschrittene', + date_ranges: [date_range_mint_camp], + application_deadline: Date.tomorrow, + draft: false, + application_status_locked: false + ) +end + +def event_bechersaeuberungsevent + date_range_singleday = DateRange.create!( + start_date: Date.new(2017, 04, 04), + end_date: Date.new(2017, 04, 05) + ) + Event.new( + name: 'Bechersäuberungsevent', + description: 'Es dreht sich den ganzen Tag um das Säubern von Bechern. Wie säubert man einen Becher am +effizientesten oder am schnellsten? Wie immer bieten wir eine Reihe an Expertenvorträgen an. Dieses Mal erfahrt ihr +unter anderem wie ihr Edding-Markierungen selbst nach einer Spülmaschinen-Reinigung noch entfernen könnt oder wie man +die richtige Größe für Becher-Stapel herausfindet und anwendet.', + max_participants: 25, + organizer: 'FSR', + knowledge_level: 'Anfänger', + date_ranges: [date_range_singleday], + application_deadline: Date.tomorrow, + draft: false, + application_status_locked: false + ) +end + +def event_gongakrobatik + date_range_long = DateRange.create!( + start_date: Date.new(2020, 02, 29), + end_date: Date.new(2021, 03, 05) + ) + Event.new( + name: 'Einführung in die Kunst der Gongakrobatik', + description: 'Schon im alten China erzählte man sich von den sa­gen­um­wo­benen Legenden der Gongakrobatik. +Spätestens seit dieser Trend auch seinen Weg nach Japan gefunden hat, stellt sich die Gongakrobatik auch für uns als +ernstzunehmende Alternative gegenüber herkömmlichen Stimmbildungübungen und ähnlichem dar. In dieser Einführung möchten +wir euch einen groben Überblick über das Thema geben: Wie findet man am besten seinen Weg in die Gongakrobatik, was +braucht man dafür. In den letzten Jahren hat sich zudem eine große Community rund um dieses faszinierende Thema gebildet. +Höhepunkt der Veranstaltung ist demnach unser Besuch einer echten Gongmanufaktor im Herzen Berlins, durchgeführt von dem +Ding Gong-Verein Berlin. Bei Bedarf können wir eine zweite Veranstaltung durchführen, bis dahin gilt first come first serve :)', + max_participants: 19, + knowledge_level: 'Ihr braucht kein besonderes Vorwissen, jeder ist Willkommen!', + date_ranges: [date_range_long], + application_deadline: Date.tomorrow, + draft: false, + application_status_locked: false + ) +end + +def event_batterie_akustik + date_range_short = DateRange.create!( + start_date: Date.tomorrow.next_day(3), + end_date: Date.tomorrow.next_day(5) + ) + + date_range_medium = DateRange.create!( + start_date: Date.new(2017, 06, 01), + end_date: Date.new(2017, 06, 14) + ) + Event.new( + name: 'Batterie-Akustik für Fortgeschrittene', + description: 'Viele Menschen sammeln bereits im jungen Alter Erfahrung mit der überaus komplexen Batterie-Akustik. +"Leider wird daraus bei vielen aber nicht mehr als bloßes Jugendwissen, das sich höchstens für den jährlichen Party-Gag +eignet" erklärt Dr. Warta Durazell vom Institut für Angewandte Batterie-Akustik (IAB). Mit dieser Workshop-Serie möchten +wir etwas an diesem Missstand ändern. Wie viele Batterien werden benötigt um einen Echo-Effekt gleichwertig zum +Akkumulator-Echo zu erzeugen? Ist dies überhaupt außerhalb von Laborbedingungen möglich? Die Teilnehmer haben nach den +Veranstaltungen ein fundiertes Wissen über die Klangeigenschaften sowie das (nicht äquivalente) Klangverhalten von +Batterien. Zudem erhalten sie ein offizielles Zertifikat des IAB, welches es ihnen ermöglicht an weiterführenden +Veranstaltungen zum Thema teilzunehmen. Gerade in Zeit von E-Autos ist dies ein wichtiges Alleinstellungsmerkmal auf dem +Arbeitsmarkt. Bitte beachtet die maximale Teilnehmeranzahl! Wichtig: Es gilt first come last served.', + max_participants: 32, + organizer: 'IAB', + date_ranges: [date_range_short, date_range_medium], + application_deadline: Date.tomorrow, + draft: false, + application_status_locked: false + ) +end + +def event_bachlorpodium + date_range_singleday1 = DateRange.create!( + start_date: Date.tomorrow, + end_date: Date.tomorrow + ) + date_range_singleday2 = DateRange.create!( + start_date: Date.new(2017, 04, 04), + end_date: Date.new(2017, 04, 05) + ) + date_range_singleday3 = DateRange.create!( + start_date: Date.new(2017, 04, 06), + end_date: Date.new(2017, 04, 06) + ) + Event.new( + name: 'Bachelorpodium', + description: 'Trotz modernem Videostreaming in HD in die anderen Hörsäle bleibt Hörsaal 1 doch der Publikumsliebling +bei diesem jährlich mit größter Sorgfalt organisierten spektakulären PR Gag', + max_participants: 442, + date_ranges: [date_range_singleday1, date_range_singleday2, date_range_singleday3], + application_deadline: Date.tomorrow, + draft: false, + application_status_locked: false + ) +end diff --git a/db/sample_data/profiles.rb b/db/sample_data/profiles.rb new file mode 100644 index 00000000..7d9d2d61 --- /dev/null +++ b/db/sample_data/profiles.rb @@ -0,0 +1,101 @@ +def profile_pupil(user) + Profile.new( + user: user, + first_name: "Karl", + last_name: "Doe", + gender: "male", + birth_date: Date.parse('2000.11.29'), + school: "Schule am Griebnitzsee", + street_name: "Rudolf-Breitscheid-Str. 52", + zip_code: "14482", + city: "Potsdam", + state: "Brandenburg", + country: "Deutschland", + graduates_school_in: "2019" + ) +end + +def profile_pupil_max(user) + Profile.new( + user: user, + first_name: "Max", + last_name: "Mustermann", + gender: "male", + birth_date: Date.parse('2000.12.09'), + school: "Musterschule", + street_name: "Musterstraße 42", + zip_code: "14482", + city: "Potsdam", + state: "Brandenburg", + country: "Deutschland", + graduates_school_in: "2018" + ) +end + +def profile_teacher(user) + Profile.new( + user: user, + first_name: "Ernst", + last_name: "Teacher", + gender: "male", + birth_date: Date.parse('1970.1.1'), + school: "Schule am Griebnitzsee", + street_name: "Domstraße 14", + zip_code: "14482", + city: "Potsdam", + state: "Brandenburg", + country: "Deutschland", + graduates_school_in: "Bereits Abitur" + ) +end + +def profile_applicant(user) + Profile.new( + user: user, + first_name: "Erika", + last_name: "Mustermann", + gender: "female", + birth_date: Date.parse('1999.08.14'), + school: "Schule am Griebnitzsee", + street_name: "Rudolf-Breitscheid-Str. 52", + zip_code: "14482", + city: "Potsdam", + state: "Brandenburg", + country: "Deutschland", + graduates_school_in: "2017" + ) +end + +def profile_tobi(user) + Profile.new( + user: user, + first_name: "Tobias", + last_name: "Dürschmid", + gender: "male", + birth_date: Date.parse('1995.08.31'), + school: "Goetheschule Ilmenau", + street_name: "Stahnsdorfer Str.", + zip_code: "14482", + city: "Potsdam", + state: "Brandenburg", + country: "Deutschland", + graduates_school_in: "Bereits Abitur" + ) +end + +def profile_lisa(user) + Profile.new( + user: user, + first_name: "Lisa", + last_name: "Ihde", + gender: "female", + birth_date: Date.parse('1996.09.21'), + school: "Sophie-Scholl-Gymnasium", + street_name: "Stahnsdorfer Str.", + zip_code: "14482", + city: "Potsdam", + state: "Brandenburg", + country: "Deutschland", + graduates_school_in: "Bereits Abitur" + ) +end \ No newline at end of file diff --git a/db/sample_data/requests.rb b/db/sample_data/requests.rb new file mode 100644 index 00000000..f36e9cb4 --- /dev/null +++ b/db/sample_data/requests.rb @@ -0,0 +1,6 @@ +def request_hardware_entwicklung(user) + Request.new( + topics: "Hardware-Entwicklung mit einem CAD-System", + user: user + ) +end \ No newline at end of file diff --git a/db/sample_data/users.rb b/db/sample_data/users.rb new file mode 100644 index 00000000..c760f8db --- /dev/null +++ b/db/sample_data/users.rb @@ -0,0 +1,75 @@ +def user_password + "123456" +end + +def user_pupil + User.new( + name: "Schueler", + email: "schueler@example.com", + password: user_password, + role: :pupil + ) +end + +def user_teacher + User.new( + name: "Lehrer", + email: "lehrer@example.com", + password: user_password, + role: :teacher + ) +end + +def user_applicant + User.new( + name: "Bewerber", + email: "bewerber@example.com", + password: user_password, + role: :pupil + ) +end + +def user_max + User.new( + name: "Max Mustermann", + email: "max@schueler.com", + password: user_password, + role: :pupil + ) +end + +def user_lisa + User.new( + name: "Lisa Ihde", + email: "lisa@schueler.com", + password: user_password, + role: :pupil + ) +end + +def user_tobi + User.new( + name: "Tobias Dürschmid", + email: "tobias.duerschmid@t-online.de", + password: user_password, + role: :pupil + ) +end + +def user_organizer + User.new( + name: "Organizer", + email: "organizer@workshops.hpi.de", + password: user_password, + role: :admin + ) +end + +def user_coach + User.new( + name: "Coach", + email: "coach@workshops.hpi.de", + password: user_password, + role: :coach + ) +end \ No newline at end of file diff --git a/db/seeds.rb b/db/seeds.rb index 15360e37..9ede90eb 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -2,123 +2,13 @@ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). # -# Users -users = [] -pupil = User.find_or_initialize_by( - name: "Schueler", - email: "schueler@example.com" +# Create Superuser +admin = User.find_or_initialize_by( + name: "admin", + email: "info@domain.com", + role: :admin ) -users.push(pupil) +admin.password = "system" +admin.save! -teacher = User.find_or_initialize_by( - name: "Lehrer", - email: "lehrer@example.com" -) -users.push(teacher) - -applicant = User.find_or_initialize_by( - name: "Bewerber", - email: "bewerber@example.com" -) -users.push(applicant) - -# Set a password for every user -# They are only initialized, save them to the db -users.each do |user| - user.password = "123456" - user.save! -end - -date_range = DateRange.find_or_create_by!( - start_date: Date.tomorrow.next_day(3), - end_date: Date.tomorrow.next_day(5) -) - -# An event -event = Event.new( - name: "Messung und Verarbeitung von Umweltdaten", - description: "Veranstaltung mit Phidgets und Etoys", - max_participants: 20, - application_deadline: Date.tomorrow, - draft: false, - application_status_locked: false -) -event.date_ranges << date_range -event.save - -# Pupil's profile -Profile.find_or_create_by!( - user: pupil, - first_name: "Karl", - last_name: "Doe", - gender: "male", - birth_date: Date.parse('2000.11.29'), - school: "Schule am Griebnitzsee", - street_name: "Rudolf-Breitscheid-Str. 52", - zip_code: "14482", - city: "Potsdam", - state: "Brandenburg", - country: "Deutschland", - graduates_school_in: "2019" -) - -# Teacher's profile -Profile.find_or_create_by!( - user: teacher, - first_name: "Ernst", - last_name: "Teacher", - gender: "male", - birth_date: Date.parse('1970.1.1'), - school: "Schule am Griebnitzsee", - street_name: "Domstraße 14", - zip_code: "14482", - city: "Potsdam", - state: "Brandenburg", - country: "Deutschland", - graduates_school_in: "Bereits Abitur" -) - -# Applicant's profile -Profile.find_or_create_by!( - user: applicant, - first_name: "Erika", - last_name: "Mustermann", - gender: "female", - birth_date: Date.parse('1999.08.14'), - school: "Schule am Griebnitzsee", - street_name: "Rudolf-Breitscheid-Str. 52", - zip_code: "14482", - city: "Potsdam", - state: "Brandenburg", - country: "Deutschland", - graduates_school_in: "2017" -) - -# Teacher's event request -Request.find_or_create_by!( - topics: "Hardware-Entwicklung mit einem CAD-System", - user: teacher -) - -# Applicant's application letter -ApplicationLetter.find_or_create_by!( - motivation: "Ich würde sehr gerne an eurer Veranstaltung teilnehmen", - grade: 10, - experience: "Internet", - coding_skills: "HTML", - emergency_number: "01234567891", - vegeterian: false, - vegan: false, - allergic: false, - allergies: "", - status: ApplicationLetter.statuses[:pending], - user: applicant, - event: event -) - -AgreementLetter.find_or_create_by!( - user: applicant, - event: event, - path: "/storage/agreement_letters/foo.pdf" -) diff --git a/lib/pdf_generation/applications_pdf.rb b/lib/pdf_generation/applications_pdf.rb new file mode 100644 index 00000000..b1e70608 --- /dev/null +++ b/lib/pdf_generation/applications_pdf.rb @@ -0,0 +1,158 @@ +class ApplicationsPDF + include Prawn::View + Prawn::Font::AFM.hide_m17n_warning = true #consider adding TTF font + + # Generates a PDF file containing the details of every application for an event + # + # param event [Event] the event whose applications are taken + # return [String] the generated PDF + def self.generate(event) + self.new(event).create.render + end + + def initialize(event) + @event = event + @document = Prawn::Document.new(page_size: 'A4') + @application_letters_count = @event.application_letters.count + end + + # Adds all necessary data and formatting to the ApplicationsPDF + # + # param none + # return [ApplicationsPDF] self + def create + create_overview + @event.application_letters.each_with_index do |a,i| + create_application_page(a, i) + end + self + end + + private + def create_overview + text t("events.applicants_overview.title", title: @event.name), size: 20 + table description_table_data do + cells.borders = [] + cells.padding = 3 + column(0).font_style = :bold + column(0).align = :right + end + unless @event.application_letters.empty? + table overview_table_data, + header: 2, position: :center, row_colors: ["F9F9F9", "FFFFFF"] do + cells.borders = [] + row(1).borders = [:bottom] + row(1).font_style = :bold + row(0).font_style = :bold + row(0).padding = [5, 0, 5, 5] #minimize padding between first two rows + row(1).padding = [0, 5, 5, 5] + end + end + end + + def description_table_data + data = [[Event.human_attribute_name(:description)+":", @event.description], + [Event.human_attribute_name(:max_participants) + ":", @event.max_participants], + [Event.human_attribute_name(:date_ranges) + ":", @event.date_ranges[0].to_s]] + data += @event.date_ranges.drop(1).map { |d| ["", d.to_s] } + data += [[Event.human_attribute_name(:organizer) + ":", @event.organizer]] if @event.organizer + data += [[Event.human_attribute_name(:knowledge_level) + ":", @event.knowledge_level]] if @event.knowledge_level + data += [[t("events.applications_pdf.free_places") + ":", @event.compute_free_places], + [t("events.applications_pdf.occupied_places") + ":", @event.compute_occupied_places]] + end + + def overview_table_data + #line breaks lead to weird table formatting, so we create 2 header rows to fit all the text + data = [["", "", "", t("events.applicants_overview.participations"), ""]] + data += [[Profile.human_attribute_name(:name), + Profile.human_attribute_name(:gender), + Profile.human_attribute_name(:age), + t("events.applicants_overview.accepted_rejected"), + ApplicationLetter.human_attribute_name(:status)]] + data += @event.application_letters.map do |a| + [a.user.profile.name, + a.user.profile.gender, + a.user.profile.age, + "#{a.user.accepted_applications_count(@event)} / #{a.user.rejected_applications_count(@event)}", + t("application_status.#{a.status}")] + end + end + + def create_application_page(application_letter, index) + start_new_page + first_page = page_number + create_main_header(application_letter) + + bounding_box([bounds.left, bounds.top - 50], + width: bounds.width, + height: bounds.height - 100) do + create_application_page_content(application_letter) + end + + create_headers(application_letter, first_page, page_number) + create_footers(index, first_page, page_number) + end + + def create_application_page_content(application_letter) + table applicants_detail_data(application_letter) do + cells.borders = [] + cells.padding = 3 + column(0).font_style = :bold + column(0).align = :right + end + pad_top(20) { text "#{ApplicationLetter.human_attribute_name(:motivation)}", inline_format: true} + pad_top(5) { text application_letter.motivation } + unless application_letter.application_notes.count == 0 + pad_top(15) do + text "#{ApplicationNote.model_name.human(count: application_letter.application_notes.count)}", inline_format: true + application_letter.application_notes.each do |note| + pad_top(5) { text note.note } + end + end + end + end + + def applicants_detail_data(application_letter) + [[Profile.human_attribute_name(:gender)+":", application_letter.user.profile.gender], + [Profile.human_attribute_name(:age)+":", application_letter.user.profile.age], + [Profile.human_attribute_name(:address)+":", application_letter.user.profile.address], + [User.human_attribute_name(:accepted_application_count)+":", application_letter.user.accepted_applications_count(@event)], + [User.human_attribute_name(:rejected_application_count)+":", application_letter.user.rejected_applications_count(@event)], + [Profile.human_attribute_name(:status)+":", t("application_status.#{application_letter.status}")]] + end + + def create_main_header(application_letter) + text t("application_letters.application_page.title", name: application_letter.user.profile.name), size: 20 + text t("application_letters.application_page.for", event: @event.name), size: 14 + stroke_horizontal_rule + end + + def create_headers(application_letter, first_page, last_page) + repeat(first_page + 1..last_page) do + bounding_box [bounds.left, bounds.top], width: bounds.width do + text @event.name + text_box application_letter.user.profile.name, align: :right + stroke_horizontal_rule + end + end + end + + def create_footers(index, first_page, last_page) + repeat(first_page..last_page, dynamic: true) do + bounding_box [bounds.left, bounds.bottom + 25], width: bounds.width do + relative_page = page_number - first_page + 1 + relative_last_page = last_page - first_page + 1 + stroke_horizontal_rule + move_down(5) + text_box(t("events.applications_pdf.page") + " #{relative_page}/#{relative_last_page}", + at: [0, cursor], + align: :right) + text ApplicationLetter.model_name.human + " #{index + 1}/#{@application_letters_count}" + end + end + end + + def t(string, options = {}) + I18n.t(string, options) + end +end diff --git a/lib/tasks/sample_data.rake b/lib/tasks/sample_data.rake new file mode 100644 index 00000000..1421a6d3 --- /dev/null +++ b/lib/tasks/sample_data.rake @@ -0,0 +1,8 @@ +require './db/sample_data' + +namespace :db do + desc 'Populates the database with sample data' + task populate_sample_data: :environment do + add_sample_data + end +end \ No newline at end of file diff --git a/spec/controllers/application_letters_controller_spec.rb b/spec/controllers/application_letters_controller_spec.rb index 4d5feaad..19ee27ee 100644 --- a/spec/controllers/application_letters_controller_spec.rb +++ b/spec/controllers/application_letters_controller_spec.rb @@ -78,8 +78,7 @@ vegeterian: true, vegan: true, allergic: true, - allergys: "Many", - status: "accepted" + allergys: "Many" } } @@ -87,7 +86,6 @@ put :update, id: @application.to_param, application_letter: new_attributes, session: valid_session @application.reload expect(@application.motivation).to eq(new_attributes[:motivation]) - expect(@application.status).to eq(new_attributes[:status]) end it "assigns the requested application as @application" do @@ -114,6 +112,43 @@ end end + describe "PUT #update_status" do + before :each do + sign_in FactoryGirl.create(:user, role: :admin) + end + context "with valid params" do + let(:new_status) { {status: 'accepted'} } + + it "assigns the requested application as @application" do + put :update_status, id: @application.to_param, application_letter: new_status, session: valid_session + expect(assigns(:application_letter)).to eq(@application) + end + + it "updates the status" do + put :update_status, id: @application.to_param, application_letter: new_status, session: valid_session + @application.reload + expect(@application.status).to eq(new_status[:status]) + end + + it "redirects back" do + put :update_status, id: @application.to_param, application_letter: new_status, session: valid_session + expect(response).to redirect_to(request.env['HTTP_REFERER']) + end + end + + context "with invalid params" do + it "assigns the application as @application" do + put :update_status, id: @application.to_param, application_letter: {status: nil}, session: valid_session + expect(assigns(:application_letter)).to eq(@application) + end + + it "re-renders the 'edit' template" do + put :update_status, id: @application.to_param, application_letter: {status: nil}, session: valid_session + expect(response).to render_template("edit") + end + end + end + describe "DELETE #destroy" do it "destroys the requested application" do expect { @@ -163,4 +198,4 @@ end end end -end \ No newline at end of file +end diff --git a/spec/controllers/events_controller_spec.rb b/spec/controllers/events_controller_spec.rb index 59e7d11d..50726465 100644 --- a/spec/controllers/events_controller_spec.rb +++ b/spec/controllers/events_controller_spec.rb @@ -256,4 +256,79 @@ expect(assigns(:event).date_ranges.first.end_date).to eq(date_range.end_date) end end + + describe "GET #print_applications" do + before :each do + @event = Event.create! valid_attributes + @user = FactoryGirl.create(:user, role: :organizer) + sign_in @user + end + + it "returns success" do + get :print_applications, id: @event.to_param, session: valid_session + expect(response).to be_success + end + + it 'returns downloadable PDF' do + get :print_applications, id: @event.to_param, session: valid_session + PDF::Inspector::Text.analyze response.body + end + + it "returns a PDF with a correct overview page" do + get :print_applications, id: @event.to_param, session: valid_session + page_analysis = PDF::Inspector::Page.analyze(response.body) + expect(page_analysis.pages.size).to be 1 + analysis = PDF::Inspector::Text.analyze response.body + text = analysis.strings.join + expect(text).to include( + @event.name, + @event.max_participants.to_s, + @event.organizer, + @event.knowledge_level, + @event.compute_free_places.to_s, + @event.compute_occupied_places.to_s) + @event.date_ranges.each { |d| expect(text).to include(d.to_s) } + end + + it "shows an overview of all and details of every application" do + al = FactoryGirl.create(:application_letter, event: @event,) + FactoryGirl.create(:application_note, application_letter_id: al.id) + User.find_each { |u| FactoryGirl.create(:profile, user: u) } + get :print_applications, id: @event.to_param, session: valid_session + analysis = PDF::Inspector::Text.analyze response.body + text = analysis.strings.join(' ') + @event.application_letters.each do |a| + expect(text).to include( + a.user.profile.name, + a.user.profile.age.to_s, + a.user.profile.gender, + a.user.accepted_applications_count(@event).to_s, + a.user.rejected_applications_count(@event).to_s, + I18n.t("application_status.#{a.status}"), + a.user.profile.address, + a.motivation + ) + a.application_notes.each do |note| + expect(text).to include(note.note) + end + end + end + + it "includes at last one page per application" do + FactoryGirl.create(:application_letter, event: @event,) + FactoryGirl.create(:application_letter2, event: @event,) + User.find_each { |u| FactoryGirl.create(:profile, user: u) } + get :print_applications, id: @event.to_param, session: valid_session + page_analysis = PDF::Inspector::Page.analyze(response.body) + expect(page_analysis.pages.size).to be >= 3 + end + + it "extends long applications over several pages" do + FactoryGirl.create(:application_letter_long, event: @event,) + User.find_each { |u| FactoryGirl.create(:profile, user: u) } + get :print_applications, id: @event.to_param, session: valid_session + page_analysis = PDF::Inspector::Page.analyze(response.body) + expect(page_analysis.pages.size).to be >= 3 + end + end end diff --git a/spec/db/seeds_spec.rb b/spec/db/seeds_spec.rb index bc5b9be1..0f56abac 100644 --- a/spec/db/seeds_spec.rb +++ b/spec/db/seeds_spec.rb @@ -1,4 +1,5 @@ require 'rails_helper' +require 'rake' describe 'Seeds' do it 'should seed the database without errors' do @@ -7,4 +8,11 @@ Rails.application.load_seed }.to_not raise_error end +end + +describe 'Sample data' do + it 'should load sample data without errors' do + Rails.application.load_tasks + Rake::Task['db:populate_sample_data'].invoke + end end \ No newline at end of file diff --git a/spec/factories/application_letters.rb b/spec/factories/application_letters.rb index d8476e3b..d7c2c79d 100644 --- a/spec/factories/application_letters.rb +++ b/spec/factories/application_letters.rb @@ -25,6 +25,17 @@ event end + factory :application_letter2, parent: :application_letter do + grade 11 + experience "A lot" + motivation "Ich bin sehr motiviert, glaubt mir." + emergency_number "110" + vegeterian true + end + + factory :application_letter_long, parent: :application_letter do + motivation "Ich bin sehr motiviert, glaubt mir." * 200 + end factory :application_letter_deadline_over, parent: :application_letter do association :event, factory: :event, application_deadline: Date.yesterday @@ -37,4 +48,8 @@ factory :application_letter_rejected, parent: :application_letter do status :rejected end + + factory :application_letter_alternative, parent: :application_letter do + status :alternative + end end diff --git a/spec/features/event_spec.rb b/spec/features/event_spec.rb index e84af04d..59c69393 100644 --- a/spec/features/event_spec.rb +++ b/spec/features/event_spec.rb @@ -97,6 +97,29 @@ expect(page).to have_text("Bewerbungsschluss muss vor Beginn der Veranstaltung liegen") end + + it "should not display errors on date ranges twice", js: true do + visit new_event_path + + fill_in "Maximale Teilnehmerzahl", :with => 25 + + within page.find("#event-date-pickers").all("div")[0] do + fill_in "event[date_ranges_attributes][][start_date]", with: I18n.l(Date.current.prev_day(7)) + fill_in "event[date_ranges_attributes][][end_date]", with: I18n.l(Date.yesterday.prev_day(7)) + end + + click_link "Zeitspanne hinzufügen" + + within page.find("#event-date-pickers").all("div")[1] do + fill_in "event[date_ranges_attributes][][start_date]", with: I18n.l(Date.current) + fill_in "event[date_ranges_attributes][][end_date]", with: I18n.l(Date.yesterday) + end + + click_button I18n.t(".events.form.publish") + + expect(page).to have_css("div.has-error") + expect(page).to have_content("kann nicht vor Start-Datum liegen", count: 1) + end end describe "show page" do diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 6fa394fe..db49213e 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -124,7 +124,7 @@ def mock_writing_to_filesystem find('input[name=commit]').click - expect(page).to have_text('Profile was successfully updated.') + expect(page).to have_text(I18n.t('profiles.successful_update')) end scenario "user fills in an invalid birth date" do diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 1eb745c5..763ada1e 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -106,6 +106,13 @@ expect(ability).to be_able_to(:view_applicants, Event) end + + it "can print an event's applications as #{role}" do + user = FactoryGirl.create(:user, role: role) + ability = Ability.new(user) + + expect(ability).to be_able_to(:print_applications, Event) + end end it "cannot view and add notes to application letters as pupil" do @@ -122,6 +129,13 @@ expect(ability).to_not be_able_to(:view_applicants, Event) end + it "cannot print applications for an event as pupil" do + user = FactoryGirl.create(:user, role: :pupil) + ability = Ability.new(user) + + expect(ability).to_not be_able_to(:print_applications, Event) + end + it "can do everything as admin" do user = FactoryGirl.create(:user, role: :admin) ability = Ability.new(user) @@ -149,4 +163,41 @@ expect(ability).to_not be_able_to(:update, another_application) expect(ability).to_not be_able_to(:destroy, another_application) end + + %i[pupil coach].each do |role| + it "cannot update application letter status as #{role}" do + user = FactoryGirl.create(:user, role: role) + ability = Ability.new(user) + + expect(ability).to_not be_able_to(:update_status, ApplicationLetter) + end + end + + it "can update application letter status as organizer" do + user = FactoryGirl.create(:user, role: :organizer) + another_user = FactoryGirl.create(:user) + another_application = FactoryGirl.create(:application_letter, user: another_user) + ability = Ability.new(user) + + expect(ability).to be_able_to(:update_status, another_application) + end + + it "can manage events as organzier" do + user = FactoryGirl.create(:user, role: :organizer) + ability = Ability.new(user) + expect(ability).to be_able_to(:manage, Event) + end + + it "can create requests as pupil" do + user = FactoryGirl.create(:user, role: :pupil) + ability = Ability.new(user) + expect(ability).to be_able_to(:create, Request) + expect(ability).to be_able_to(:new, Request) + end + + it "can manage requests as organzier" do + user = FactoryGirl.create(:user, role: :organizer) + ability = Ability.new(user) + expect(ability).to be_able_to(:manage, Request) + end end diff --git a/spec/views/application_letters/index.html.erb_spec.rb b/spec/views/application_letters/index.html.erb_spec.rb new file mode 100644 index 00000000..0e94c102 --- /dev/null +++ b/spec/views/application_letters/index.html.erb_spec.rb @@ -0,0 +1,55 @@ +require 'rails_helper' + +RSpec.describe "application_letters/index", type: :view do + + context "checks states of applications" do + it "checks if page displays accepted application" do + @application_letters = [FactoryGirl.create(:application_letter_accepted)] + render + expect(rendered).to have_content(I18n.t("application_status.accepted")) + end + it "checks if page displays rejected application" do + @application_letters = [FactoryGirl.create(:application_letter_rejected)] + render + expect(rendered).to have_content(I18n.t("application_status.rejected")) + end + it "checks if page displays pending application after deadline" do + @application_letters = [FactoryGirl.create(:application_letter)] + @application_letters[0].event.application_deadline = Date.yesterday + render + expect(rendered).to have_content(I18n.t("application_status.pending_after_deadline")) + end + it "checks if page displays pending application before deadline" do + @application_letters = [FactoryGirl.create(:application_letter)] + render + expect(rendered).to have_content(I18n.t("application_status.pending_before_deadline")) + end + it "checks if page displays alternative status application letter" do + @application_letters = [FactoryGirl.create(:application_letter_alternative)] + render + expect(rendered).to have_content(I18n.t("application_status.alternative")) + end + end + + it "should display the name of the event" do + @application_letters = [FactoryGirl.create(:application_letter)] + render + expect(rendered).to have_content(@application_letters[0].event.name) + end + + it "should display the edit button for a pending event" do + @application_letters = [FactoryGirl.create(:application_letter)] + render + expect(rendered).to have_css("a.btn", :text => "Bearbeiten") + end + it "should not display edit button after deadline" do + @application_letters = [FactoryGirl.build(:application_letter_deadline_over)] + render + expect(rendered).to_not have_css("a.btn", :text => "Bearbeiten") + end + it "should have link with the event name" do + @application_letters = [FactoryGirl.create(:application_letter)] + render + expect(rendered).to have_link(@application_letters[0].event.name, href: event_path(@application_letters[0].event.id)) + end +end \ No newline at end of file diff --git a/spec/views/events/index.html.erb_spec.rb b/spec/views/events/index.html.erb_spec.rb index 57d0cc55..f9214a35 100644 --- a/spec/views/events/index.html.erb_spec.rb +++ b/spec/views/events/index.html.erb_spec.rb @@ -19,4 +19,21 @@ render expect(rendered).to_not have_text("Id") end + + it "should not display new, edit, delete buttons for non-organizers" do + sign_in(FactoryGirl.create(:user, role: :coach)) + render + expect(rendered).to_not have_link(I18n.t('helpers.links.new')) + expect(rendered).to_not have_link(I18n.t('helpers.links.edit')) + expect(rendered).to_not have_link(I18n.t('helpers.links.destroy')) + + end + + it "should display new, edit, delete buttons for organizers" do + sign_in(FactoryGirl.create(:user, role: :organizer)) + render + expect(rendered).to have_link(I18n.t('helpers.links.new')) + expect(rendered).to have_link(I18n.t('helpers.links.edit')) + expect(rendered).to have_link(I18n.t('helpers.links.destroy')) + end end diff --git a/spec/views/events/show.html.erb_spec.rb b/spec/views/events/show.html.erb_spec.rb index 7bc3cb70..546eabf5 100644 --- a/spec/views/events/show.html.erb_spec.rb +++ b/spec/views/events/show.html.erb_spec.rb @@ -53,6 +53,10 @@ expect(rendered).to have_link(t(:details, scope: 'events.applicants_overview')) end + it "displays print applications button" do + render + expect(rendered).to have_link(t(:print_all, scope: 'events.applicants_overview')) + end it "displays print badges button" do render diff --git a/spec/views/requests/index.html.erb_spec.rb b/spec/views/requests/index.html.erb_spec.rb index 6b9321f9..bf16dd93 100644 --- a/spec/views/requests/index.html.erb_spec.rb +++ b/spec/views/requests/index.html.erb_spec.rb @@ -13,4 +13,26 @@ render assert_select "tr>td", :text => @topics, :count => 2 end + + it "should not display the new button for non-pupils" do + render + expect(rendered).to_not have_link(I18n.t('helpers.links.new')) + end + + it "should display new button but not display edit, delete buttons for non-organizers" do + sign_in(FactoryGirl.create(:user, role: :coach)) + render + expect(rendered).to have_link(I18n.t('helpers.links.new')) + + expect(rendered).to_not have_link(I18n.t('helpers.links.edit')) + expect(rendered).to_not have_link(I18n.t('helpers.links.destroy')) + end + + it "should display edit, delete buttons for organizers" do + sign_in(FactoryGirl.create(:user, role: :organizer)) + render + + expect(rendered).to have_link(I18n.t('helpers.links.edit')) + expect(rendered).to have_link(I18n.t('helpers.links.destroy')) + end end diff --git a/spec/views/requests/show.html.erb_spec.rb b/spec/views/requests/show.html.erb_spec.rb index b11da680..ccdf7179 100644 --- a/spec/views/requests/show.html.erb_spec.rb +++ b/spec/views/requests/show.html.erb_spec.rb @@ -2,11 +2,25 @@ RSpec.describe "requests/show", type: :view do before(:each) do - @request = assign(:request, FactoryGirl.create(:request, topics: 'Topics')) + @aRequest = assign(:request, FactoryGirl.create(:request, topics: 'Topics')) end it "renders attributes" do render - expect(rendered).to have_text(@request.topics) + expect(rendered).to have_text(@aRequest.topics) + end + + it "should not display edit, delete buttons for non-organizers" do + sign_in(FactoryGirl.create(:user, role: :coach)) + render + expect(rendered).to_not have_link(I18n.t('helpers.links.edit')) + expect(rendered).to_not have_link(I18n.t('helpers.links.destroy')) + end + + it "should display edit, delete buttons for organizers" do + sign_in(FactoryGirl.create(:user, role: :organizer)) + render + expect(rendered).to have_link(I18n.t('helpers.links.edit')) + expect(rendered).to have_link(I18n.t('helpers.links.destroy')) end end