voluntary_scholarship 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +3 -0
  3. data/Rakefile +23 -0
  4. data/app/controllers/concerns/scholarship/base_controller.rb +17 -0
  5. data/app/controllers/product/scholarship_controller.rb +6 -0
  6. data/app/controllers/scholarship/iteration_participations_controller.rb +80 -0
  7. data/app/controllers/scholarship/iterations_controller.rb +72 -0
  8. data/app/controllers/scholarship/programs_controller.rb +66 -0
  9. data/app/controllers/scholarship/team_memberships_controller.rb +77 -0
  10. data/app/controllers/scholarship/teams_controller.rb +63 -0
  11. data/app/controllers/scholarship/workflow/organization_owner_controller.rb +10 -0
  12. data/app/controllers/scholarship/workflow/team_leader_controller.rb +10 -0
  13. data/app/controllers/scholarship/workflow_controller.rb +10 -0
  14. data/app/helpers/voluntary_scholarship/iteration_participations_helper.rb +19 -0
  15. data/app/helpers/voluntary_scholarship/team_memberships_helper.rb +19 -0
  16. data/app/models/concerns/scholarship/role_request.rb +36 -0
  17. data/app/models/product/scholarship.rb +2 -0
  18. data/app/models/scholarship/iteration.rb +36 -0
  19. data/app/models/scholarship/iteration_participation.rb +66 -0
  20. data/app/models/scholarship/program.rb +18 -0
  21. data/app/models/scholarship/team.rb +33 -0
  22. data/app/models/scholarship/team_membership.rb +22 -0
  23. data/app/views/product/scholarship/index.html.erb +0 -0
  24. data/app/views/scholarship/iteration_participations/_actions.html.erb +18 -0
  25. data/app/views/scholarship/iteration_participations/_collection.html.erb +51 -0
  26. data/app/views/scholarship/iteration_participations/_form.html.erb +23 -0
  27. data/app/views/scholarship/iteration_participations/edit.html.erb +3 -0
  28. data/app/views/scholarship/iteration_participations/index.html.erb +3 -0
  29. data/app/views/scholarship/iteration_participations/new.html.erb +3 -0
  30. data/app/views/scholarship/iteration_participations/with_state.html.erb +1 -0
  31. data/app/views/scholarship/iterations/_actions.html.erb +19 -0
  32. data/app/views/scholarship/iterations/_form.html.erb +14 -0
  33. data/app/views/scholarship/iterations/edit.html.erb +3 -0
  34. data/app/views/scholarship/iterations/index.html.erb +33 -0
  35. data/app/views/scholarship/iterations/new.html.erb +3 -0
  36. data/app/views/scholarship/iterations/show.html.erb +9 -0
  37. data/app/views/scholarship/programs/_actions.html.erb +19 -0
  38. data/app/views/scholarship/programs/_form.html.erb +13 -0
  39. data/app/views/scholarship/programs/edit.html.erb +3 -0
  40. data/app/views/scholarship/programs/index.html.erb +33 -0
  41. data/app/views/scholarship/programs/new.html.erb +3 -0
  42. data/app/views/scholarship/programs/show.html.erb +11 -0
  43. data/app/views/scholarship/team_memberships/_actions.html.erb +18 -0
  44. data/app/views/scholarship/team_memberships/_collection.html.erb +47 -0
  45. data/app/views/scholarship/team_memberships/_form.html.erb +22 -0
  46. data/app/views/scholarship/team_memberships/edit.html.erb +3 -0
  47. data/app/views/scholarship/team_memberships/index.html.erb +3 -0
  48. data/app/views/scholarship/team_memberships/new.html.erb +3 -0
  49. data/app/views/scholarship/team_memberships/with_state.html.erb +1 -0
  50. data/app/views/scholarship/teams/_actions.html.erb +19 -0
  51. data/app/views/scholarship/teams/_form.html.erb +15 -0
  52. data/app/views/scholarship/teams/edit.html.erb +3 -0
  53. data/app/views/scholarship/teams/index.html.erb +29 -0
  54. data/app/views/scholarship/teams/new.html.erb +3 -0
  55. data/app/views/scholarship/teams/show.html.erb +19 -0
  56. data/app/views/scholarship/workflow/index.html.erb +0 -0
  57. data/app/views/scholarship/workflow/organization_owner/index.html.erb +24 -0
  58. data/app/views/scholarship/workflow/team_leader/index.html.erb +24 -0
  59. data/config/locales/products/scholarship/workflow/en.yml +12 -0
  60. data/config/locales/resources/scholarship_iteration/en.yml +22 -0
  61. data/config/locales/resources/scholarship_iteration_participation/en.yml +39 -0
  62. data/config/locales/resources/scholarship_program/en.yml +16 -0
  63. data/config/locales/resources/scholarship_team/en.yml +23 -0
  64. data/config/locales/resources/scholarship_team_membership/en.yml +30 -0
  65. data/config/main_scholarship_navigation.rb +3 -0
  66. data/config/routes.rb +54 -0
  67. data/db/migrate/20140306201232_add_scholarship_product.rb +12 -0
  68. data/db/migrate/20140310181004_create_scholarship_programs.rb +12 -0
  69. data/db/migrate/20140310192931_create_scholarship_iterations.rb +13 -0
  70. data/db/migrate/20140311173818_create_scholarship_teams.rb +28 -0
  71. data/db/migrate/20140314154216_create_scholarship_iteration_participations.rb +15 -0
  72. data/db/schema.rb +85 -0
  73. data/lib/tasks/voluntary_scholarship_tasks.rake +4 -0
  74. data/lib/voluntary_scholarship.rb +13 -0
  75. data/lib/voluntary_scholarship/ability.rb +34 -0
  76. data/lib/voluntary_scholarship/concerns/model/has_scholarship_programs.rb +13 -0
  77. data/lib/voluntary_scholarship/concerns/model/user/has_scholarship_teams.rb +43 -0
  78. data/lib/voluntary_scholarship/engine.rb +28 -0
  79. data/lib/voluntary_scholarship/navigation.rb +114 -0
  80. data/lib/voluntary_scholarship/version.rb +3 -0
  81. metadata +468 -0
