voluntary_scholarship 0.0.1

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.
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' %>