voluntary_scholarship 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +23 -0
- data/app/controllers/concerns/scholarship/base_controller.rb +17 -0
- data/app/controllers/product/scholarship_controller.rb +6 -0
- data/app/controllers/scholarship/iteration_participations_controller.rb +80 -0
- data/app/controllers/scholarship/iterations_controller.rb +72 -0
- data/app/controllers/scholarship/programs_controller.rb +66 -0
- data/app/controllers/scholarship/team_memberships_controller.rb +77 -0
- data/app/controllers/scholarship/teams_controller.rb +63 -0
- data/app/controllers/scholarship/workflow/organization_owner_controller.rb +10 -0
- data/app/controllers/scholarship/workflow/team_leader_controller.rb +10 -0
- data/app/controllers/scholarship/workflow_controller.rb +10 -0
- data/app/helpers/voluntary_scholarship/iteration_participations_helper.rb +19 -0
- data/app/helpers/voluntary_scholarship/team_memberships_helper.rb +19 -0
- data/app/models/concerns/scholarship/role_request.rb +36 -0
- data/app/models/product/scholarship.rb +2 -0
- data/app/models/scholarship/iteration.rb +36 -0
- data/app/models/scholarship/iteration_participation.rb +66 -0
- data/app/models/scholarship/program.rb +18 -0
- data/app/models/scholarship/team.rb +33 -0
- data/app/models/scholarship/team_membership.rb +22 -0
- data/app/views/product/scholarship/index.html.erb +0 -0
- data/app/views/scholarship/iteration_participations/_actions.html.erb +18 -0
- data/app/views/scholarship/iteration_participations/_collection.html.erb +51 -0
- data/app/views/scholarship/iteration_participations/_form.html.erb +23 -0
- data/app/views/scholarship/iteration_participations/edit.html.erb +3 -0
- data/app/views/scholarship/iteration_participations/index.html.erb +3 -0
- data/app/views/scholarship/iteration_participations/new.html.erb +3 -0
- data/app/views/scholarship/iteration_participations/with_state.html.erb +1 -0
- data/app/views/scholarship/iterations/_actions.html.erb +19 -0
- data/app/views/scholarship/iterations/_form.html.erb +14 -0
- data/app/views/scholarship/iterations/edit.html.erb +3 -0
- data/app/views/scholarship/iterations/index.html.erb +33 -0
- data/app/views/scholarship/iterations/new.html.erb +3 -0
- data/app/views/scholarship/iterations/show.html.erb +9 -0
- data/app/views/scholarship/programs/_actions.html.erb +19 -0
- data/app/views/scholarship/programs/_form.html.erb +13 -0
- data/app/views/scholarship/programs/edit.html.erb +3 -0
- data/app/views/scholarship/programs/index.html.erb +33 -0
- data/app/views/scholarship/programs/new.html.erb +3 -0
- data/app/views/scholarship/programs/show.html.erb +11 -0
- data/app/views/scholarship/team_memberships/_actions.html.erb +18 -0
- data/app/views/scholarship/team_memberships/_collection.html.erb +47 -0
- data/app/views/scholarship/team_memberships/_form.html.erb +22 -0
- data/app/views/scholarship/team_memberships/edit.html.erb +3 -0
- data/app/views/scholarship/team_memberships/index.html.erb +3 -0
- data/app/views/scholarship/team_memberships/new.html.erb +3 -0
- data/app/views/scholarship/team_memberships/with_state.html.erb +1 -0
- data/app/views/scholarship/teams/_actions.html.erb +19 -0
- data/app/views/scholarship/teams/_form.html.erb +15 -0
- data/app/views/scholarship/teams/edit.html.erb +3 -0
- data/app/views/scholarship/teams/index.html.erb +29 -0
- data/app/views/scholarship/teams/new.html.erb +3 -0
- data/app/views/scholarship/teams/show.html.erb +19 -0
- data/app/views/scholarship/workflow/index.html.erb +0 -0
- data/app/views/scholarship/workflow/organization_owner/index.html.erb +24 -0
- data/app/views/scholarship/workflow/team_leader/index.html.erb +24 -0
- data/config/locales/products/scholarship/workflow/en.yml +12 -0
- data/config/locales/resources/scholarship_iteration/en.yml +22 -0
- data/config/locales/resources/scholarship_iteration_participation/en.yml +39 -0
- data/config/locales/resources/scholarship_program/en.yml +16 -0
- data/config/locales/resources/scholarship_team/en.yml +23 -0
- data/config/locales/resources/scholarship_team_membership/en.yml +30 -0
- data/config/main_scholarship_navigation.rb +3 -0
- data/config/routes.rb +54 -0
- data/db/migrate/20140306201232_add_scholarship_product.rb +12 -0
- data/db/migrate/20140310181004_create_scholarship_programs.rb +12 -0
- data/db/migrate/20140310192931_create_scholarship_iterations.rb +13 -0
- data/db/migrate/20140311173818_create_scholarship_teams.rb +28 -0
- data/db/migrate/20140314154216_create_scholarship_iteration_participations.rb +15 -0
- data/db/schema.rb +85 -0
- data/lib/tasks/voluntary_scholarship_tasks.rake +4 -0
- data/lib/voluntary_scholarship.rb +13 -0
- data/lib/voluntary_scholarship/ability.rb +34 -0
- data/lib/voluntary_scholarship/concerns/model/has_scholarship_programs.rb +13 -0
- data/lib/voluntary_scholarship/concerns/model/user/has_scholarship_teams.rb +43 -0
- data/lib/voluntary_scholarship/engine.rb +28 -0
- data/lib/voluntary_scholarship/navigation.rb +114 -0
- data/lib/voluntary_scholarship/version.rb +3 -0
- metadata +468 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
en:
|
2
|
+
scholarship_programs:
|
3
|
+
index:
|
4
|
+
title: Scholarship Programs
|
5
|
+
short_title: Programs
|
6
|
+
name: Name
|
7
|
+
empty_collection: No scholarship programs available.
|
8
|
+
|
9
|
+
new:
|
10
|
+
title: New Scholarship Program
|
11
|
+
edit:
|
12
|
+
title: Edit Scholarship Program
|
13
|
+
|
14
|
+
activerecord:
|
15
|
+
models:
|
16
|
+
scholarship/program: Scholarship Program
|
@@ -0,0 +1,23 @@
|
|
1
|
+
en:
|
2
|
+
scholarship_teams:
|
3
|
+
index:
|
4
|
+
title: Scholarship Teams
|
5
|
+
short_title: Teams
|
6
|
+
name: Name
|
7
|
+
empty_collection: No scholarship teams available.
|
8
|
+
|
9
|
+
new:
|
10
|
+
title: New Scholarship Team
|
11
|
+
edit:
|
12
|
+
title: Edit Scholarship Team
|
13
|
+
|
14
|
+
activerecord:
|
15
|
+
models:
|
16
|
+
scholarship/team: Scholarship Team
|
17
|
+
|
18
|
+
attributes:
|
19
|
+
scholarship/team:
|
20
|
+
kind: Kind
|
21
|
+
github_handle: Github handle
|
22
|
+
twitter_handle: Twitter handle
|
23
|
+
text: Text
|
@@ -0,0 +1,30 @@
|
|
1
|
+
en:
|
2
|
+
scholarship_team_memberships:
|
3
|
+
index:
|
4
|
+
title: Team Members
|
5
|
+
name: Name
|
6
|
+
empty_collection: No scholarship team memberships available.
|
7
|
+
|
8
|
+
new:
|
9
|
+
title: Join Team
|
10
|
+
edit:
|
11
|
+
title: Edit Team Membership
|
12
|
+
destroy:
|
13
|
+
title: Leave Team
|
14
|
+
|
15
|
+
show:
|
16
|
+
events:
|
17
|
+
accept: Accept
|
18
|
+
deny: Deny
|
19
|
+
change_roles: Change roles
|
20
|
+
|
21
|
+
workflow:
|
22
|
+
title: Team Memberships
|
23
|
+
requested: Requested
|
24
|
+
accepted: Accepted
|
25
|
+
changed_roles: Changed roles
|
26
|
+
denied:
|
27
|
+
|
28
|
+
activerecord:
|
29
|
+
models:
|
30
|
+
scholarship/team_membership: Scholarship Team Membership
|
data/config/routes.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
Rails.application.routes.draw do
|
2
|
+
get '/products/scholarship' => 'product/scholarship#index', as: 'scholarship_product'
|
3
|
+
|
4
|
+
namespace :scholarship do
|
5
|
+
resources :programs do
|
6
|
+
resources :iterations, only: [:index, :new]
|
7
|
+
end
|
8
|
+
|
9
|
+
resources :teams do
|
10
|
+
resources :members, controller: 'team_memberships', only: [:index, :new]
|
11
|
+
end
|
12
|
+
|
13
|
+
resources :team_memberships, only: [:create, :edit, :update, :destroy] do
|
14
|
+
collection do
|
15
|
+
get :with_state
|
16
|
+
end
|
17
|
+
|
18
|
+
member do
|
19
|
+
put :accept
|
20
|
+
put :deny
|
21
|
+
put :change_roles
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
resources :iterations, only: [:create, :show, :edit, :update, :destroy] do
|
26
|
+
resources :participants, controller: 'iteration_participations', only: [:index, :new]
|
27
|
+
end
|
28
|
+
|
29
|
+
resources :iteration_participations, only: [:create, :edit, :update, :destroy] do
|
30
|
+
collection do
|
31
|
+
get :with_state
|
32
|
+
end
|
33
|
+
|
34
|
+
member do
|
35
|
+
put :accept
|
36
|
+
put :deny
|
37
|
+
put :change_roles
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
get 'workflow' => 'workflow#index', as: :workflow
|
42
|
+
|
43
|
+
namespace 'workflow' do
|
44
|
+
resources :organization_owner, only: :index
|
45
|
+
resources :team_leader, only: :index
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
resources :organizations do
|
50
|
+
namespace :scholarship do
|
51
|
+
resources :programs, only: [:index, :new]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class AddScholarshipProduct < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
if product = Product.where(name: 'Scholarship').first
|
4
|
+
else
|
5
|
+
Product.create(name: 'Scholarship', text: 'Dummy')
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def down
|
10
|
+
product.destroy if product = Product.where(name: 'Scholarship').first
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateScholarshipPrograms < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :scholarship_programs do |t|
|
4
|
+
t.integer :organization_id
|
5
|
+
t.string :name
|
6
|
+
t.text :text
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :scholarship_programs, [:organization_id, :name], unique: true
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateScholarshipIterations < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :scholarship_iterations do |t|
|
4
|
+
t.integer :program_id
|
5
|
+
t.string :name
|
6
|
+
t.datetime :from
|
7
|
+
t.datetime :to
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :scholarship_iterations, [:program_id, :from, :to], unique: true
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class CreateScholarshipTeams < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :scholarship_teams do |t|
|
4
|
+
t.string :name
|
5
|
+
t.string :slug
|
6
|
+
t.text :text
|
7
|
+
t.string :kind
|
8
|
+
t.string :github_handle
|
9
|
+
t.string :twitter_handle
|
10
|
+
t.string :state
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
add_index :scholarship_teams, :name, unique: true
|
15
|
+
add_index :scholarship_teams, :slug, unique: true
|
16
|
+
|
17
|
+
create_table :scholarship_team_memberships do |t|
|
18
|
+
t.integer :team_id
|
19
|
+
t.integer :user_id
|
20
|
+
t.integer :roles
|
21
|
+
t.text :text
|
22
|
+
t.string :state
|
23
|
+
t.timestamps
|
24
|
+
end
|
25
|
+
|
26
|
+
add_index :scholarship_team_memberships, [:team_id, :user_id], unique: true
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateScholarshipIterationParticipations < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :scholarship_iteration_participations do |t|
|
4
|
+
t.integer :iteration_id
|
5
|
+
t.integer :user_id
|
6
|
+
t.integer :roles
|
7
|
+
t.integer :team_id
|
8
|
+
t.text :text
|
9
|
+
t.string :state
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
add_index :scholarship_iteration_participations, [:iteration_id, :user_id], unique: true, name: 'index_scholarship_iteration_participations_on_iteration_user'
|
14
|
+
end
|
15
|
+
end
|
data/db/schema.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
create_table :scholarship_programs do |t|
|
2
|
+
t.integer :organization_id
|
3
|
+
t.string :name
|
4
|
+
t.text :text
|
5
|
+
t.timestamps
|
6
|
+
end
|
7
|
+
|
8
|
+
add_index :scholarship_programs, [:organization_id, :name], unique: true
|
9
|
+
|
10
|
+
create_table :scholarship_iterations do |t|
|
11
|
+
t.integer :program_id
|
12
|
+
t.string :name
|
13
|
+
t.datetime :start
|
14
|
+
t.datetime :end
|
15
|
+
t.timestamps
|
16
|
+
end
|
17
|
+
|
18
|
+
add_index :scholarship_iterations, [:program_id, :start, :end], unique: true
|
19
|
+
|
20
|
+
create_table :scholarship_teams do |t|
|
21
|
+
t.string :name
|
22
|
+
t.text :text
|
23
|
+
t.string :kind
|
24
|
+
t.string :github_handle
|
25
|
+
t.string :twitter_handle
|
26
|
+
t.string :state
|
27
|
+
t.timestamps
|
28
|
+
end
|
29
|
+
|
30
|
+
add_index :scholarship_teams, :name, unique: true
|
31
|
+
|
32
|
+
create_table :scholarship_team_memberships do |t|
|
33
|
+
t.integer :team_id
|
34
|
+
t.integer :user_id
|
35
|
+
t.integer :roles # :team_leader, :student, :coach, :mentor, :supervisor
|
36
|
+
t.string :state
|
37
|
+
t.timestamps
|
38
|
+
end
|
39
|
+
|
40
|
+
add_index :scholarship_team_members, [:team_id, :user_id], unique: true
|
41
|
+
|
42
|
+
create_table :scholarship_iteration_participations do |t|
|
43
|
+
t.integer :iteration_id
|
44
|
+
t.integer :user_id
|
45
|
+
t.integer :roles
|
46
|
+
t.integer :team_id
|
47
|
+
t.text :text
|
48
|
+
t.string :state
|
49
|
+
t.timestamps
|
50
|
+
end
|
51
|
+
|
52
|
+
add_index :scholarship_iteration_participations, [:iteration_id, :user_id], unique: true, name: 'index_scholarship_iteration_participations_on_iteration_user'
|
53
|
+
|
54
|
+
create_table :scholarship_iterations_teams do |t|
|
55
|
+
t.integer :iteration_id
|
56
|
+
t.integer :team_id
|
57
|
+
t.date :start
|
58
|
+
t.date :end
|
59
|
+
t.string :state
|
60
|
+
t.timestamps
|
61
|
+
end
|
62
|
+
|
63
|
+
add_index :scholarship_program_user_roles, [:iteration_id, :user_id], unique: true, name: 'index_scholarship_program_user_roles_on_iteration_user'
|
64
|
+
|
65
|
+
add_column :users, :github_handle, :string unless User.new.respond_to? :github_handle
|
66
|
+
add_column :users, :twitter_handle, :string unless User.new.respond_to? :twitter_handle
|
67
|
+
add_column :users, :irc_handle, :string unless User.new.respond_to? :irc_handle
|
68
|
+
add_column :users, :timezone, :string unless User.new.respond_to? :timezone
|
69
|
+
add_column :users, :location, :string unless User.new.respond_to? :location
|
70
|
+
|
71
|
+
create_table :scholarship_roles do |t|
|
72
|
+
t.string :name
|
73
|
+
t.timestamps
|
74
|
+
end
|
75
|
+
|
76
|
+
add_index :scholarship_roles, :name, unique: true
|
77
|
+
|
78
|
+
create_table :scholarship_program_roles do |t|
|
79
|
+
t.integer :program_id
|
80
|
+
t.integer :role_id
|
81
|
+
t.timestamps
|
82
|
+
end
|
83
|
+
|
84
|
+
add_index :scholarship_program_roles, [:program_id, :role_id], unique: true
|
85
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'voluntary'
|
2
|
+
|
3
|
+
require 'bitmask_attributes'
|
4
|
+
|
5
|
+
require 'voluntary_scholarship/ability'
|
6
|
+
require 'voluntary_scholarship/navigation'
|
7
|
+
require 'voluntary_scholarship/concerns/model/user/has_scholarship_teams'
|
8
|
+
require 'voluntary_scholarship/concerns/model/has_scholarship_programs'
|
9
|
+
|
10
|
+
require "voluntary_scholarship/engine"
|
11
|
+
|
12
|
+
module VoluntaryScholarship
|
13
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module VoluntaryScholarship
|
2
|
+
class Ability
|
3
|
+
def self.after_initialize
|
4
|
+
Proc.new do |ability, user, options|
|
5
|
+
ability.can :read, [
|
6
|
+
Scholarship::Team, Scholarship::TeamMembership, Scholarship::Program, Scholarship::Iteration, Scholarship::IterationParticipation
|
7
|
+
]
|
8
|
+
|
9
|
+
if user.present?
|
10
|
+
ability.can(:restful_actions, Scholarship::Team) {|team| team.new_record? || user.is_leader_of_scholarship_team?(team) }
|
11
|
+
|
12
|
+
ability.can(:restful_actions, Scholarship::TeamMembership) do |membership|
|
13
|
+
membership.new_record? || membership.user_id == user.id || user.is_leader_of_scholarship_team?(membership.team)
|
14
|
+
end
|
15
|
+
|
16
|
+
ability.can([:with_state] + Scholarship::TeamMembership::EVENTS, Scholarship::TeamMembership) do |membership|
|
17
|
+
user.is_leader_of_scholarship_team?(membership.team)
|
18
|
+
end
|
19
|
+
|
20
|
+
ability.can(:restful_actions, Scholarship::IterationParticipation) do |participation|
|
21
|
+
participation.new_record? || participation.user_id == user.id || user.id == participation.iteration.program.organization.user_id
|
22
|
+
end
|
23
|
+
|
24
|
+
ability.can([:with_state] + Scholarship::IterationParticipation::EVENTS, Scholarship::IterationParticipation) do |participation|
|
25
|
+
user.id == participation.iteration.program.organization.user_id
|
26
|
+
end
|
27
|
+
|
28
|
+
ability.can(:restful_actions, Scholarship::Program) {|program| program.organization.blank? || program.organization.user_id == user.id }
|
29
|
+
ability.can(:restful_actions, Scholarship::Iteration) {|iteration| iteration.program.blank? || iteration.program.organization.user_id == user.id }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module VoluntaryScholarship
|
2
|
+
module Concerns
|
3
|
+
module Model
|
4
|
+
module HasScholarshipPrograms
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
has_many :scholarship_programs, class_name: 'Scholarship::Program', dependent: :destroy
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module VoluntaryScholarship
|
2
|
+
module Concerns
|
3
|
+
module Model
|
4
|
+
module User
|
5
|
+
module HasScholarshipTeams
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def scholarship_teams
|
9
|
+
Scholarship::Team.joins(:memberships).where('scholarship_team_memberships.user_id = ?', id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def scholarship_teams_as_leader
|
13
|
+
scholarship_teams.merge(Scholarship::TeamMembership.with_roles(:team_leader))
|
14
|
+
end
|
15
|
+
|
16
|
+
def scholarship_iterations_as_organization_owner
|
17
|
+
Scholarship::Iteration.joins(program: :organization).where('organizations.user_id = ?', id)
|
18
|
+
end
|
19
|
+
|
20
|
+
def is_leader_of_scholarship_team?(team)
|
21
|
+
team.memberships.where(user_id: id).with_roles(:team_leader).any?
|
22
|
+
end
|
23
|
+
|
24
|
+
def is_member_of_scholarship_team?(team)
|
25
|
+
team.memberships.where(user_id: id).any?
|
26
|
+
end
|
27
|
+
|
28
|
+
def membership_of_scholarship_team(team)
|
29
|
+
team.memberships.where(user_id: id).first
|
30
|
+
end
|
31
|
+
|
32
|
+
def is_participant_of_scholarship_iteration?(iteration)
|
33
|
+
iteration.participations.where(user_id: id).any?
|
34
|
+
end
|
35
|
+
|
36
|
+
def participation_of_scholarship_iteration(iteration)
|
37
|
+
iteration.participations.where(user_id: id).first
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'voluntary'
|
2
|
+
|
3
|
+
module VoluntaryScholarship
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
config.autoload_paths << File.expand_path("../../../app/models/concerns", __FILE__)
|
6
|
+
config.autoload_paths << File.expand_path("../../../app/controllers/concerns", __FILE__)
|
7
|
+
config.i18n.load_path += Dir[File.expand_path("../../../config/locales/**/*.{rb,yml}", __FILE__)]
|
8
|
+
|
9
|
+
SimpleNavigation::config_file_paths << File.expand_path("../../../config", __FILE__)
|
10
|
+
|
11
|
+
config.generators do |g|
|
12
|
+
g.orm :active_record
|
13
|
+
end
|
14
|
+
|
15
|
+
config.to_prepare do
|
16
|
+
User.send :include, ::VoluntaryScholarship::Concerns::Model::User::HasScholarshipTeams
|
17
|
+
Organization.send :include, ::VoluntaryScholarship::Concerns::Model::HasScholarshipPrograms
|
18
|
+
|
19
|
+
::Ability.add_after_initialize_callback(VoluntaryScholarship::Ability.after_initialize)
|
20
|
+
|
21
|
+
VoluntaryScholarship::Navigation.voluntary_menu_customization
|
22
|
+
end
|
23
|
+
|
24
|
+
initializer "voluntary_scholarship.add_view_helpers" do |config|
|
25
|
+
ActionView::Base.send :include, VoluntaryScholarship::TeamMembershipsHelper
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|