@@ -0,0 +1,10 @@
1
+ class Scholarship::Workflow::TeamLeaderController < ApplicationController
2
+ include ::Scholarship::BaseController
3
+
4
+ before_filter :authenticate_user!
5
+
6
+ def index
7
+ @team_memberships = Scholarship::TeamMembership.of_team_leader(current_user).requested.
8
+ order_by_user_full_name.includes(:team).paginate(page: params[:page], per_page: 25)
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class Scholarship::WorkflowController < ApplicationController
2
+ include ::Scholarship::BaseController
3
+
4
+ before_filter :authenticate_user!
5
+
6
+ def index
7
+ @twitter_sidenav_level = 2
8
+ @sidenav_links_count = 1
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module VoluntaryScholarship
2
+ module IterationParticipationsHelper
3
+ def can_update_scholarship_iteration_participation_roles?(iteration_participation)
4
+ iteration_participation.new_record? ||
5
+ current_user.is_master? ||
6
+ current_user.id == iteration_participation.iteration.program.organization.user_id ||
7
+ (can?(:update, iteration_participation) && iteration_participation.accepted?)
8
+ end
9
+
10
+ def destroy_scholarship_iteration_participation_link(iteration_participation)
11
+ link_to(
12
+ t('scholarship_iteration_participations.destroy.title'),
13
+ scholarship_iteration_participation_path(iteration_participation.id), id: "iteration_participation_#{iteration_participation.id}", method: :delete,
14
+ data: { confirm: t('general.questions.are_you_sure') },
15
+ onclick: "delete_link('iteration_participation_#{iteration_participation.id}'); return false;"
16
+ )
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module VoluntaryScholarship
2
+ module TeamMembershipsHelper
3
+ def can_update_scholarship_team_membership_roles?(team_membership)
4
+ team_membership.new_record? ||
5
+ current_user.is_master? ||
6
+ current_user.is_leader_of_scholarship_team?(team_membership.team) ||
7
+ (can?(:update, team_membership) && team_membership.accepted?)
8
+ end
9
+
10
+ def destroy_scholarship_team_membership_link(team_membership)
11
+ link_to(
12
+ t('scholarship_team_memberships.destroy.title'),
13
+ scholarship_team_membership_path(team_membership.id), id: "team_membership_#{team_membership.id}", method: :delete,
14
+ data: { confirm: t('general.questions.are_you_sure') },
15
+ onclick: "delete_link('team_membership_#{team_membership.id}'); return false;"
16
+ )
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ module Scholarship
2
+ module RoleRequest
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ const_set 'EVENTS', [:accept, :deny, :change_roles]
7
+
8
+ scope :order_by_user_full_name, -> do
9
+ joins(:user).
10
+ select(table_name + '.*, CONCAT(users.first_name, " ", users.last_name) AS user_full_name').
11
+ order('user_full_name ASC')
12
+ end
13
+
14
+ scope :requested, -> { where(state: 'requested') }
15
+ scope :accepted, -> { where(state: 'accepted') }
16
+ scope :denied, -> { where(state: 'denied') }
17
+ scope :changed_roles, -> { where(state: 'changed_roles') }
18
+
19
+ validates :roles, presence: true
20
+
21
+ state_machine :state, initial: :requested do
22
+ event :accept do
23
+ transition [:requested, :denied, :changed_roles] => :accepted
24
+ end
25
+
26
+ event :deny do
27
+ transition [:requested, :accepted] => :denied
28
+ end
29
+
30
+ event :change_roles do
31
+ transition :accepted => :changed_roles
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,2 @@
1
+ class Product::Scholarship < ::Product
2
+ end
@@ -0,0 +1,36 @@
1
+ module Scholarship
2
+ class Iteration < ActiveRecord::Base
3
+ self.table_name = 'scholarship_iterations'
4
+
5
+ include Applicat::Mvc::Model::Resource::Base
6
+
7
+ belongs_to :program, class_name: 'Scholarship::Program'
8
+
9
+ has_many :participations, class_name: 'Scholarship::IterationParticipation'
10
+ has_many :participants, class_name: 'User', through: :participations
11
+
12
+ validates :program_id, presence: true, uniqueness: { scope: [:from, :to] }
13
+ validates :from, presence: true
14
+ validates :to, presence: true
15
+ validate :to_must_be_greater_than_from
16
+
17
+ attr_accessible :name, :program_id, :from, :to
18
+
19
+ PARENT_TYPES = ['scholarship_program']
20
+
21
+ def to_s
22
+ name.present? ? name : "#{program.name} #{from.strftime('%d.%m.%Y')} - #{to.strftime('%d.%m.%Y')}"
23
+ end
24
+
25
+ private
26
+
27
+ def to_must_be_greater_than_from
28
+ if from > to
29
+ errors.add(
30
+ :to,
31
+ I18n.t('activerecord.errors.models.scholarship_iteration.attributes.to.to_must_be_greater_than_from')
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,66 @@
1
+ module Scholarship
2
+ class IterationParticipation < ActiveRecord::Base
3
+ ROLES = [:student, :coach, :mentor, :helpdesk, :supervisor, :organizer, :developer]
4
+
5
+ self.table_name = 'scholarship_iteration_participations'
6
+
7
+ include Applicat::Mvc::Model::Resource::Base
8
+ include RoleRequest
9
+
10
+ belongs_to :iteration, class_name: 'Scholarship::Iteration'
11
+ belongs_to :user
12
+ belongs_to :team, class_name: 'Scholarship::Team'
13
+
14
+ scope :of_organization_owner, ->(user) { where(iteration_id: user.scholarship_iterations_as_organization_owner.map(&:id)) }
15
+
16
+ validates :iteration_id, presence: true
17
+ validates :user_id, presence: true, uniqueness: { scope: :iteration_id }
18
+ validate :accepted_team_membership_for_team_roles
19
+
20
+ attr_accessible :iteration_id, :text, :team_id, :roles
21
+
22
+ bitmask :roles, as: ROLES
23
+
24
+ after_create :accept_organization_owner_participations
25
+
26
+ private
27
+
28
+ def accepted_team_membership_for_team_roles
29
+ team_roles = roles.select{|role| TeamMembership::ROLES.include?(role) }
30
+
31
+ return unless team_roles.any?
32
+
33
+ if team_id.blank?
34
+ errors.add(:team_id, I18n.t('errors.messages.blank'))
35
+ return
36
+ end
37
+
38
+ unless team_membership = team.memberships.where(user_id: user_id).first
39
+ errors.add(
40
+ :team_id,
41
+ I18n.t('activerecord.errors.models.scholarship/iteration_participation.attributes.team_id.no_team_membership')
42
+ )
43
+
44
+ return
45
+ end
46
+
47
+ unless team_membership.accepted?
48
+ errors.add(
49
+ :team_id,
50
+ I18n.t('activerecord.errors.models.scholarship/iteration_participation.attributes.team_id.no_accepted_team_membership')
51
+ )
52
+ end
53
+
54
+ if team_roles.select{|role| !team_membership.roles.include?(role)}.any?
55
+ errors.add(
56
+ :team_id,
57
+ I18n.t('activerecord.errors.models.scholarship/iteration_participation.attributes.team_id.team_role_not_included_in_team_membership_roles')
58
+ )
59
+ end
60
+ end
61
+
62
+ def accept_organization_owner_participations
63
+ accept! if iteration.program.organization.user_id == user_id
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,18 @@
1
+ module Scholarship
2
+ class Program < ActiveRecord::Base
3
+ self.table_name = 'scholarship_programs'
4
+
5
+ include Applicat::Mvc::Model::Resource::Base
6
+
7
+ belongs_to :organization
8
+
9
+ has_many :iterations, class_name: 'Scholarship::Iteration', dependent: :destroy
10
+
11
+ validates :organization_id, presence: true
12
+ validates :name, presence: true, uniqueness: { scope: [:organization_id] }
13
+
14
+ attr_accessible :name, :text, :organization_id
15
+
16
+ PARENT_TYPES = ['organization']
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ module Scholarship
2
+ class Team < ActiveRecord::Base
3
+ KINDS = %w(sponsored voluntary)
4
+
5
+ self.table_name = 'scholarship_teams'
6
+
7
+ include Applicat::Mvc::Model::Resource::Base
8
+
9
+ has_many :memberships, class_name: 'Scholarship::TeamMembership'
10
+ has_many :members, class_name: 'User', through: :memberships
11
+
12
+ validates :name, presence: true, uniqueness: true
13
+ validates :kind, presence: true, inclusion: { in: KINDS }
14
+
15
+ attr_accessible :name, :text, :kind, :github_handle, :twitter_handle
16
+
17
+ attr_accessor :leader
18
+
19
+ after_create :create_team_leader_membership
20
+
21
+ extend FriendlyId
22
+ friendly_id :name, use: :slugged
23
+
24
+ private
25
+
26
+ def create_team_leader_membership
27
+ membership = memberships.new(roles: [:team_leader])
28
+ membership.user_id = leader.try(:id)
29
+ membership.save!
30
+ membership.accept!
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ module Scholarship
2
+ class TeamMembership < ActiveRecord::Base
3
+ ROLES = [:team_leader, :student, :coach, :mentor, :supervisor]
4
+
5
+ self.table_name = 'scholarship_team_memberships'
6
+
7
+ include Applicat::Mvc::Model::Resource::Base
8
+ include RoleRequest
9
+
10
+ belongs_to :team, class_name: 'Scholarship::Team'
11
+ belongs_to :user
12
+
13
+ scope :of_team_leader, ->(user) { where(team_id: user.scholarship_teams_as_leader.map(&:id)) }
14
+
15
+ validates :team_id, presence: true
16
+ validates :user_id, presence: true, uniqueness: { scope: :team_id }
17
+
18
+ attr_accessible :team_id, :roles, :text
19
+
20
+ bitmask :roles, as: ROLES
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ <% if can?(:destroy, iteration_participation) || can?(:edit, iteration_participation) %>
2
+ <div class="dropdown">
3
+ <a class="dropdown-toggle" data-toggle="dropdown" href="#"><%= t('general.actions') %></a>
4
+ <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
5
+ <% if can? :destroy, iteration_participation %>
6
+ <li><%= destroy_scholarship_iteration_participation_link(iteration_participation) %></li>
7
+ <% end %>
8
+ <% if can? :edit, iteration_participation %>
9
+ <li><%= link_to t('general.edit'), eval("edit_scholarship_iteration_participation_path(iteration_participation)") %></li>
10
+ <% end %>
11
+
12
+ <% if iteration_participation.state_events.select{|event| can? event, iteration_participation }.any? %>
13
+ <li class="divider"></li>
14
+ <% end %>
15
+ <%= render 'shared/resource/event_elements', resource: iteration_participation, type: 'scholarship_iteration_participations' %>
16
+ </ul>
17
+ </div>
18
+ <% end %>
@@ -0,0 +1,51 @@
1
+ <% show_iteration ||= false %>
2
+ <% if @iteration_participations.none? || @iteration_participations.select{|e| e.id.present? }.none? %>
3
+ <p><%= I18n.t("scholarship_iteration_participations.index.empty_collection")%></p>
4
+ <% else %>
5
+ <table class="table table-striped">
6
+ <thead>
7
+ <tr class="<%= cycle('odd', 'even') %>">
8
+ <th><%= t('activerecord.models.user') %></th>
9
+ <% if show_iteration %>
10
+ <th><%= t('activerecord.models.scholarship/organization') %></th>
11
+ <th><%= t('activerecord.models.scholarship/iteration') %></th>
12
+ <% end %>
13
+ <th><%= t('attributes.roles') %></th>
14
+ <th></th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ <% @iteration_participations.select{|e| e.id.present? }.each do |iteration_participation| %>
19
+ <tr class="<%= cycle('odd', 'even') %>">
20
+ <td>
21
+ <%= link_to iteration_participation.user.full_name, user_path(iteration_participation.user) %>
22
+ </td>
23
+ <% if show_iteration %>
24
+ <td>
25
+ <%= link_to iteration_participation.iteration.program.organization, organization_path(iteration_participation.iteration.program.organization) %>
26
+ </td>
27
+ <td>
28
+ <%= link_to iteration_participation.iteration, scholarship_iteration_path(iteration_participation.iteration) %>
29
+ </td>
30
+ <% end %>
31
+ <td>
32
+ <%= iteration_participation.roles.map(&:to_s).map(&:humanize).join(', ') %>
33
+ </td>
34
+ <td>
35
+ <%= render partial: 'scholarship/iteration_participations/actions', locals: { iteration_participation: iteration_participation } %>
36
+ </td>
37
+ </tr>
38
+ <% end %>
39
+ </tbody>
40
+ </table>
41
+ <% end %>
42
+
43
+ <%= will_paginate @iteration_participations %>
44
+
45
+ <% if @iteration.present? %>
46
+ <% if current_user.is_participant_of_scholarship_iteration?(@iteration) %>
47
+ <%= destroy_scholarship_iteration_participation_link(current_user.participation_of_scholarship_iteration(@iteration)) %>
48
+ <% elsif can?(:create, Scholarship::IterationParticipation) %>
49
+ <%= link_to t("scholarship_iteration_participations.new.title"), new_scholarship_iteration_participant_path(@iteration) %>
50
+ <% end %>
51
+ <% end %>
@@ -0,0 +1,23 @@
1
+ <%= simple_form_for(@iteration_participation, html: {class: 'form-horizontal'}) do |f| %>
2
+ <%= f.input :iteration_id, as: :hidden %>
3
+ <%= render partial: 'shared/form/error_messages', locals: { resource: @iteration_participation } %>
4
+
5
+ <div class="form-inputs">
6
+ <%= f.input :team_id, collection: current_user.scholarship_teams %>
7
+ <% if can_update_scholarship_iteration_participation_roles?(@iteration_participation) %>
8
+ <%= f.input :roles, as: :check_boxes, collection: Scholarship::IterationParticipation::ROLES.map { |r| [r.capitalize, r] }, include_hidden: false, label: t('attributes.roles') %>
9
+ <% else %>
10
+ <div class="control-group string required">
11
+ <label class="string required control-label">
12
+ <abbr title="required">*</abbr> Roles
13
+ </label>
14
+ <div class="controls"><%= @iteration_participation.roles.map(&:to_s).map(&:humanize).join(', ') %></div>
15
+ </div>
16
+ <% end %>
17
+ <%= f.input :text, label: t('attributes.text'), input_html: {style: 'width: 500px; height:300px;'} %>
18
+ </div>
19
+
20
+ <div class="form-actions">
21
+ <%= f.button :submit %>
22
+ </div>
23
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <h3><%= t('scholarship_iteration_participations.edit.title') %></h3>
2
+
3
+ <%= render 'form' %>
@@ -0,0 +1,3 @@
1
+ <h3><%= t("scholarship_iteration_participations.index.title") %></h3>
2
+
3
+ <%= render 'collection' %>
@@ -0,0 +1,3 @@
1
+ <h3><%= t('scholarship_iteration_participations.new.title') %></h3>
2
+
3
+ <%= render 'form' %>
@@ -0,0 +1 @@
1
+ <%= render partial: 'collection', locals: { show_iteration: true } %>
@@ -0,0 +1,19 @@
1
+ <% if can?(:destroy, iteration) || can?(:edit, iteration) %>
2
+ <div class="dropdown">
3
+ <a class="dropdown-toggle" data-toggle="dropdown" href="#"><%= t('general.actions') %></a>
4
+ <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
5
+ <% if can? :destroy, iteration %>
6
+ <li>
7
+ <%= link_to(
8
+ t('general.destroy'), scholarship_iteration_path(iteration.id), id: "iteration_#{iteration.id}", method: :delete,
9
+ data: { confirm: t('general.questions.are_you_sure') },
10
+ onclick: "delete_link('iteration_#{iteration.id}'); return false;"
11
+ ) %>
12
+ </li>
13
+ <% end %>
14
+ <% if can? :edit, iteration %>
15
+ <li><%= link_to t('general.edit'), eval("edit_scholarship_iteration_path(iteration)") %></li>
16
+ <% end %>
17
+ </ul>
18
+ </div>
19
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <%= simple_form_for(@iteration, html: {class: 'form-horizontal'}) do |f| %>
2
+ <%= f.hidden_field :program_id %>
3
+ <%= render partial: 'shared/form/error_messages', locals: { resource: @iteration } %>
4
+
5
+ <div class="form-inputs">
6
+ <%= f.input :name %>
7
+ <%= f.input :from, as: :string, input_html: { class: 'datepicker' } %>
8
+ <%= f.input :to, as: :string, input_html: { class: 'datepicker' } %>
9
+ </div>
10
+
11
+ <div class="form-actions">
12
+ <%= f.button :submit %>
13
+ </div>
14
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <h3><%= t('scholarship_iterations.edit.title') %></h3>
2
+
3
+ <%= render 'form' %>