diff --git a/.gitignore b/.gitignore
index 2da7c79d1e..06e74f0bb7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,3 +67,4 @@ TAGS
*.iml
.generators
.idea
+.tool-versions
diff --git a/.rubocop.yml b/.rubocop.yml
index 8dca9e39d9..9e4acb2b23 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,5 +1,4 @@
AllCops:
- RunRailsCops: true
DisplayCopNames: true
Exclude:
- Rakefile
@@ -12,6 +11,12 @@ AllCops:
- tmp/**/*
- app/assets/config.rb
+Rails:
+ Enabled: true
+
+# we have not yet introcued ApplicationRecord as a Pattern
+Rails/ApplicationRecord:
+ Enabled: false
Metrics/AbcSize:
Max: 20
@@ -21,6 +26,10 @@ Metrics/ClassLength:
Max: 200
Severity: error
+Metrics/ModuleLength:
+ Max: 200
+ Severity: error
+
Metrics/CyclomaticComplexity:
Max: 6
Severity: error
@@ -28,12 +37,12 @@ Metrics/CyclomaticComplexity:
Metrics/LineLength:
Max: 100
Severity: warning
+ IgnoreCopDirectives: true
Metrics/MethodLength:
Max: 10
Severity: error
-
# Keep for now, easier with superclass definitions
ClassAndModuleChildren:
Enabled: false
@@ -55,10 +64,6 @@ Documentation:
DotPosition:
Enabled: false
-# Missing UTF-8 encoding statements should always be created.
-Encoding:
- Severity: error
-
# Keep single line bodys for if and unless
IfUnlessModifier:
Enabled: false
@@ -72,23 +77,31 @@ Rails/Delegate:
Enabled: false
# That's no huge stopper
-Style/EmptyLines:
+Layout/EmptyLines:
Enabled: false
# We thinks that's fine for specs
-Style/EmptyLinesAroundBlockBody:
+Layout/EmptyLinesAroundBlockBody:
Enabled: false
# We thinks that's fine
-Style/EmptyLinesAroundClassBody:
+Layout/EmptyLinesAroundClassBody:
Enabled: false
# We thinks that's fine
-Style/EmptyLinesAroundModuleBody:
+Layout/EmptyLinesAroundModuleBody:
Enabled: false
# We thinks that's fine
-Style/MultilineOperationIndentation:
+Layout/MultilineOperationIndentation:
+ Enabled: false
+
+# We are using Ruby 2+ anyway...
+Style/AsciiComments:
+ Enabled: false
+
+# For now, we keep encoding comment
+Style/Encoding:
Enabled: false
# We thinks that's fine
@@ -103,6 +116,19 @@ Style/SymbolProc:
Style/GuardClause:
Enabled: false
+# We think that's fine
+Style/PercentLiteralDelimiters:
+ PreferredDelimiters:
+ '%w': '()'
+
+# We think that's fine
+Style/SymbolArray:
+ EnforcedStyle: brackets
+
+Style/PercentLiteralDelimiters:
+ PreferredDelimiters:
+ '%w': '()'
+
# We thinks that's fine
Style/SingleLineBlockParams:
Enabled: false
diff --git a/.s2i/post_assemble b/.s2i/post_assemble
new file mode 100755
index 0000000000..ae88dc70cd
--- /dev/null
+++ b/.s2i/post_assemble
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+set -ex
+
+# this script is executed after our rails images default assemble script.
+
+pushd /opt/app-root/src
+
+# load development seeds when demo instance
+if [ $DEMO_INSTANCE -eq 1 ]; then
+ echo 'demo instance: creating symlink for loading development seeds'
+ for wagon in vendor/wagons/*
+ do
+ cd $wagon/db/seeds
+ ln -s development production
+ done
+fi
+
+if [ $PULL_TRANSIFEX -eq 1 ]; then
+ echo 'pulling transifex translations ...'
+ RAILS_HOST_NAME='build.hitobito.ch' bundle exec rake tx:pull tx:wagon:pull -t
+fi
+
+BUILD_DATE=$(date '+%Y-%m-%d %H:%M:%S')
+echo "(built at: $BUILD_DATE)" > BUILD_INFO
+
+popd
diff --git a/.s2i/post_deploy b/.s2i/post_deploy
new file mode 100755
index 0000000000..d773104041
--- /dev/null
+++ b/.s2i/post_deploy
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -ex
+
+# This script is executed after our rails images' default assemble script.
+
+pushd /opt/app-root/src
+
+bundle exec rake db:seed
+bundle exec rake wagon:migrate
+bundle exec rake wagon:seed
+
+for dir in vendor/wagons/*; do
+ if [[ -x $dir/.s2i/post_deploy ]] ; then
+ $dir/.s2i/post_deploy
+ fi
+done
+
+popd
diff --git a/.s2i/pre_assemble b/.s2i/pre_assemble
new file mode 100755
index 0000000000..fbd4334873
--- /dev/null
+++ b/.s2i/pre_assemble
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+set -ex
+
+source_dir=$(dirname $0)/..
+
+# this script places the core and wagon files in the right folders and creates the Wagonfile.
+# after this, we are able to use our rails images default assemble script to do the execute default
+# tasks like assets precompilation
+
+pushd $source_dir
+
+# update the composition-repo to newest versions of configured .gitmodules-branch
+# devel is our indicator for the integration-environment
+if [[ "x${OPENSHIFT_BUILD_REFERENCE}" = "xdevel" ]]; then
+ git submodule update --remote
+fi
+
+# move core
+rm -r hitobito/.git
+mv hitobito/* .
+
+# add wagon sources
+mkdir vendor/wagons
+for dir in hitobito_*; do
+ if [[ ( -d $dir ) ]]; then
+ rm -r $dir/.git
+ mv $dir vendor/wagons/
+ fi
+done
+
+# place Wagonfile
+mv -f config/rpm/Wagonfile .
+
+# move hidden core dirs
+rm -f .s2i/pre_assemble
+cp -rf hitobito/.s2i . # cannot be moved since it is in use during this script's execution
+mv hitobito/.tx .
+
+# finally remove core source directory
+rm -rf hitobito
+rm -r .git
+
+# TODO: Investigate. This seems ugly and is a hack to prevent assemble from failing with
+#
+# You are trying to install in deployment mode after changing
+# your Gemfile. Run `bundle install` elsewhere and add the
+# updated Gemfile.lock to version control.
+
+# You have added to the Gemfile:
+# * source: source at /home/sraez/dev/hitobito_generic_composition_apply/vendor/wagons/hitobito_generic
+# * hitobito_generic
+
+# You have deleted from the Gemfile:
+# * source: source at ../hitobito_insieme
+# * hitobito_insieme
+# This inludes fixes from https://github.com/bundler/bundler/issues/2854#issuecomment-38991901
+bundle install --no-deployment --path vendor/bundle
+# Speed up the second `bundle install` run
+bundle package --all
+
+popd
diff --git a/.travis.yml b/.travis.yml
index cb607f0222..8c12fc06eb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,7 @@
language: ruby
cache: bundler
+addons:
+ firefox: 45.0
branches:
only:
- master
@@ -9,18 +11,22 @@ env:
- HEADLESS=true
- RAILS_DB_ADAPTER=mysql2
rvm:
- - 1.9.3
- - 2.0.0
- 2.1.7
- 2.2.3
- 2.3.1
- 2.4.0
-matrix:
- allow_failures:
- - rvm: 2.4.0
+before_install:
+ - sudo apt-get -qq update
+ - sudo apt-get install sphinxsearch
+ - echo '[mysqld]' | sudo tee /etc/mysql/conf.d/sort_buffer_size.cnf > /dev/null
+ - echo 'sort_buffer_size = 2M' | sudo tee -a /etc/mysql/conf.d/sort_buffer_size.cnf > /dev/null
+ - sudo service mysql restart
install:
- sed -i "s/^\(gem .mysql2.\),.*$/\1/" Gemfile
- bundle install --path vendor/bundle
- bundle update mysql2
script:
- - bundle exec rake db:create ci --trace
+ - bundle exec rake db:create ci --trace skip_tasks=spec:features
+matrix:
+ allow_failures:
+ - rvm: 2.4.0
diff --git a/AUTHORS b/AUTHORS
index 5bfe5c3608..544d03b409 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -6,6 +6,7 @@ Code:
Pascal Simon, Puzzle ITC
Mathis Hofer, Puzzle ITC
Diego Steiner
+ Lukas Blunschi
Design & Style:
Roland Studer, Puzzle ITC
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0813a85250..7e30c456ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,13 +1,60 @@
# Hitobito Changelog
+## Version 1.X
+
+* Alle Personenfilter sind zusammengefasst und lassen sich abspeichern.
+* Personenfilter erlauben den Gültigzeitszeitraum einer Rolle einzuschränken.
+
+
+## Version 1.17
+
+* Export der Abonnenten einer Mailingliste wird im Hintergrund erstellt und per mail versendet
+
+
+## Version 1.16
+
+* Vorbedingungen von Kursarten können zusätzlich mit ODER verknüpft werden.
+* Für alle Anlässe lassen sich beliebige Administrationsangaben zu den Teilnehmenden definieren.
+* Anzeige der Hauptebene bei Personenexporten und Teilnehmerlisten.
+* Anlässe können dupliziert werden.
+* Personenfilter nach Qualifikationsdaten und mehreren Qualifikationen.
+* Sichtbarkeit der Anmeldungen auf Kursliste für alle Personen ist pro Kurs konfigurierbar.
+* Aktualisieren der Kontaktdaten bei der Eventanmeldung
+* Festlegen von Pflichtangaben zur Person bei der Eventanmeldung
+* Anmeldestand kann für alle sichtbar gemacht werden
+
+
+## Version 1.15
+
+* Neue Rolle "Helfer/-in" für Anlässe.
+* Unterschriften können nun bei allen Anlässen eingefordert werden.
+* Anzeige des Geburtsdatums in Anlassteilnahmelisten.
+* Notizen ebenfalls auf Gruppen möglich.
+* Alle Personen derselben Firma sind unter Person > Mitarbeiter/-innen ersichtlich.
+* Qualifikationen werden in Kursen erst auf Knopfdruck aktualisiert.
+* Anmeldedatum wird bei Anmeldeknopf auf Anlassliste angezeigt.
+
+
## Version 1.14
* Automatisches Ausfüllen der Kurs Beschreibung wenn ein Kurstyp gewählt wird.
+* Admin kann gelöschte Personen in der Volltextsuche finden.
+* Anfrageverfahren wird für gelöschte Personen ebenfalls ausgelöst.
+* Gelöschte Personen können pro Ebene angezeigt werden.
+* Benutzer/-innen können personalisierte Etiketten erstellen.
+* Übername und ein P.P. Post Feld können den Etiketten hinzugefügt werden.
+* Globale Suche nach Anlassnamen und Kursnummern.
+* Excel-Export für Personen und Anlässe.
+* CSV- und Excel-Exporte von Personen mit allen Angaben enthalten aktuelle Qualifikationen.
+* Der Verlauf einer Person zeigt neu die Rollen so an, dass die Gruppen auch die übergeordneten Ebenen anzeigt.
+* Der Verlauf einer Person wird neu nach der Gruppe inkl. übergeordneter Ebenen sortiert.
+
## Version 1.13
* Personen können in Mailinglisten nach Tags gefiltert werden.
+
## Version 1.12
* Zu Personen können eingeschränkt sichtbare Notizen hinterlegt werden.
@@ -96,4 +143,3 @@
* Separat definierbare Qualifikationstypen für Kursleiter.
* Pflichtfelder für Anlass Fragen.
* Mehrfachauswahl bei Personen Filter und Abo Listen.
-
diff --git a/Gemfile b/Gemfile
index 15947f501f..a7a53f471c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,59 +1,62 @@
# encoding: utf-8
-# Copyright (c) 2012-2014, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
source 'https://rubygems.org'
-gem 'rails', '4.2.7.1'
+gem 'rails', '4.2.8'
gem 'activerecord-session_store'
gem 'acts-as-taggable-on', '~> 3.5.0'
gem 'airbrake', '< 5.0' # requires newer errbit
gem 'axlsx', '2.0.1'
-gem 'awesome_nested_set'
+gem 'awesome_nested_set', '< 3.1.0' # requires ruby 2.0
gem 'bcrypt-ruby'
gem 'cancancan', '< 1.13.0' # requires ruby 2.0
-gem 'carrierwave'
+gem 'carrierwave', '< 0.11.1' # uses 2.0 for testing (no explicit requirement, yet)
gem 'cmess'
gem 'country_select'
gem 'daemons'
gem 'dalli'
gem 'delayed_job_active_record'
-gem 'devise'
+gem 'devise', '< 4.0.0' # requires ruby 2.1
gem 'draper'
-gem 'faker'
+gem 'faker', '< 1.6.4' # uses 2.0 for testing (no explicit requirement, yet)
gem 'globalize'
gem 'haml'
gem 'http_accept_language'
+gem 'icalendar'
gem 'magiclabs-userstamp', require: 'userstamp'
gem 'mime-types', '~> 2.6.2' # newer requires ruby 2.0
gem 'mini_magick'
-gem 'mysql2', '0.3.15' # 0.3.16 fails sphinx specs on jenkins
+gem 'mysql2', '0.4.9'
gem 'nested_form'
gem 'oat'
gem 'paper_trail'
-gem 'paranoia'
+gem 'paranoia', '< 2.1.2' # uses 2.0 for testing (no explicit requirement, yet)
gem 'customized_piwik_analytics', '~> 1.0.0'
gem 'prawn', '< 2.0' # 2.0 requires ruby 2.0
gem 'prawn-table'
gem 'protective'
gem 'rack'
gem 'rails_autolink'
-gem 'config'
+gem 'config', '< 1.1.0' # requires ruby 2
gem 'rails-i18n'
+gem 'rubyzip'
gem 'seed-fu'
gem 'simpleidn'
gem 'sqlite3' # for development, test and production when generating assets
gem 'thinking-sphinx'
gem 'validates_by_schema'
gem 'validates_timeliness', '< 4.0'
+gem 'vcard'
gem 'wagons'
# load after others because of active record inherited alias chain.
-gem 'kaminari'
+gem 'kaminari', '< 1.0.0' # requires ruby 2.0
# Gems used only for assets
gem 'bootstrap-sass', '~> 2.3'
@@ -71,6 +74,14 @@ gem 'therubyracer', platforms: :ruby
gem 'turbolinks'
gem 'uglifier'
+# if these are ever in your way, you can remove these lines.
+# they mostly serve as a version-restriction
+group :dependencies do
+ gem 'nokogiri', '< 1.7.0' # requires ruby 2.1
+ gem 'addressable', '< 2.5' # requires ruby 2.0
+ gem 'sort_alphabetical', '< 1.1.0' # requires ruby 2.0
+end
+
group :development, :test do
gem 'binding_of_caller'
gem 'rspec-rails'
@@ -90,14 +101,15 @@ end
group :test do
gem 'capybara'
+ gem 'capybara-screenshot'
gem 'database_cleaner'
gem 'fabrication'
gem 'headless'
gem 'launchy'
gem 'rspec-its'
gem 'rspec-collection_matchers'
- gem 'selenium-webdriver'
- gem 'timecop'
+ gem 'selenium-webdriver', '2.51.0' # 3.2.2 fails with "Unable to find Mozilla geckodriver"
+ gem 'pdf-inspector', require: 'pdf/inspector'
end
group :console do
@@ -127,4 +139,4 @@ end
#
# To create a Wagonfile suitable for development, run 'rake wagon:file'
wagonfile = File.expand_path('../Wagonfile', __FILE__)
-eval(File.read(wagonfile)) if File.exist?(wagonfile)
+eval(File.read(wagonfile)) if File.exist?(wagonfile) # rubocop:disable Security/Eval
diff --git a/Gemfile.lock b/Gemfile.lock
index 22cd1d7aff..4abe315fa1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,66 +1,78 @@
+PATH
+ remote: ../hitobito_pbs
+ specs:
+ hitobito_pbs (0.0.1)
+ hitobito_youth
+
+PATH
+ remote: ../hitobito_youth
+ specs:
+ hitobito_youth (0.0.1)
+
GEM
remote: https://rubygems.org/
specs:
- actionmailer (4.2.7.1)
- actionpack (= 4.2.7.1)
- actionview (= 4.2.7.1)
- activejob (= 4.2.7.1)
+ Ascii85 (1.0.2)
+ actionmailer (4.2.8)
+ actionpack (= 4.2.8)
+ actionview (= 4.2.8)
+ activejob (= 4.2.8)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.7.1)
- actionview (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ actionpack (4.2.8)
+ actionview (= 4.2.8)
+ activesupport (= 4.2.8)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.7.1)
- activesupport (= 4.2.7.1)
+ actionview (4.2.8)
+ activesupport (= 4.2.8)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (4.2.7.1)
- activesupport (= 4.2.7.1)
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
+ activejob (4.2.8)
+ activesupport (= 4.2.8)
globalid (>= 0.3.0)
- activemodel (4.2.7.1)
- activesupport (= 4.2.7.1)
+ activemodel (4.2.8)
+ activesupport (= 4.2.8)
builder (~> 3.1)
- activerecord (4.2.7.1)
- activemodel (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ activerecord (4.2.8)
+ activemodel (= 4.2.8)
+ activesupport (= 4.2.8)
arel (~> 6.0)
- activerecord-session_store (0.1.2)
- actionpack (>= 4.0.0, < 5)
- activerecord (>= 4.0.0, < 5)
- railties (>= 4.0.0, < 5)
- activesupport (4.2.7.1)
+ activerecord-session_store (1.0.0)
+ actionpack (>= 4.0, < 5.1)
+ activerecord (>= 4.0, < 5.1)
+ multi_json (~> 1.11, >= 1.11.2)
+ rack (>= 1.5.2, < 3)
+ railties (>= 4.0, < 5.1)
+ activesupport (4.2.8)
i18n (~> 0.7)
- json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
acts-as-taggable-on (3.5.0)
activerecord (>= 3.2, < 5)
addressable (2.4.0)
+ afm (0.2.2)
airbrake (4.3.4)
builder
multi_json
- annotate (2.7.0)
+ annotate (2.7.1)
activerecord (>= 3.2, < 6.0)
- rake (~> 10.4)
- arel (6.0.3)
- ast (2.2.0)
- astrolabe (1.3.1)
- parser (~> 2.2)
+ rake (>= 10.4, < 12.0)
+ arel (6.0.4)
+ ast (2.3.0)
awesome_nested_set (3.0.2)
activerecord (>= 4.0.0, < 5)
- awesome_print (1.6.1)
+ awesome_print (1.7.0)
axlsx (2.0.1)
htmlentities (~> 4.3.1)
nokogiri (>= 1.4.1)
rubyzip (~> 1.0.0)
- bcrypt (3.1.10)
+ bcrypt (3.1.11)
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
binding_of_caller (0.7.2)
@@ -69,63 +81,55 @@ GEM
sass (~> 3.2)
bootstrap-wysihtml5-rails (0.3.1.24)
railties (>= 3.0)
- brakeman (3.1.4)
- erubis (~> 2.6)
- fastercsv (~> 1.5)
- haml (>= 3.0, < 5.0)
- highline (>= 1.6.20, < 2.0)
- multi_json (~> 1.2)
- ruby2ruby (>= 2.1.1, < 2.3.0)
- ruby_parser (~> 3.7.0)
- safe_yaml (>= 1.0)
- sass (~> 3.0)
- slim (>= 1.3.6, < 4.0)
- terminal-table (~> 1.4)
- builder (3.2.2)
- bullet (4.14.10)
+ brakeman (3.5.0)
+ builder (3.2.3)
+ bullet (5.5.1)
activesupport (>= 3.0.0)
- uniform_notifier (~> 1.9.0)
+ uniform_notifier (~> 1.10.0)
cancancan (1.12.0)
- capybara (2.5.0)
+ capybara (2.12.1)
+ addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
+ capybara-screenshot (1.0.14)
+ capybara (>= 1.0, < 3)
+ launchy
carrierwave (0.10.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
mime-types (>= 1.16)
- childprocess (0.5.9)
+ childprocess (0.6.2)
ffi (~> 1.0, >= 1.0.11)
choice (0.2.0)
- chosen-rails (1.4.3)
+ chosen-rails (1.5.2)
coffee-rails (>= 3.2)
- compass-rails (>= 2.0.4)
railties (>= 3.0)
sass-rails (>= 3.2)
- chunky_png (1.3.5)
+ chunky_png (1.3.8)
ci_reporter (2.0.0)
builder (>= 2.1.2)
ci_reporter_rspec (1.0.0)
ci_reporter (~> 2.0)
rspec (>= 2.14, < 4)
- cmess (0.5.0)
+ cmess (0.5.1)
htmlentities (~> 4.3)
- nuggets (~> 1.0)
+ nuggets (~> 1.5)
safe_yaml (~> 1.0)
- coderay (1.1.0)
+ coderay (1.1.1)
codez-tarantula (0.5.5)
hpricot (~> 0.8.4)
htmlentities (~> 4.3.0)
- coffee-rails (4.1.1)
+ coffee-rails (4.2.1)
coffee-script (>= 2.2.0)
- railties (>= 4.0.0, < 5.1.x)
+ railties (>= 4.0.0, < 5.2.x)
coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.10.0)
+ coffee-script-source (1.12.2)
columnize (0.9.0)
compass (1.0.3)
chunky_png (~> 1.2)
@@ -139,10 +143,11 @@ GEM
sass (>= 3.3.0, < 3.5)
compass-import-once (1.0.5)
sass (>= 3.2, < 3.5)
- compass-rails (2.0.4)
+ compass-rails (3.0.2)
compass (~> 1.0.0)
- sass-rails (<= 5.0.1)
- sprockets (< 2.13)
+ sass-rails (< 5.1)
+ sprockets (< 4.0)
+ concurrent-ruby (1.0.5)
config (1.0.0)
activesupport (>= 3.0)
deep_merge (~> 1.0.0)
@@ -157,9 +162,9 @@ GEM
actionpack
activesupport
rails (>= 3.0.0)
- daemons (1.2.3)
- dalli (2.7.5)
- database_cleaner (1.5.1)
+ daemons (1.2.4)
+ dalli (2.7.6)
+ database_cleaner (1.5.3)
debug_inspector (0.0.2)
debugger (1.6.8)
columnize (>= 0.3.1)
@@ -168,19 +173,19 @@ GEM
debugger-linecache (1.2.0)
debugger-ruby_core_source (1.3.8)
deep_merge (1.0.1)
- delayed_job (4.1.1)
- activesupport (>= 3.0, < 5.0)
- delayed_job_active_record (4.1.0)
- activerecord (>= 3.0, < 5)
+ delayed_job (4.1.2)
+ activesupport (>= 3.0, < 5.1)
+ delayed_job_active_record (4.1.1)
+ activerecord (>= 3.0, < 5.1)
delayed_job (>= 3.0, < 5)
- devise (3.5.3)
+ devise (3.5.10)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)
responders
thread_safe (~> 0.1)
warden (~> 1.2.3)
- diff-lcs (1.2.5)
+ diff-lcs (1.3)
docile (1.1.5)
draper (2.1.0)
actionpack (>= 3.0)
@@ -188,13 +193,12 @@ GEM
activesupport (>= 3.0)
request_store (~> 1.0)
erubis (2.7.0)
- eventmachine (1.0.8)
- execjs (2.6.0)
- fabrication (2.14.1)
- faker (1.6.1)
+ eventmachine (1.0.9.1)
+ execjs (2.7.0)
+ fabrication (2.16.1)
+ faker (1.6.3)
i18n (~> 0.5)
- fastercsv (1.5.5)
- ffi (1.9.10)
+ ffi (1.9.18)
globalid (0.3.7)
activesupport (>= 4.1.0)
globalize (5.0.1)
@@ -202,43 +206,45 @@ GEM
activerecord (>= 4.2.0, < 4.3)
haml (4.0.7)
tilt
- headless (2.2.0)
- highline (1.7.8)
- hike (1.2.3)
+ hashery (2.1.2)
+ headless (2.3.1)
hirb (0.7.3)
hpricot (0.8.6)
htmlentities (4.3.4)
- http_accept_language (2.0.5)
- i18n (0.7.0)
+ http_accept_language (2.1.0)
+ i18n (0.8.6)
i18n_data (0.7.0)
+ icalendar (2.4.1)
innertube (1.1.0)
joiner (0.3.4)
activerecord (>= 4.1.0)
- jquery-rails (4.0.5)
- rails-dom-testing (~> 1.0)
+ jquery-rails (4.2.2)
+ rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
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)
- kaminari (0.16.3)
+ json (2.1.0)
+ kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
launchy (2.4.3)
addressable (~> 2.3)
- libv8 (3.16.14.13)
+ libv8 (3.16.14.17)
loofah (2.0.3)
nokogiri (>= 1.5.9)
- magiclabs-userstamp (2.1.0)
+ magiclabs-userstamp (3.0)
+ actionpack (>= 4.0)
+ activerecord (>= 4.0)
mail (2.6.4)
mime-types (>= 1.16, < 4)
- mailcatcher (0.6.2)
- activesupport (>= 4.0.0, < 5)
- eventmachine (= 1.0.8)
+ mailcatcher (0.6.5)
+ eventmachine (= 1.0.9.1)
mail (~> 2.3)
+ rack (~> 1.5)
sinatra (~> 1.2)
skinny (~> 0.2.3)
sqlite3 (~> 1.3)
@@ -246,27 +252,35 @@ GEM
method_source (0.8.2)
middleware (0.1.0)
mime-types (2.6.2)
- mini_magick (4.3.6)
+ mini_magick (4.6.1)
mini_portile2 (2.1.0)
- minitest (5.9.1)
+ minitest (5.10.3)
multi_json (1.12.1)
- mysql2 (0.3.15)
+ mysql2 (0.4.9)
nested_form (0.3.2)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
- nuggets (1.4.0)
- oat (0.4.6)
+ nuggets (1.5.0)
+ oat (0.5.0)
activesupport
orm_adapter (0.5.0)
- paper_trail (4.0.1)
- activerecord (>= 3.0, < 6.0)
- activesupport (>= 3.0, < 6.0)
+ paper_trail (6.0.2)
+ activerecord (>= 4.0, < 5.2)
request_store (~> 1.1)
- paranoia (2.1.4)
+ parallel (1.12.0)
+ paranoia (2.1.1)
activerecord (~> 4.0)
- parser (2.2.3.0)
- ast (>= 1.1, < 3.0)
+ parser (2.4.0.2)
+ ast (~> 2.3)
pdf-core (0.4.0)
+ pdf-inspector (1.2.1)
+ pdf-reader (~> 1.0)
+ pdf-reader (1.4.1)
+ Ascii85 (~> 1.0.0)
+ afm (~> 0.2.1)
+ hashery (~> 2.0)
+ ruby-rc4
+ ttfunk
powerpack (0.1.1)
prawn (1.3.0)
pdf-core (~> 0.4.0)
@@ -275,17 +289,17 @@ GEM
prawn (>= 1.3.0, < 3.0.0)
protective (0.1.0)
activerecord
- pry (0.10.3)
+ pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-debugger (0.2.3)
debugger (~> 1.3)
pry (>= 0.9.10, < 0.11.0)
- pry-doc (0.8.0)
+ pry-doc (0.10.0)
pry (~> 0.9)
- yard (~> 0.8)
- pry-rails (0.3.4)
+ yard (~> 0.9)
+ pry-rails (0.3.5)
pry (>= 0.9.10)
pry-remote (0.1.8)
pry (~> 0.9)
@@ -295,118 +309,114 @@ GEM
pry (>= 0.9.11)
quiet_assets (1.1.0)
railties (>= 3.1, < 5.0)
- rack (1.6.4)
+ rack (1.6.5)
rack-protection (1.5.3)
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.7.1)
- actionmailer (= 4.2.7.1)
- actionpack (= 4.2.7.1)
- actionview (= 4.2.7.1)
- activejob (= 4.2.7.1)
- activemodel (= 4.2.7.1)
- activerecord (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ rails (4.2.8)
+ actionmailer (= 4.2.8)
+ actionpack (= 4.2.8)
+ actionview (= 4.2.8)
+ activejob (= 4.2.8)
+ activemodel (= 4.2.8)
+ activerecord (= 4.2.8)
+ activesupport (= 4.2.8)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.7.1)
+ railties (= 4.2.8)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.7)
+ rails-dom-testing (1.0.8)
activesupport (>= 4.2.0.beta, < 5.0)
- nokogiri (~> 1.6.0)
+ nokogiri (~> 1.6)
rails-deprecated_sanitizer (>= 1.0.1)
- rails-erd (1.4.4)
+ rails-erd (1.5.0)
activerecord (>= 3.2)
activesupport (>= 3.2)
choice (~> 0.2.0)
ruby-graphviz (~> 1.2)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- rails-i18n (4.0.8)
+ rails-i18n (4.0.9)
i18n (~> 0.7)
railties (~> 4.0)
rails_autolink (1.1.6)
rails (> 3.1)
- railties (4.2.7.1)
- actionpack (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ railties (4.2.8)
+ actionpack (= 4.2.8)
+ activesupport (= 4.2.8)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- rainbow (2.0.0)
- rake (10.5.0)
- rb-fsevent (0.9.7)
- rb-inotify (0.9.5)
+ rainbow (2.2.2)
+ rake
+ rake (11.3.0)
+ rb-fsevent (0.9.8)
+ rb-inotify (0.9.8)
ffi (>= 0.5.0)
- rdoc (4.2.1)
- json (~> 1.4)
+ rdoc (4.3.0)
rdoc-tags (1.3)
rdoc (~> 4)
- redcarpet (3.3.4)
+ redcarpet (3.4.0)
ref (2.0.0)
- remotipart (1.2.1)
+ remotipart (1.3.1)
request_profiler (0.0.4)
ruby-prof
- request_store (1.2.1)
- responders (2.1.1)
+ request_store (1.3.2)
+ responders (2.3.0)
railties (>= 4.2.0, < 5.1)
- riddle (1.5.12)
- rspec (3.4.0)
- rspec-core (~> 3.4.0)
- rspec-expectations (~> 3.4.0)
- rspec-mocks (~> 3.4.0)
- rspec-collection_matchers (1.1.2)
+ riddle (2.2.0)
+ rspec (3.5.0)
+ rspec-core (~> 3.5.0)
+ rspec-expectations (~> 3.5.0)
+ rspec-mocks (~> 3.5.0)
+ rspec-collection_matchers (1.1.3)
rspec-expectations (>= 2.99.0.beta1)
- rspec-core (3.4.1)
- rspec-support (~> 3.4.0)
- rspec-expectations (3.4.0)
+ rspec-core (3.5.4)
+ rspec-support (~> 3.5.0)
+ rspec-expectations (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.4.0)
+ rspec-support (~> 3.5.0)
rspec-its (1.2.0)
rspec-core (>= 3.0.0)
rspec-expectations (>= 3.0.0)
- rspec-mocks (3.4.0)
+ rspec-mocks (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.4.0)
- rspec-rails (3.4.0)
- actionpack (>= 3.0, < 4.3)
- activesupport (>= 3.0, < 4.3)
- railties (>= 3.0, < 4.3)
- rspec-core (~> 3.4.0)
- rspec-expectations (~> 3.4.0)
- rspec-mocks (~> 3.4.0)
- rspec-support (~> 3.4.0)
- rspec-support (3.4.1)
- rubocop (0.35.1)
- astrolabe (~> 1.3)
- parser (>= 2.2.3.0, < 3.0)
+ rspec-support (~> 3.5.0)
+ rspec-rails (3.5.2)
+ actionpack (>= 3.0)
+ activesupport (>= 3.0)
+ railties (>= 3.0)
+ rspec-core (~> 3.5.0)
+ rspec-expectations (~> 3.5.0)
+ rspec-mocks (~> 3.5.0)
+ rspec-support (~> 3.5.0)
+ rspec-support (3.5.0)
+ rubocop (0.51.0)
+ parallel (~> 1.10)
+ parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
- rainbow (>= 1.99.1, < 3.0)
+ rainbow (>= 2.2.2, < 3.0)
ruby-progressbar (~> 1.7)
- tins (<= 1.6.0)
- rubocop-checkstyle_formatter (0.2.0)
- rubocop (>= 0.20.1)
+ unicode-display_width (~> 1.0, >= 1.0.1)
+ rubocop-checkstyle_formatter (0.3.0)
+ rubocop (>= 0.30.1)
ruby-graphviz (1.2.2)
- ruby-prof (0.15.9)
- ruby-progressbar (1.7.5)
- ruby2ruby (2.2.0)
- ruby_parser (~> 3.1)
- sexp_processor (~> 4.0)
- ruby_parser (3.7.2)
- sexp_processor (~> 4.1)
+ ruby-prof (0.16.2)
+ ruby-progressbar (1.9.0)
+ ruby-rc4 (0.1.5)
rubyzip (1.0.0)
safe_yaml (1.0.4)
- sass (3.4.20)
- sass-rails (5.0.1)
- railties (>= 4.0.0, < 5.0)
+ sass (3.4.23)
+ sass-rails (5.0.6)
+ railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
- tilt (~> 1.1)
- seed-fu (2.3.5)
- activerecord (>= 3.1, < 4.3)
- activesupport (>= 3.1, < 4.3)
+ tilt (>= 1.1, < 3)
+ seed-fu (2.3.6)
+ activerecord (>= 3.1)
+ activesupport (>= 3.1)
seed-fu-ndo (0.0.2)
seed-fu (>= 2.2.0)
selenium-webdriver (2.51.0)
@@ -414,88 +424,81 @@ GEM
multi_json (~> 1.0)
rubyzip (~> 1.0)
websocket (~> 1.0)
- sexp_processor (4.6.0)
- simplecov (0.11.1)
+ simplecov (0.15.1)
docile (~> 1.1.0)
- json (~> 1.8)
+ json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
- simplecov-html (0.10.0)
+ simplecov-html (0.10.2)
simplecov-rcov (0.2.3)
simplecov (>= 0.4.1)
- simpleidn (0.0.5)
- sinatra (1.4.6)
- rack (~> 1.4)
+ simpleidn (0.0.7)
+ sinatra (1.4.8)
+ rack (~> 1.5)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
- skinny (0.2.3)
+ skinny (0.2.4)
eventmachine (~> 1.0.0)
- thin (~> 1.5.0)
- slim (3.0.6)
- temple (~> 0.7.3)
- tilt (>= 1.3.3, < 2.1)
+ thin (>= 1.5, < 1.7)
slop (3.6.0)
sort_alphabetical (1.0.2)
unicode_utils (>= 1.2.2)
- spring (1.6.1)
+ spring (2.0.1)
+ activesupport (>= 4.2)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
- sprockets (2.12.4)
- hike (~> 1.2)
- multi_json (~> 1.0)
- rack (~> 1.0)
- tilt (~> 1.1, != 1.3.0)
- sprockets-rails (2.3.3)
- actionpack (>= 3.0)
- activesupport (>= 3.0)
- sprockets (>= 2.8, < 4.0)
- sqlite3 (1.3.11)
- temple (0.7.6)
- terminal-table (1.5.2)
- therubyracer (0.12.2)
- libv8 (~> 3.16.14.0)
+ sprockets (3.7.1)
+ concurrent-ruby (~> 1.0)
+ rack (> 1, < 3)
+ sprockets-rails (3.2.0)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0)
+ sqlite3 (1.3.13)
+ therubyracer (0.12.3)
+ libv8 (~> 3.16.14.15)
ref
thin (1.5.1)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
- thinking-sphinx (3.1.4)
+ thinking-sphinx (3.4.1)
activerecord (>= 3.1.0)
builder (>= 2.1.2)
innertube (>= 1.0.2)
joiner (>= 0.2.0)
middleware (>= 0.1.0)
- riddle (>= 1.5.11)
- thor (0.19.1)
- thread_safe (0.3.5)
- tilt (1.4.1)
- timecop (0.8.0)
- timeliness (0.3.7)
- tins (1.6.0)
+ riddle (>= 2.0.0)
+ thor (0.19.4)
+ thread_safe (0.3.6)
+ tilt (2.0.6)
+ timeliness (0.3.8)
ttfunk (1.4.0)
- turbolinks (2.5.3)
- coffee-rails
- tzinfo (1.2.2)
+ turbolinks (5.0.1)
+ turbolinks-source (~> 5)
+ turbolinks-source (5.0.0)
+ tzinfo (1.2.3)
thread_safe (~> 0.1)
- uglifier (2.7.2)
- execjs (>= 0.3.0)
- json (>= 1.8.0)
+ uglifier (3.1.4)
+ execjs (>= 0.3.0, < 3)
+ unicode-display_width (1.3.0)
unicode_utils (1.4.0)
- uniform_notifier (1.9.0)
+ uniform_notifier (1.10.0)
validates_by_schema (0.3.0)
activerecord (>= 3.1.0)
validates_timeliness (3.0.15)
timeliness (~> 0.3.7)
+ vcard (0.2.15)
wagons (0.4.8)
bundler (>= 1.1)
rails (>= 3.2)
seed-fu-ndo (>= 0.0.2)
- warden (1.2.4)
+ warden (1.2.7)
rack (>= 1.0)
- websocket (1.2.2)
+ websocket (1.2.4)
wirble (0.1.3)
xpath (2.0.0)
nokogiri (~> 1.3)
- yard (0.8.7.6)
+ yard (0.9.8)
PLATFORMS
ruby
@@ -503,9 +506,10 @@ PLATFORMS
DEPENDENCIES
activerecord-session_store
acts-as-taggable-on (~> 3.5.0)
+ addressable (< 2.5)
airbrake (< 5.0)
annotate
- awesome_nested_set
+ awesome_nested_set (< 3.1.0)
awesome_print
axlsx (= 2.0.1)
bcrypt-ruby
@@ -516,7 +520,8 @@ DEPENDENCIES
bullet
cancancan (< 1.13.0)
capybara
- carrierwave
+ capybara-screenshot
+ carrierwave (< 0.11.1)
chosen-rails
ci_reporter_rspec
cmess
@@ -524,36 +529,41 @@ DEPENDENCIES
coffee-rails
compass
compass-rails
- config
+ config (< 1.1.0)
country_select
customized_piwik_analytics (~> 1.0.0)
daemons
dalli
database_cleaner
delayed_job_active_record
- devise
+ devise (< 4.0.0)
draper
fabrication
- faker
+ faker (< 1.6.4)
globalize
haml
headless
hirb
+ hitobito_pbs!
+ hitobito_youth!
http_accept_language
+ icalendar
jquery-rails
jquery-turbolinks
jquery-ui-rails
- kaminari
+ kaminari (< 1.0.0)
launchy
magiclabs-userstamp
mailcatcher
mime-types (~> 2.6.2)
mini_magick
- mysql2 (= 0.3.15)
+ mysql2 (= 0.4.9)
nested_form
+ nokogiri (< 1.7.0)
oat
paper_trail
- paranoia
+ paranoia (< 2.1.2)
+ pdf-inspector
prawn (< 2.0)
prawn-table
protective
@@ -564,7 +574,7 @@ DEPENDENCIES
pry-stack_explorer
quiet_assets
rack
- rails (= 4.2.7.1)
+ rails (= 4.2.8)
rails-erd
rails-i18n
rails_autolink
@@ -578,19 +588,24 @@ DEPENDENCIES
rubocop
rubocop-checkstyle_formatter
ruby-prof
+ rubyzip
sass-rails
seed-fu
- selenium-webdriver
+ selenium-webdriver (= 2.51.0)
simplecov-rcov
simpleidn
+ sort_alphabetical (< 1.1.0)
spring-commands-rspec
sqlite3
therubyracer
thinking-sphinx
- timecop
turbolinks
uglifier
validates_by_schema
validates_timeliness (< 4.0)
+ vcard
wagons
wirble
+
+BUNDLED WITH
+ 1.16.0
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000000..2e470afa35
--- /dev/null
+++ b/Procfile
@@ -0,0 +1,4 @@
+web: bundle exec rails s -b 0.0.0.0 -p $PORT
+worker: bundle exec rake jobs:work
+mail: mailcatcher -f
+
diff --git a/README.md b/README.md
index 3ef875999b..1cb33aa694 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,10 @@ hitobito is an open source web application to manage complex group hierarchies w
## Development
-Hitobito is a Ruby on Rails application that runs on Ruby >= 1.9.3 and Rails 4.
+Hitobito is a Ruby on Rails application that runs on Ruby >= 2.1 and Rails 4.
+It might run with minor tweaks on older Rubies, but is not tested against those
+versions.
+
To get going, after you got a copy of hitobito and at least one wagon with an organization
structure setup as described below, issue the following commands in the main directory:
diff --git a/VERSION b/VERSION
index 63738cc28d..b48f322609 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.14
+1.17
diff --git a/app/abilities/ability.rb b/app/abilities/ability.rb
index 0ed71288b6..d5ede16648 100644
--- a/app/abilities/ability.rb
+++ b/app/abilities/ability.rb
@@ -15,13 +15,15 @@ class Ability
store.register EventAbility,
Event::ApplicationAbility,
Event::ParticipationAbility,
+ Event::ParticipationContactDataAbility,
Event::RoleAbility,
GroupAbility,
+ InvoiceAbility,
MailingListAbility,
+ NoteAbility,
PeopleFilterAbility,
PersonAbility,
Person::AddRequestAbility,
- Person::NoteAbility,
QualificationAbility,
RoleAbility,
SubscriptionAbility,
diff --git a/app/abilities/event/participation_ability.rb b/app/abilities/event/participation_ability.rb
index dc6a1d0098..a8a08904f9 100644
--- a/app/abilities/event/participation_ability.rb
+++ b/app/abilities/event/participation_ability.rb
@@ -14,23 +14,24 @@ class Event::ParticipationAbility < AbilityDsl::Base
permission(:any).may(:show).her_own_or_for_participations_read_events
permission(:any).may(:show_details, :print).her_own_or_for_participations_full_events
permission(:any).may(:create).her_own_if_application_possible
- permission(:any).may(:update).for_participations_full_events
+ permission(:any).may(:show_full, :update).for_participations_full_events
+ permission(:any).may(:destroy).her_own_if_application_cancelable
permission(:group_full).
- may(:show, :show_details, :print, :create, :update, :destroy).
+ may(:show, :show_details, :show_full, :print, :create, :update, :destroy).
in_same_group
permission(:group_and_below_full).
- may(:show, :show_details, :print, :create, :update, :destroy).
+ may(:show, :show_details, :show_full, :print, :create, :update, :destroy).
in_same_group_or_below
permission(:layer_full).
- may(:show, :show_details, :print, :update).
+ may(:show, :show_details, :show_full, :print, :update).
in_same_layer_or_different_prio
permission(:layer_full).may(:create, :destroy).in_same_layer
permission(:layer_and_below_full).
- may(:show, :show_details, :print, :update).
+ may(:show, :show_details, :show_full, :print, :update).
in_same_layer_or_below_or_different_prio
permission(:layer_and_below_full).may(:create, :destroy).in_same_layer
@@ -51,6 +52,12 @@ def her_own_if_application_possible
her_own && event.application_possible?
end
+ def her_own_if_application_cancelable
+ her_own &&
+ event.applications_cancelable? &&
+ (!event.application_closing_at? || event.application_closing_at >= Time.zone.today)
+ end
+
private
def participation
diff --git a/app/abilities/event/participation_contact_data_ability.rb b/app/abilities/event/participation_contact_data_ability.rb
new file mode 100644
index 0000000000..33ab02fb4c
--- /dev/null
+++ b/app/abilities/event/participation_contact_data_ability.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Pfadibewegung Schweiz. This file is part of
+# hitobito_pbs and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito_pbs.
+
+class Event::ParticipationContactDataAbility < AbilityDsl::Base
+
+ on(Event::ParticipationContactData) do
+ permission(:any).may(:show, :update).her_own
+ end
+
+ def her_own
+ subject.person.id == user.id
+ end
+
+end
diff --git a/app/abilities/event_ability.rb b/app/abilities/event_ability.rb
index b594116dde..836711f27b 100644
--- a/app/abilities/event_ability.rb
+++ b/app/abilities/event_ability.rb
@@ -15,7 +15,7 @@ class EventAbility < AbilityDsl::Base
permission(:any).may(:show).all
permission(:any).may(:index_participations).for_participations_read_events
permission(:any).may(:update).for_leaded_events
- permission(:any).may(:qualify).for_qualify_event
+ permission(:any).may(:qualify, :qualifications_read).for_qualify_event
permission(:group_full).may(:index_participations, :create, :update, :destroy).in_same_group
@@ -24,15 +24,17 @@ class EventAbility < AbilityDsl::Base
in_same_group_or_below
permission(:layer_full).
- may(:index_participations, :update, :create, :destroy, :application_market, :qualify).
+ may(:index_participations, :update, :create, :destroy,
+ :application_market, :qualify, :qualifications_read).
in_same_layer
permission(:layer_and_below_full).
may(:index_participations, :update).in_same_layer_or_below
permission(:layer_and_below_full).
- may(:create, :destroy, :application_market, :qualify).in_same_layer
+ may(:create, :destroy, :application_market, :qualify, :qualifications_read).in_same_layer
- general(:create, :destroy, :application_market, :qualify).at_least_one_group_not_deleted
+ general(:create, :destroy, :application_market, :qualify, :qualifications_read).
+ at_least_one_group_not_deleted
end
on(Event::Course) do
diff --git a/app/abilities/group_ability.rb b/app/abilities/group_ability.rb
index de2ff73ebd..611b04cd57 100644
--- a/app/abilities/group_ability.rb
+++ b/app/abilities/group_ability.rb
@@ -41,8 +41,9 @@ class GroupAbility < AbilityDsl::Base
permission(:layer_full).may(:create).with_parent_in_same_layer
permission(:layer_full).may(:destroy).in_same_layer_except_permission_giving
permission(:layer_full).
- may(:update, :reactivate, :index_person_add_requests, :index_person_notes,
- :manage_person_tags, :activate_person_add_requests, :deactivate_person_add_requests).
+ may(:update, :reactivate, :index_person_add_requests, :index_notes,
+ :manage_person_tags, :activate_person_add_requests, :deactivate_person_add_requests,
+ :index_deleted_people).
in_same_layer
permission(:layer_and_below_read).
@@ -54,14 +55,16 @@ class GroupAbility < AbilityDsl::Base
permission(:layer_and_below_full).may(:create).with_parent_in_same_layer_or_below
permission(:layer_and_below_full).may(:destroy).in_same_layer_or_below_except_permission_giving
permission(:layer_and_below_full).
- may(:update, :reactivate, :index_person_add_requests, :index_person_notes,
- :manage_person_tags).
+ may(:update, :reactivate, :index_person_add_requests, :index_notes,
+ :manage_person_tags, :index_deleted_people).
in_same_layer_or_below
permission(:layer_and_below_full).may(:modify_superior).in_below_layers
permission(:layer_and_below_full).
may(:activate_person_add_requests, :deactivate_person_add_requests).
in_same_layer
+ permission(:finance).may(:index_invoices).in_layer_group
+
general(:update).group_not_deleted
general(:index_person_add_requests,
:activate_person_add_requests,
@@ -69,6 +72,10 @@ class GroupAbility < AbilityDsl::Base
if_layer_group
end
+ def in_layer_group
+ user.finance_groups.include?(subject)
+ end
+
def with_parent_in_same_layer
parent = group.parent
!group.layer? && parent && !parent.deleted? && permission_in_layer?(parent.layer_group_id)
diff --git a/app/abilities/invoice_ability.rb b/app/abilities/invoice_ability.rb
new file mode 100644
index 0000000000..4429ccb218
--- /dev/null
+++ b/app/abilities/invoice_ability.rb
@@ -0,0 +1,38 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class InvoiceAbility < AbilityDsl::Base
+
+ on(Invoice) do
+ permission(:finance).may(:create, :show, :edit, :update, :destroy).in_layer
+ end
+
+ on(InvoiceArticle) do
+ permission(:finance).may(:new, :create, :show, :edit, :update, :destroy).in_layer
+ end
+
+ on(InvoiceConfig) do
+ permission(:finance).may(:show, :edit, :update).in_layer
+ end
+
+ on(Payment) do
+ permission(:finance).may(:create).in_layer
+ end
+
+ on(PaymentReminder) do
+ permission(:finance).may(:create).in_layer
+ end
+
+ def any_finance_group
+ user.finance_groups.present?
+ end
+
+ def in_layer
+ user.groups_with_permission(:finance).collect(&:layer_group).include?(subject.group)
+ end
+
+end
diff --git a/app/abilities/note_ability.rb b/app/abilities/note_ability.rb
new file mode 100644
index 0000000000..0e349c426f
--- /dev/null
+++ b/app/abilities/note_ability.rb
@@ -0,0 +1,42 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Dachverband Schweizer Jugendparlamente. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class NoteAbility < AbilityDsl::Base
+
+ on(Note) do
+ permission(:layer_full).
+ may(:create, :show, :destroy).
+ in_same_layer
+
+ permission(:layer_and_below_full).
+ may(:create, :show, :destroy).
+ in_same_layer_or_below
+ end
+
+ def in_same_layer
+ case subj
+ when Group then permission_in_layer?(subj.layer_group_id)
+ when Person then permission_in_layers?(subj.layer_group_ids)
+ else raise(ArgumentError, "Unknown note subject #{subj.class}")
+ end
+ end
+
+ def in_same_layer_or_below
+ case subj
+ when Group then permission_in_layers?(subj.layer_hierarchy.collect(&:id))
+ when Person then permission_in_layers?(subj.groups_hierarchy_ids)
+ else raise(ArgumentError, "Unknown note subject #{subj.class}")
+ end
+ end
+
+ private
+
+ def subj
+ subject.subject
+ end
+
+end
diff --git a/app/abilities/people_filter_ability.rb b/app/abilities/people_filter_ability.rb
index 95cae60fef..31ef6ac20d 100644
--- a/app/abilities/people_filter_ability.rb
+++ b/app/abilities/people_filter_ability.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -14,9 +14,9 @@ class PeopleFilterAbility < AbilityDsl::Base
permission(:group_read).may(:new).in_same_group
permission(:group_and_below_read).may(:new).in_same_group_or_below
permission(:layer_read).may(:new).in_same_layer
- permission(:layer_full).may(:create, :destroy).in_same_layer
+ permission(:layer_full).may(:create, :destroy, :edit, :update).in_same_layer
permission(:layer_and_below_read).may(:new).in_same_layer_or_below
- permission(:layer_and_below_full).may(:create, :destroy).in_same_layer
+ permission(:layer_and_below_full).may(:create, :destroy, :edit, :update).in_same_layer
end
end
diff --git a/app/abilities/person/add_request_ability.rb b/app/abilities/person/add_request_ability.rb
index 41f7274a2b..851c9bd496 100644
--- a/app/abilities/person/add_request_ability.rb
+++ b/app/abilities/person/add_request_ability.rb
@@ -13,12 +13,18 @@ class Person::AddRequestAbility < AbilityDsl::Base
permission(:any).may(:approve, :reject).herself
permission(:any).may(:reject).her_own
- permission(:group_full).may(:approve, :reject).non_restricted_in_same_group
- permission(:group_and_below_full).may(:approve, :reject).non_restricted_in_same_group_or_below
- permission(:layer_full).may(:approve, :reject).non_restricted_in_same_layer
+ permission(:group_full).
+ may(:approve, :reject).
+ non_restricted_or_deleted_in_same_group
+ permission(:group_and_below_full).
+ may(:approve, :reject).
+ non_restricted_or_deleted_in_same_group_or_below
+ permission(:layer_full).
+ may(:approve, :reject).
+ non_restricted_or_deleted_in_same_layer
permission(:layer_and_below_full).
may(:approve, :reject).
- non_restricted_in_same_layer_or_visible_below
+ non_restricted_or_deleted_in_same_layer_or_visible_below
# This does not tell if people actually may be added, just if they may be added to some body,
# that no request is required. Basically, this is possible if the user may already show the
@@ -29,16 +35,80 @@ class Person::AddRequestAbility < AbilityDsl::Base
permission(:group_and_below_read).may(:add_without_request).in_same_group_or_below
permission(:layer_read).may(:add_without_request).in_same_layer
permission(:layer_and_below_read).may(:add_without_request).in_same_layer_or_below
+ permission(:group_full).
+ may(:add_without_request).
+ active_or_deleted_in_same_group
+ permission(:group_and_below_full).
+ may(:add_without_request).
+ active_or_deleted_in_same_group_or_below
+ permission(:layer_full).
+ may(:add_without_request).
+ active_or_deleted_in_same_layer
+ permission(:layer_and_below_full).
+ may(:add_without_request).
+ active_or_deleted_in_same_layer_or_below
end
def her_own
user.id == subject.requester_id
end
+ def non_restricted_or_deleted_in_same_group
+ non_restricted_in_same_group || deleted_in_same_group
+ end
+
+ def non_restricted_or_deleted_in_same_group_or_below
+ non_restricted_in_same_group || deleted_in_same_group_or_below
+ end
+
+ def non_restricted_or_deleted_in_same_layer
+ non_restricted_in_same_layer || deleted_in_same_layer
+ end
+
+ def non_restricted_or_deleted_in_same_layer_or_visible_below
+ non_restricted_in_same_layer_or_visible_below || deleted_in_same_layer_or_below
+ end
+
+ def active_or_deleted_in_same_group
+ in_same_group || deleted_in_same_group
+ end
+
+ def active_or_deleted_in_same_group_or_below
+ in_same_group_or_below || deleted_in_same_group_or_below
+ end
+
+ def active_or_deleted_in_same_layer
+ in_same_layer || deleted_in_same_layer
+ end
+
+ def active_or_deleted_in_same_layer_or_below
+ in_same_layer_or_below || deleted_in_same_layer_or_below
+ end
+
private
def person
subject.person
end
+ def deleted_in_same_group
+ role = person.last_non_restricted_role
+ role && permission_in_group?(role.group_id)
+ end
+
+ def deleted_in_same_group_or_below
+ role = person.last_non_restricted_role
+ role && permission_in_group?(role.group.local_hierarchy.collect(&:id))
+ end
+
+ def deleted_in_same_layer
+ role = person.last_non_restricted_role
+ role && permission_in_layer?(role.group.layer_group_id)
+ end
+
+ def deleted_in_same_layer_or_below
+ role = person.last_non_restricted_role
+ role && permission_in_layers?(role.group.hierarchy.collect(&:id))
+ end
+
end
diff --git a/app/abilities/person/note_ability.rb b/app/abilities/person/note_ability.rb
deleted file mode 100644
index 301122213d..0000000000
--- a/app/abilities/person/note_ability.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
-
-class Person::NoteAbility < AbilityDsl::Base
-
- include AbilityDsl::Constraints::Person
-
- on(Person::Note) do
- permission(:layer_full).
- may(:create, :show).
- in_same_layer
-
- permission(:layer_and_below_full).
- may(:create, :show).
- in_same_layer_or_below
- end
-
- private
-
- def person
- subject.person
- end
-
-end
diff --git a/app/abilities/person_ability.rb b/app/abilities/person_ability.rb
index 148d6e02a8..ca6c020847 100644
--- a/app/abilities/person_ability.rb
+++ b/app/abilities/person_ability.rb
@@ -11,10 +11,14 @@ class PersonAbility < AbilityDsl::Base
on(Person) do
class_side(:index, :query).everybody
+ class_side(:index_people_without_role).if_admin
- permission(:any).may(:show, :show_full, :history, :update,
- :update_email, :primary_group, :log).
- herself
+ permission(:admin).may(:destroy).not_self
+
+ permission(:any).
+ may(:show, :show_details, :show_full, :history, :update, :update_email, :primary_group, :log,
+ :update_settings).
+ herself
permission(:contact_data).may(:show).other_with_contact_data
@@ -63,13 +67,26 @@ class PersonAbility < AbilityDsl::Base
if_permissions_in_all_capable_groups_or_layer_or_above
permission(:layer_and_below_full).may(:create).all # restrictions are on Roles
+ permission(:finance).may(:index_invoices).in_layer_group
+ permission(:any).may(:index_invoices).herself
+
+ permission(:admin).may(:show).people_without_roles
+
general(:send_password_instructions).not_self
end
+ def in_layer_group
+ contains_any?(user.finance_groups.collect(&:id), person.layer_group_ids)
+ end
+
def not_self
subject.id != user.id
end
+ def people_without_roles
+ subject.roles.empty?
+ end
+
def if_permissions_in_all_capable_groups
!subject.root? &&
# true if capable roles is empty.
diff --git a/app/abilities/person_fetchables.rb b/app/abilities/person_fetchables.rb
index 479f07577e..3a65f9ace6 100644
--- a/app/abilities/person_fetchables.rb
+++ b/app/abilities/person_fetchables.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
-# hitobito_pbs and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_pbs.
+# https://github.com/hitobito/hitobito.
# Commnon Base Class for fetching people.
class PersonFetchables
@@ -101,9 +101,9 @@ def layer_groups_with_permissions(*permissions)
end
def groups_with_permissions(*permissions)
- permissions.collect { |p| user.groups_with_permission(p) }.
- flatten.
- uniq
+ permissions.collect { |p| user.groups_with_permission(p) }
+ .flatten
+ .uniq
end
end
diff --git a/app/abilities/person_readables.rb b/app/abilities/person_readables.rb
index 2294d9f1f4..4983b4930d 100644
--- a/app/abilities/person_readables.rb
+++ b/app/abilities/person_readables.rb
@@ -51,11 +51,11 @@ def accessible_people
if user.root?
Person.only_public_data
else
- Person.only_public_data.
- joins(roles: :group).
- where(roles: { deleted_at: nil }, groups: { deleted_at: nil }).
- where(accessible_conditions.to_a).
- uniq
+ Person.only_public_data
+ .joins(roles: :group)
+ .where(roles: { deleted_at: nil }, groups: { deleted_at: nil })
+ .where(accessible_conditions.to_a)
+ .uniq
end
end
diff --git a/app/abilities/various_ability.rb b/app/abilities/various_ability.rb
index 770a8e51a5..8d9cc7ce0c 100644
--- a/app/abilities/various_ability.rb
+++ b/app/abilities/various_ability.rb
@@ -13,8 +13,10 @@ class VariousAbility < AbilityDsl::Base
end
on(LabelFormat) do
- class_side(:index).if_admin
+ class_side(:index).everybody
+ class_side(:manage_global).if_admin
permission(:admin).may(:manage).all
+ permission(:any).may(:create, :update, :destroy, :read).own
end
if Group.course_types.present?
@@ -29,4 +31,7 @@ class VariousAbility < AbilityDsl::Base
end
end
+ def own
+ subject.person_id == user.id
+ end
end
diff --git a/app/assets/images/group.svg b/app/assets/images/group.svg
new file mode 100644
index 0000000000..c97ddcb100
--- /dev/null
+++ b/app/assets/images/group.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 896ea7b3c7..59e8fb64ca 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -1,27 +1,29 @@
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-# This is a manifest file that'll be compiled into application.js, which will include all the files
-# listed below.
+# This is a manifest file that'll be compiled into application.js, which will
+# include all the files listed below.
#
-# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-# or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# the compiled file.
+# Any JavaScript/Coffee file within this directory, lib/assets/javascripts,
+# vendor/assets/javascripts, or vendor/assets/javascripts of plugins, if any,
+# can be referenced here using a relative path. It's not advisable to add code
+# directly here, but if you do, it'll appear at the bottom of the the compiled
+# file.
#
-# WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
-# GO AFTER THE REQUIRES BELOW.
+# WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY
+# BLANK LINE SHOULD GO AFTER THE REQUIRES BELOW.
#
#= require jquery
-#= require jquery.turbolinks
#= require jquery_ujs
-#= require jquery-ui/datepicker
+#= require jquery-ui/widgets/datepicker
#= require jquery-ui-datepicker-i18n
-#= require jquery-ui/effect-highlight
+#= require jquery-ui/effects/effect-highlight
+#= require bootstrap-transition
#= require bootstrap-alert
#= require bootstrap-button
+#= require bootstrap-collapse
#= require bootstrap-dropdown
#= require bootstrap-tooltip
#= require bootstrap-popover
@@ -32,83 +34,9 @@
#= require jquery.remotipart
#= require modernizr.custom.min
#= require moment.min
-#= require_self
#= require_tree ./modules
#= require wagon
#= require turbolinks
-#= require progress-bar
#
-# scope for global functions
-app = window.App ||= {}
-
-# add trim function for older browsers
-if !String.prototype.trim
- String.prototype.trim = () -> this.replace(/^\s+|\s+$/g, '')
-
-
-replaceContent = (e, data, status, xhr) ->
- replace = $(this).data('replace')
- el = if replace is true then $(this).closest('form') else $("##{replace}")
- console.warn "found no element to replace" if el.size() is 0
- el.html(data)
-
-setDataType = (xhr) ->
- $(this).data('type', 'html')
-
-toggleGroupContact = ->
- open = !$('#group_contact_id').val()
- fields = $('fieldset.info')
- if !open && fields.is(':visible')
- fields.slideUp()
- else if open && !fields.is(':visible')
- fields.slideDown()
-
-toggleFilterRoles = (event) ->
- target = $(event.target)
-
- boxes = target.nextUntil('.filter-toggle').find(':checkbox')
- checked = boxes.filter(':checked').length == boxes.length
-
- boxes.each((el) -> $(this).prop('checked', !checked))
- target.data('checked', !checked)
-
-app.activateChosen = (i, element) ->
- element = $(element)
- blank = element.find('option[value]').first().val() == ''
- text = element.data('chosen-no-results') || ' '
- element.chosen({ no_results_text: text, search_contains: true, allow_single_deselect: blank, width: '100%' })
-
-
-
-########################################################################
-# because of turbolinks.jquery, do bind ALL document events on top level
-
-# wire up elements with ajax replace
-$(document).on('ajax:success','[data-replace]', replaceContent)
-$(document).on('ajax:before','[data-replace]', setDataType)
-
-# show alert if ajax requests fail
-$(document).on('ajax:error', (event, xhr, status, error) ->
- alert('Sorry, something went wrong\n(' + error + ')'))
-
-# wire up disabled links
-$(document).on('click', 'a.disabled', (event) -> $.rails.stopEverything(event); event.preventDefault();)
-
-# make clicking on typeahead item always select it (https://github.com/twitter/bootstrap/issues/4018)
-$(document).on('mousedown', 'ul.typeahead', (e) -> e.preventDefault())
-
-# control visibilty of group contact fields in relation to contact
-$(document).on('change', '#group_contact_id', toggleGroupContact)
-
-$(document).on('click', '.filter-toggle', toggleFilterRoles)
-
-# only bind events for non-document elements in $ ->
-$ ->
-
- # wire up tooltips
- $(document).tooltip({ selector: '[rel^=tooltip]', placement: 'right' })
-
- # enable chosen js
- $('.chosen-select').each(app.activateChosen)
diff --git a/app/assets/javascripts/modules/ajax_error_notification.js.coffee b/app/assets/javascripts/modules/ajax_error_notification.js.coffee
new file mode 100644
index 0000000000..5bcd1ea9ac
--- /dev/null
+++ b/app/assets/javascripts/modules/ajax_error_notification.js.coffee
@@ -0,0 +1,9 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+# show alert if ajax requests fail
+$(document).on('ajax:error', (event, xhr, status, error) ->
+ alert('Sorry, something went wrong\n(' + error + ')'))
+
diff --git a/app/assets/javascripts/modules/ajax_replace.js.coffee b/app/assets/javascripts/modules/ajax_replace.js.coffee
new file mode 100644
index 0000000000..f77648b740
--- /dev/null
+++ b/app/assets/javascripts/modules/ajax_replace.js.coffee
@@ -0,0 +1,17 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+replaceContent = (e, data, status, xhr) ->
+ replace = $(this).data('replace')
+ el = if replace is true then $(this).closest('form') else $("##{replace}")
+ console.warn "found no element to replace" if el.size() is 0
+ el.html(data)
+
+setDataType = (xhr) ->
+ $(this).data('type', 'html')
+
+# wire up elements with ajax replace
+$(document).on('ajax:success','[data-replace]', replaceContent)
+$(document).on('ajax:before','[data-replace]', setDataType)
diff --git a/app/assets/javascripts/modules/ajax_upload.js.coffee b/app/assets/javascripts/modules/ajax_upload.js.coffee
index 8bd0c8b694..e46e5d63bd 100644
--- a/app/assets/javascripts/modules/ajax_upload.js.coffee
+++ b/app/assets/javascripts/modules/ajax_upload.js.coffee
@@ -1,4 +1,4 @@
-# Copyright (c) 2015 Pro Natura Schweiz. This file is part of
+# Copyright (c) 2015-2017 Pro Natura Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -13,6 +13,7 @@ class app.AjaxUpload
form = $(input).closest('form')
new app.Spinner().show(form)
form.submit()
+ $(input).closest('form').reset()
bind: ->
self = this
diff --git a/app/assets/javascripts/modules/auto_submit.js.coffee b/app/assets/javascripts/modules/auto_submit.js.coffee
new file mode 100644
index 0000000000..8af8d27a95
--- /dev/null
+++ b/app/assets/javascripts/modules/auto_submit.js.coffee
@@ -0,0 +1,9 @@
+# wire up auto submit fields
+
+$(document).on('change', '[data-submit]', (e) ->
+ form = $(this).closest('form')
+ if form.attr('method') == 'get'
+ Turbolinks.visit("#{form.attr('action')}?#{form.serialize()}", action: 'replace')
+ else
+ form.submit()
+)
diff --git a/app/assets/javascripts/modules/checkable.js.coffee b/app/assets/javascripts/modules/checkable.js.coffee
new file mode 100644
index 0000000000..6868777b3a
--- /dev/null
+++ b/app/assets/javascripts/modules/checkable.js.coffee
@@ -0,0 +1,23 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+$(document).on('click', 'table[data-checkable] thead :checkbox', (e) ->
+ checked = e.target.checked
+ table = $(e.target).closest('table[data-checkable]')
+ table.find('tbody :checkbox').prop('checked', checked)
+)
+
+$(document).on('click', 'a[data-checkable]:not(data-method)', (e) ->
+ e.target.href = buildLinkWithIds(e.target.href)
+)
+
+buildLinkWithIds = (href) ->
+ ids = ($(item).val() for item in $('table[data-checkable] tbody :checked'))
+ separator = if href.indexOf('?') != -1 then '&' else '?'
+ href + separator + "ids=#{ids}"
+
+$.rails.href = (element) ->
+ href = element[0].href
+ if $(element).is('a[data-checkable]') then buildLinkWithIds(href) else href
diff --git a/app/assets/javascripts/modules/chosen.js.coffee b/app/assets/javascripts/modules/chosen.js.coffee
new file mode 100644
index 0000000000..437ecb48d4
--- /dev/null
+++ b/app/assets/javascripts/modules/chosen.js.coffee
@@ -0,0 +1,23 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+# scope for global functions
+app = window.App ||= {}
+
+app.activateChosen = (i, element) ->
+ element = $(element)
+ blank = element.find('option[value]').first().val() == ''
+ text = element.data('chosen-no-results') || ' '
+ element.chosen({
+ no_results_text: text,
+ search_contains: true,
+ allow_single_deselect: blank,
+ width: '100%' })
+
+# only bind events for non-document elements in turbolinks:load
+$(document).on('turbolinks:load', ->
+ # enable chosen js
+ $('.chosen-select').each(app.activateChosen)
+)
diff --git a/app/assets/javascripts/modules/clear_input.js.coffee b/app/assets/javascripts/modules/clear_input.js.coffee
new file mode 100644
index 0000000000..967affba05
--- /dev/null
+++ b/app/assets/javascripts/modules/clear_input.js.coffee
@@ -0,0 +1,26 @@
+class ClearInput
+
+ clear: (cross) ->
+ @_input(cross).val('').trigger('change')
+
+ toggleHide: (input) ->
+ group = input.parents('.control-group')
+ if input.val() == ''
+ group.addClass('has-empty-value')
+ else
+ console.log input.val()
+ group.removeClass('has-empty-value')
+
+ _input: (cross) ->
+ cross.parents('.control-group').find('input')
+
+ bind: ->
+ self = this
+ $(document).on('click', '[data-clear]', () -> self.clear($(this)))
+ $(document).on('change', '.has-clear input', () -> self.toggleHide($(this)))
+
+
+new ClearInput().bind()
+
+$(document).on 'turbolinks:load', ->
+ $('.has-clear input').each((i, e) -> new ClearInput().toggleHide($(e)))
diff --git a/app/assets/javascripts/modules/course_description_handler.js.coffee b/app/assets/javascripts/modules/course_description_handler.js.coffee
deleted file mode 100644
index 7f70e56283..0000000000
--- a/app/assets/javascripts/modules/course_description_handler.js.coffee
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright (c) 2015 Pro Natura Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-
-app = window.App ||= {}
-
-app.EventDescription = {
- getDescription: ->
- that = app.EventDescription
- id = $(this).val()
- that.insertOrAsk(id)
-
- insertOrAsk: (id) ->
- if this.descriptionEmpty()
- this.fillDescription(id)
- else
- this.enableDefaultLink(id)
-
- getDescriptionForId: (id) ->
- $('.default-description[data-kind=' + id + ']').text().trim()
-
- fillDescription: (id) ->
- textarea = this.elements().textarea
- oldText = textarea.val()
- newText = this.getDescriptionForId(id)
-
- spacer = if oldText == "" then "" else " "
- textarea.val(oldText + spacer + newText)
-
- descriptionEmpty: ->
- return this.elements().textarea.val() == ""
-
- elements: ->
- {
- descriptionLink: $('.standard-description-link'),
- textarea: $('textarea#event_description')
- }
-
- enableDefaultLink: (id) ->
- this.showLink()
- link = this.elements().descriptionLink
-
- that = this
-
- link.off('click')
- link.click (e) ->
- e.preventDefault()
- that.fillDescription(id)
- that.hideLink()
-
- hideLink: ->
- this.elements().descriptionLink.parents('.controls').hide();
-
- showLink: ->
- this.elements().descriptionLink.parents('.controls').show();
-}
-
-$(document).on('change', 'select#event_kind_id', app.EventDescription.getDescription)
diff --git a/app/assets/javascripts/modules/disabled_links.js.coffee b/app/assets/javascripts/modules/disabled_links.js.coffee
new file mode 100644
index 0000000000..fe1bd7aa08
--- /dev/null
+++ b/app/assets/javascripts/modules/disabled_links.js.coffee
@@ -0,0 +1,11 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+# wire up disabled links
+$(document).on('click', 'a.disabled', (event) ->
+ $.rails.stopEverything(event)
+ event.preventDefault()
+)
+
diff --git a/app/assets/javascripts/modules/element_swapper.js.coffee b/app/assets/javascripts/modules/element_swapper.js.coffee
index 47b2c20b67..a7a70f7587 100644
--- a/app/assets/javascripts/modules/element_swapper.js.coffee
+++ b/app/assets/javascripts/modules/element_swapper.js.coffee
@@ -1,7 +1,7 @@
# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
+# https://github.com/hitobito/hitobito.
app = window.App ||= {}
diff --git a/app/assets/javascripts/modules/element_toggler.js.coffee b/app/assets/javascripts/modules/element_toggler.js.coffee
index d7596646d1..cb119b3cbf 100644
--- a/app/assets/javascripts/modules/element_toggler.js.coffee
+++ b/app/assets/javascripts/modules/element_toggler.js.coffee
@@ -35,7 +35,8 @@ $(document).on('change', 'input[data-hide]', (e) -> new app.ElementToggler(this)
$(document).on('change', 'input[data-show]', (e) -> new app.ElementToggler(this).show())
$(document).on('click', 'a[data-hide]', (e) -> new app.ElementToggler(this).toggle(e))
-$ ->
+$(document).on('turbolinks:load', ->
# initialize visibility of checkbox controlled elements
$('input[data-hide]').each((index, element) -> new app.ElementToggler(element).hide())
$('input[data-show]').each((index, element) -> new app.ElementToggler(element).show())
+)
diff --git a/app/assets/javascripts/modules/event_kind_preconditions.js.coffee b/app/assets/javascripts/modules/event_kind_preconditions.js.coffee
new file mode 100644
index 0000000000..c667cba3a8
--- /dev/null
+++ b/app/assets/javascripts/modules/event_kind_preconditions.js.coffee
@@ -0,0 +1,73 @@
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+app = window.App ||= {}
+
+app.EventKindPreconditions = {
+
+ showFields: (e) ->
+ e.preventDefault()
+ $('#event_kind_precondition_kind_ids').val([])
+ $('#add_precondition_grouping').hide()
+ $('#precondition_fields').slideDown()
+
+ hideFields: (e) ->
+ e.preventDefault()
+ $('#precondition_fields').slideUp()
+ $('#add_precondition_grouping').show()
+
+ removePreconditions: (e) ->
+ e.preventDefault()
+ $(this).parents('.precondition-grouping').remove()
+ $('.precondition-grouping:first-child .muted').remove()
+
+ addPreconditions: (e) ->
+ e.preventDefault()
+ obj = app.EventKindPreconditions
+ ids = $('#event_kind_precondition_kind_ids').val()
+ if ids.length
+ grouping = $('.precondition-grouping').length
+ html = '
' +
+ ids.map((id) -> obj.buildHiddenField(grouping, id)).join(' ') +
+ obj.buildConjunction(grouping) +
+ obj.buildSentence() +
+ obj.buildRemoveLink() +
+ '
'
+ $('#add_precondition_grouping').before(html)
+ obj.hideFields(e);
+
+
+ buildHiddenField: (grouping, id) ->
+ ' '
+
+ buildConjunction: (grouping) ->
+ if grouping
+ '' + $('#precondition_summary').data('or') + ' '
+ else
+ ''
+
+ buildRemoveLink: ->
+ ' '
+
+ buildSentence: ->
+ labels = app.EventKindPreconditions.fetchLabels()
+ last = labels.pop()
+ if labels.length
+ labels.join(', ') + ' ' + $('#precondition_summary').data('and') + ' ' + last
+ else
+ last
+
+ fetchLabels: ->
+ labels = []
+ $('#event_kind_precondition_kind_ids option:selected').each((i, option) ->
+ labels.push($(option).text()))
+ labels
+}
+
+$(document).on('click', '#add_precondition_grouping', app.EventKindPreconditions.showFields)
+$(document).on('click', '#precondition_fields .cancel', app.EventKindPreconditions.hideFields)
+$(document).on('click', '#precondition_fields button', app.EventKindPreconditions.addPreconditions)
+$(document).on('click', '.remove-precondition-grouping', app.EventKindPreconditions.removePreconditions)
diff --git a/app/assets/javascripts/modules/application_market.js.coffee b/app/assets/javascripts/modules/events/application_market.js.coffee
similarity index 100%
rename from app/assets/javascripts/modules/application_market.js.coffee
rename to app/assets/javascripts/modules/events/application_market.js.coffee
diff --git a/app/assets/javascripts/modules/events/course_description_handler.js.coffee b/app/assets/javascripts/modules/events/course_description_handler.js.coffee
new file mode 100644
index 0000000000..e3c9faf08b
--- /dev/null
+++ b/app/assets/javascripts/modules/events/course_description_handler.js.coffee
@@ -0,0 +1,61 @@
+# Copyright (c) 2017 Pro Natura Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+app = window.App ||= {}
+
+app.EventDescription = {
+ changeEventKind: ->
+ id = $(this).val()
+ app.EventDescription.insertOrAsk(id)
+
+ insertOrAsk: (id) ->
+ if this.descriptionEmpty()
+ this.fillDescription(id)
+ else
+ this.enableDefaultLink(id)
+
+ getDescriptionForId: (id) ->
+ $('.default-description[data-kind=' + id + ']').text().trim()
+
+ fillDescription: (id) ->
+ textarea = this.textarea()
+ oldText = textarea.val()
+ newText = this.getDescriptionForId(id)
+
+ spacer = if oldText == "" then "" else " "
+ textarea.val(oldText + spacer + newText)
+
+ descriptionEmpty: ->
+ this.textarea().val() == ""
+
+ insertDescription: (e) ->
+ e.preventDefault()
+ e.stopPropagation()
+ that = app.EventDescription
+ id = that.kindSelect().val()
+ that.fillDescription(id)
+ that.descriptionLink().hide()
+
+ enableDefaultLink: (id) ->
+ if id && this.getDescriptionForId(id) != ""
+ this.descriptionLink().show()
+ else
+ this.descriptionLink().hide()
+
+ descriptionLink: ->
+ $('.standard-description-link').parents('.help-block')
+
+ textarea: ->
+ $('textarea#event_description')
+
+ kindSelect: ->
+ $('select#event_kind_id')
+}
+
+$(document).on('change', 'select#event_kind_id', app.EventDescription.changeEventKind)
+$(document).on('click', '.standard-description-link', app.EventDescription.insertDescription)
+$(document).on('turbolinks:load', ->
+ $('select#event_kind_id').each((i, e) ->
+ app.EventDescription.enableDefaultLink($(e).val())))
diff --git a/app/assets/javascripts/modules/date_period_validator.js.coffee b/app/assets/javascripts/modules/events/date_period_validator.js.coffee
similarity index 100%
rename from app/assets/javascripts/modules/date_period_validator.js.coffee
rename to app/assets/javascripts/modules/events/date_period_validator.js.coffee
diff --git a/app/assets/javascripts/modules/groups/group_contact_toggle.js.coffee b/app/assets/javascripts/modules/groups/group_contact_toggle.js.coffee
new file mode 100644
index 0000000000..3a600dcd25
--- /dev/null
+++ b/app/assets/javascripts/modules/groups/group_contact_toggle.js.coffee
@@ -0,0 +1,16 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+# control visibilty of group contact fields in relation to contact
+
+toggleGroupContact = ->
+ open = !$('#group_contact_id').val()
+ fields = $('fieldset.info')
+ if !open && fields.is(':visible')
+ fields.slideUp()
+ else if open && !fields.is(':visible')
+ fields.slideDown()
+
+$(document).on('change', '#group_contact_id', toggleGroupContact)
diff --git a/app/assets/javascripts/modules/input_enabler.js.coffee b/app/assets/javascripts/modules/input_enabler.js.coffee
index fe05d47427..118fd3f02b 100644
--- a/app/assets/javascripts/modules/input_enabler.js.coffee
+++ b/app/assets/javascripts/modules/input_enabler.js.coffee
@@ -23,7 +23,8 @@ class app.InputEnabler
$(document).on('change', 'input[data-disable]', (e) -> new app.InputEnabler(this).disable())
$(document).on('change', 'input[data-enable]', (e) -> new app.InputEnabler(this).enable())
-$ ->
+$(document).on('turbolinks:load', ->
# initialize disabled state of checkbox controlled elements
$('input[data-disable]').each((index, element) -> new app.InputEnabler(element).disable())
$('input[data-enable]').each((index, element) -> new app.InputEnabler(element).enable())
+)
diff --git a/app/assets/javascripts/modules/invoice_articles.js.coffee b/app/assets/javascripts/modules/invoice_articles.js.coffee
new file mode 100644
index 0000000000..5f37fef8e0
--- /dev/null
+++ b/app/assets/javascripts/modules/invoice_articles.js.coffee
@@ -0,0 +1,20 @@
+app = window.App ||= {}
+
+app.InvoiceArticles = {
+ add: (e) ->
+ url = $('form[data-group').data('group')
+ articleAction = "#{url}/invoice_articles/#{e.target.value}.json"
+ $.ajax(url: articleAction, dataType: 'json', success: app.InvoiceArticles.updateForm)
+ e.target.value = undefined # reset field as preparation for next addition
+
+ updateForm: (data, status, req) ->
+ $('.add_nested_fields').first().click() # add new lineitem
+ fields = $('#invoice_items_fields .fields').last().find('input, textarea')
+ fields.each (idx, elm) ->
+ name = elm.name.match(/\d\]\[(.*)\]$/)[1]
+ elm.value = data[name] if data[name]
+ app.Invoices.recalculate()
+
+}
+
+$(document).on('change', '#invoice_item_article', app.InvoiceArticles.add)
diff --git a/app/assets/javascripts/modules/invoices.js.coffee b/app/assets/javascripts/modules/invoices.js.coffee
new file mode 100644
index 0000000000..a61970f8f3
--- /dev/null
+++ b/app/assets/javascripts/modules/invoices.js.coffee
@@ -0,0 +1,14 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+app = window.App ||= {}
+
+app.Invoices = {
+ recalculate: (e) ->
+ form = $('form[data-group')
+ $.ajax(url: "#{form.data('group')}/invoice_list/new?#{form.serialize()}", dataType: 'script')
+}
+
+$(document).on('input', '#invoice_items_fields :input[data-recalculate]', app.Invoices.recalculate)
diff --git a/app/assets/javascripts/modules/notes.js.coffee b/app/assets/javascripts/modules/notes.js.coffee
new file mode 100644
index 0000000000..2af0279d86
--- /dev/null
+++ b/app/assets/javascripts/modules/notes.js.coffee
@@ -0,0 +1,38 @@
+# Copyright (c) 2012-2017, Dachverband Schweizer Jugendparlamente. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+app = window.App ||= {}
+
+app.Notes = {
+ addNote: (note) ->
+ $('#notes-list').prepend(note)
+ $('#notes-list .pagination-info').text('')
+
+ new app.ElementSwapper().swap.call($('#notes-form'))
+ app.Notes.resetForm()
+ app.Notes.hideError()
+
+ resetForm: ->
+ $('#notes-form').find('form')[0].reset()
+
+ focus: ->
+ setTimeout(-> $('#note_text').focus())
+
+ showError: (error) ->
+ $('#notes-error').text(error).show()
+
+ hideError: ->
+ $('#notes-error').text('').hide()
+}
+
+
+$(document).on('turbolinks:load', ->
+ $('#notes-new-button').on('click', app.Notes.focus)
+ $('#notes-form .cancel').on('click', new app.ElementSwapper().swap)
+ $('#notes-form .cancel').on('click', ->
+ app.Notes.resetForm()
+ app.Notes.hideError()
+ )
+)
diff --git a/app/assets/javascripts/modules/people/people_filter_role_toggle.js.coffee b/app/assets/javascripts/modules/people/people_filter_role_toggle.js.coffee
new file mode 100644
index 0000000000..49b67b51ad
--- /dev/null
+++ b/app/assets/javascripts/modules/people/people_filter_role_toggle.js.coffee
@@ -0,0 +1,41 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+toggleFilterRoles = (event) ->
+ target = $(event.target)
+
+ boxes = target.nextUntil('.filter-toggle').find(':checkbox')
+ checked = boxes.filter(':checked').length == boxes.length
+
+ boxes.each((el) -> $(this).prop('checked', !checked))
+ target.data('checked', !checked)
+
+showAllGroups = (radio) ->
+ if radio.checked
+ $('.layer, .group').slideDown()
+
+showSameLayerGroups = (radio) ->
+ if radio.checked
+ $('.layer').hide()
+ $('.layer:not(.same-layer) input[type=checkbox]').prop('checked', false)
+ $('.same-layer').show()
+ $('.same-layer .group').slideDown()
+
+showSameGroup = (radio) ->
+ if radio.checked
+ $('.layer, .group').hide()
+ $('.layer:not(.same-layer) input[type=checkbox], .group:not(.same-group) input[type=checkbox]').prop('checked', false)
+ $('.same-layer, .same-group').show()
+
+$(document).on('click', '.filter-toggle', toggleFilterRoles)
+$(document).on('change', 'input#range_deep', (e) -> showAllGroups(e.target))
+$(document).on('change', 'input#range_layer', (e) -> showSameLayerGroups(e.target))
+$(document).on('change', 'input#range_group', (e) -> showSameGroup(e.target))
+
+$(document).on('turbolinks:load', ->
+ $('input#range_deep').each((i, e) -> showAllGroups(e))
+ $('input#range_layer').each((i, e) -> showSameLayerGroups(e))
+ $('input#range_group').each((i, e) -> showSameGroup(e))
+)
diff --git a/app/assets/javascripts/modules/person_tags.js.coffee b/app/assets/javascripts/modules/people/person_tags.js.coffee
similarity index 91%
rename from app/assets/javascripts/modules/person_tags.js.coffee
rename to app/assets/javascripts/modules/people/person_tags.js.coffee
index e7944abd6b..528fb933b8 100644
--- a/app/assets/javascripts/modules/person_tags.js.coffee
+++ b/app/assets/javascripts/modules/people/person_tags.js.coffee
@@ -1,7 +1,7 @@
# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
+# https://github.com/hitobito/hitobito.
app = window.App ||= {}
@@ -23,6 +23,7 @@ app.PersonTags = {
updateTags: (tags) ->
$('.person-tags').replaceWith(tags)
app.PersonTags.hideForm()
+ $('.person-tag-add').focus();
removeTag: (event) ->
event.preventDefault()
@@ -44,4 +45,3 @@ $(document).on('click', '.person-tag-add', app.PersonTags.showForm)
$(document).on('submit', '.person-tags-add-form', -> app.PersonTags.loading(true); return true);
$(document).on('keydown', '.person-tags-add-form input#acts_as_taggable_on_tag_name', (event) ->
event.keyCode == 27 && app.PersonTags.hideForm(); return true)
-
diff --git a/app/assets/javascripts/modules/people/toggle_condensed_labels.js.coffee b/app/assets/javascripts/modules/people/toggle_condensed_labels.js.coffee
new file mode 100644
index 0000000000..2185587e17
--- /dev/null
+++ b/app/assets/javascripts/modules/people/toggle_condensed_labels.js.coffee
@@ -0,0 +1,16 @@
+# Copyright (c) 2015 Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+$(document).on('click', '#toggle-condense-labels', (event) ->
+ event.stopPropagation()
+ $(this).find('input[type="checkbox"]').toggle
+)
+
+$(document).on('change', '#toggle-condense-labels input[type="checkbox"]', () ->
+ param = 'condense_labels='
+ checked = !!this.checked
+ $(this).parents('.dropdown-menu').find('a.export-label-format').each ->
+ $(this).attr('href', $(this).attr('href').replace(param + !checked, param + checked))
+)
diff --git a/app/assets/javascripts/modules/person_notes.js.coffee b/app/assets/javascripts/modules/person_notes.js.coffee
deleted file mode 100644
index bcf6de0fac..0000000000
--- a/app/assets/javascripts/modules/person_notes.js.coffee
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
-
-app = window.App ||= {}
-
-app.PersonNotes = {
- addNote: (note) ->
- $('#person-notes-list').prepend(note)
- $('#person-notes-pagination .pagination-info').text('')
-
- new app.ElementSwapper().swap.call($('#person-notes-form'))
- app.PersonNotes.resetForm()
- app.PersonNotes.hideError()
-
- resetForm: ->
- $('#person-notes-form').find('form')[0].reset()
-
- focus: ->
- setTimeout(-> $('#person_note_text').focus())
-
- showError: (error) ->
- $('#person-notes-error').text(error).show()
-
- hideError: ->
- $('#person-notes-error').text('').hide()
-}
-
-$ ->
- $('#person-notes-new-button').on('click', app.PersonNotes.focus)
- $('#person-notes-form .cancel').on('click', new app.ElementSwapper().swap)
- $('#person-notes-form .cancel').on('click', ->
- app.PersonNotes.resetForm()
- app.PersonNotes.hideError()
- )
diff --git a/app/assets/javascripts/modules/popover_handler.js.coffee b/app/assets/javascripts/modules/popover_handler.js.coffee
index 5d06ff569c..ad16a64de8 100644
--- a/app/assets/javascripts/modules/popover_handler.js.coffee
+++ b/app/assets/javascripts/modules/popover_handler.js.coffee
@@ -9,13 +9,14 @@ app = window.App ||= {}
class app.PopoverHandler
constructor: () ->
- toggle: (toggler) ->
+ toggle: (toggler, event) ->
# custom code to close other popovers when a new one is opened
$('[data-toggle=popover]').not(toggler).popover('hide')
$(toggler).popover()
popover = $(toggler).data('popover')
popover.options.html = true
popover.options.placement = 'bottom'
+ event.preventDefault()
if popover.tip().hasClass('fade') && !popover.tip().hasClass('in')
$(toggler).popover('hide')
else
@@ -28,7 +29,7 @@ class app.PopoverHandler
bind: ->
self = this
- $(document).on('click', '[data-toggle=popover]', (e) -> self.toggle(this))
+ $(document).on('click', '[data-toggle=popover]', (e) -> self.toggle(this, e))
$(document).on('click', '.popover a.cancel', (e) -> self.close(e))
new app.PopoverHandler().bind()
diff --git a/app/assets/javascripts/modules/remote_typeahead.js.coffee b/app/assets/javascripts/modules/remote_typeahead.js.coffee
index ec72c3fb99..f141d8b1c1 100644
--- a/app/assets/javascripts/modules/remote_typeahead.js.coffee
+++ b/app/assets/javascripts/modules/remote_typeahead.js.coffee
@@ -53,8 +53,11 @@ setupRemoteTypeahead = (input, items, updater) ->
queryForTypeahead = (query, process) ->
return [] if query.length < 3
- $.get(this.$element.data('url'), { q: query }, (data) ->
+ app.request.abort() if app.request
+ $('#quicksearch').addClass('input-loading')
+ app.request = $.get(this.$element.data('url'), { q: query }, (data) ->
json = $.map(data, (item) -> JSON.stringify(item))
+ $('#quicksearch').removeClass('input-loading')
return process(json)
)
@@ -88,10 +91,14 @@ window.nestedFormEvents.insertFields = (content, assoc, link) ->
.find('[data-provide=entity]').each(app.setupEntityTypeahead)
-$ ->
+# make clicking on typeahead item always select it (https://github.com/twitter/bootstrap/issues/4018)
+$(document).on('mousedown', 'ul.typeahead', (e) -> e.preventDefault())
+
+$(document).on('turbolinks:load', ->
# wire up quick search
app.setupQuicksearch()
# wire up person auto complete
$('[data-provide=entity]').each(app.setupEntityTypeahead)
$('[data-provide]').each(() -> $(this).attr('autocomplete', "off"))
+)
diff --git a/app/assets/javascripts/modules/string_trim.js.coffee b/app/assets/javascripts/modules/string_trim.js.coffee
new file mode 100644
index 0000000000..3b9313fa4d
--- /dev/null
+++ b/app/assets/javascripts/modules/string_trim.js.coffee
@@ -0,0 +1,9 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+# add trim function for older browsers
+if !String.prototype.trim
+ String.prototype.trim = () -> this.replace(/^\s+|\s+$/g, '')
+
diff --git a/app/assets/javascripts/modules/toggle_condensed_labels.js.coffee b/app/assets/javascripts/modules/toggle_condensed_labels.js.coffee
deleted file mode 100644
index 32a6302c0a..0000000000
--- a/app/assets/javascripts/modules/toggle_condensed_labels.js.coffee
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (c) 2015 Pfadibewegung Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-
-$ ->
- button = $('#toggle-condense-labels')
- checkbox = button.find('input[type="checkbox"]')
- param = 'condense_labels='
-
- button.click (event) ->
- event.stopPropagation()
- checkbox.toggle
-
- checkbox.change ->
- checked = !!this.checked
- $(this).parents('.dropdown-menu').find('a.export-label-format').each ->
- $(this).attr('href', $(this).attr('href').replace(param + !checked, param + checked))
diff --git a/app/assets/javascripts/modules/tooltips.js.coffee b/app/assets/javascripts/modules/tooltips.js.coffee
new file mode 100644
index 0000000000..82bc932045
--- /dev/null
+++ b/app/assets/javascripts/modules/tooltips.js.coffee
@@ -0,0 +1,10 @@
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+# only bind events for non-document elements in turbolinks:load
+$(document).on('turbolinks:load', ->
+ # wire up tooltips
+ $(document).tooltip({ selector: '[rel^=tooltip]', placement: 'right' })
+)
diff --git a/app/assets/javascripts/progress-bar.js.coffee b/app/assets/javascripts/progress-bar.js.coffee
deleted file mode 100644
index f24610d86d..0000000000
--- a/app/assets/javascripts/progress-bar.js.coffee
+++ /dev/null
@@ -1 +0,0 @@
-Turbolinks.enableProgressBar()
diff --git a/app/assets/javascripts/wysiwyg.js.coffee b/app/assets/javascripts/wysiwyg.js.coffee
index bf0781b731..522f6b808a 100644
--- a/app/assets/javascripts/wysiwyg.js.coffee
+++ b/app/assets/javascripts/wysiwyg.js.coffee
@@ -22,8 +22,8 @@ for lang, title of wysi_languages
for num in [1..6]
$.fn.wysihtml5.locale[lang].font_styles["h#{num}"] = "#{title} #{num}"
-$ ->
+$(document).on('turbolinks:load', ->
wysilocale = do ->
lang = $('html').attr('lang')
lang + '-' + lang.toUpperCase()
@@ -31,5 +31,6 @@ $ ->
# wire up wysiwyg text areas
$('textarea.wysiwyg').wysihtml5({
locale: wysilocale
- });
+ })
+)
diff --git a/app/assets/stylesheets/bootstrap_config_manual.scss b/app/assets/stylesheets/bootstrap_config_manual.scss
index dc6bcc9f76..5f3c1c3142 100644
--- a/app/assets/stylesheets/bootstrap_config_manual.scss
+++ b/app/assets/stylesheets/bootstrap_config_manual.scss
@@ -55,7 +55,7 @@
//@import "bootstrap/thumbnails";
@import "bootstrap/labels-badges";
//@import "bootstrap/progress-bars";
-//@import "bootstrap/accordion";
+@import "bootstrap/accordion";
//@import "bootstrap/carousel";
//@import "bootstrap/hero-unit";
diff --git a/app/assets/stylesheets/hitobito/_form.scss b/app/assets/stylesheets/hitobito/_form.scss
index add400b4c8..6637a16a04 100644
--- a/app/assets/stylesheets/hitobito/_form.scss
+++ b/app/assets/stylesheets/hitobito/_form.scss
@@ -32,6 +32,7 @@
}
}
+
.icon,
[class^="icon-"],
[class*=" icon-"] {
@@ -49,6 +50,12 @@
&.btn-info, &.btn-info:hover,
&.btn-inverse, &.btn-inverse:hover {
color: $white;
+
+ .icon,
+ [class^="icon-"],
+ [class*=" icon-"] {
+ background-image: image-url("glyphicons-halflings-white.png");
+ }
}
}
@@ -82,6 +89,9 @@ a.green {
margin-bottom: 0;
}
+.form-inline-search .control-group {
+ display: inline-block;
+}
@include responsive(mediaPhone) {
.form-horizontal .btn-toolbar {
margin-left: 180px;
@@ -145,6 +155,18 @@ input[type="color"],
}
}
+.input-loading {
+ background-image: image-url("spinner.gif");
+ background-size: 16px;
+ background-position:right 5px center;
+ background-repeat: no-repeat;
+}
+
+select {
+ height: 30px;
+ line-height: 30px;
+}
+
.form-inline {
select,
textarea,
@@ -264,6 +286,9 @@ textarea {
.controls > .text {
line-height: 29px;
+ padding-bottom: 8px;
+ vertical-align: middle;
+ display: inline-block;
}
.remove_nested_fields {
@@ -397,3 +422,72 @@ input.switcher {
box-shadow: inset 0 0 0 1px $green, 0 1px 2px rgba(0, 0, 0, .2);
}
}
+
+.accordion-heading {
+ .header {
+ font-size: 1.2em;
+ font-weight: 600;
+ }
+
+ a {
+ color: $black;
+ }
+
+ a:hover {
+ text-decoration: none;
+ }
+}
+
+.accordion-body {
+ legend {
+ border-bottom: 0;
+ font-size: 1.12em;
+ }
+
+ fieldset:last-child {
+ margin-bottom: 0;
+ }
+}
+
+#roles {
+ legend + .layer {
+ margin-top: 0;
+ }
+
+ .layer {
+ + .layer {
+ margin-top: 2em;
+ }
+
+ .control-group {
+ margin-top: 0;
+ }
+
+ h4, h5 {
+ cursor: pointer;
+ }
+ }
+}
+
+.has-clear {
+ position: relative;
+ input::-ms-clear { display: none; }
+
+ [data-clear] {
+ cursor: pointer;
+ opacity: 0.3;
+ pointer-events: auto;
+ position: absolute;
+ right: 5px;
+ top: 30px;
+
+ &:hover {
+ opacity: 0.5;
+ }
+ }
+
+ &.has-empty-value {
+ [data-clear] { display: none; }
+ }
+}
+
diff --git a/app/assets/stylesheets/hitobito/_layout.scss b/app/assets/stylesheets/hitobito/_layout.scss
index 03e4e964ab..ee0d8be357 100644
--- a/app/assets/stylesheets/hitobito/_layout.scss
+++ b/app/assets/stylesheets/hitobito/_layout.scss
@@ -273,11 +273,18 @@ body {
}
}
-// well
.well {
border: none;
background: $grayLighter;
+ > {
+ h1, h2, h3, h4, h5 {
+ &:first-child {
+ margin-top: 0;
+ }
+ }
+ }
+
&.panel {
margin-top: 5px;
border: 2px solid $grayLighter;
@@ -301,39 +308,6 @@ body {
background-color: $purple !important;
}
-table.roles {
- width: 100%;
- td {
- border: none;
- padding: 0px;
- }
- td:first-child { padding-left: 10px; width: 100%; }
- td { min-width: 20px; }
-}
-
-.table-responsive {
- .table {
- background-color: #fff;
- }
-
- @media screen and (max-width: $mediaDesktop) {
- width: 100%;
- margin-bottom: 15px;
- overflow-x: scroll;
- overflow-y: hidden;
- -ms-overflow-style: -ms-autohiding-scrollbar;
- -webkit-overflow-scrolling: touch;
-
- .table {
- margin-bottom: 0;
- }
- }
-}
-
-.table-striped th:first-child {
- padding-left: 8px;
-}
-
// Logs
.log-item {
margin-top: 2em;
diff --git a/app/assets/stylesheets/hitobito/_main.scss b/app/assets/stylesheets/hitobito/_main.scss
index a123021f95..81cc81c7c6 100644
--- a/app/assets/stylesheets/hitobito/_main.scss
+++ b/app/assets/stylesheets/hitobito/_main.scss
@@ -7,11 +7,16 @@
@import "hitobito/typography";
@import "hitobito/navigation";
@import "hitobito/form";
+@import "hitobito/table";
@import "hitobito/layout";
@import "hitobito/fonts";
/* Modules */
-@import "hitobito/modules/person_note";
+@import "hitobito/modules/note";
@import "hitobito/modules/person_tags";
+@import "hitobito/modules/chip";
+@import "hitobito/modules/step_wizard";
+@import "hitobito/modules/invoice";
+
@import "hitobito/wagon";
diff --git a/app/assets/stylesheets/hitobito/_table.scss b/app/assets/stylesheets/hitobito/_table.scss
new file mode 100644
index 0000000000..15799a5470
--- /dev/null
+++ b/app/assets/stylesheets/hitobito/_table.scss
@@ -0,0 +1,40 @@
+.table-responsive {
+ .table {
+ background-color: #fff;
+ }
+
+ @media screen and (max-width: $mediaDesktop) {
+ width: 100%;
+ margin-bottom: 15px;
+ overflow-x: scroll;
+ overflow-y: hidden;
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+ -webkit-overflow-scrolling: touch;
+
+ .table {
+ margin-bottom: 0;
+ }
+ }
+}
+
+.table-striped th:first-child {
+ padding-left: 8px;
+}
+
+.table-fixed {
+ table-layout: fixed;
+}
+
+td.action {
+ text-align: center;
+}
+
+table.roles {
+ width: 100%;
+ td {
+ border: none;
+ padding: 0px;
+ }
+ td:first-child { padding-left: 10px; width: 100%; }
+ td { min-width: 20px; }
+}
diff --git a/app/assets/stylesheets/hitobito/modules/_chip.scss b/app/assets/stylesheets/hitobito/modules/_chip.scss
new file mode 100644
index 0000000000..adbbdd99ce
--- /dev/null
+++ b/app/assets/stylesheets/hitobito/modules/_chip.scss
@@ -0,0 +1,33 @@
+.chip {
+ display: inline-block;
+ box-sizing: border-box;
+ height: 28px;
+ margin-bottom: 8px;
+ padding: 4px 12px 6px 12px;
+ font-size: 13px;
+ border-radius: 14px;
+ background-color: $grayLight;
+
+ i {
+ opacity: 0.6;
+ }
+ i:hover {
+ opacity: 1;
+ }
+
+ &.chip-add {
+ border: 2px dashed $grayLight;
+ padding: 2px 10px 4px 10px;
+ font-weight: normal;
+ background-color: transparent;
+ color: $textColor;
+ cursor: pointer;
+
+ i {
+ opacity: 0.8;
+ }
+ &:hover {
+ background-color: $grayLighter;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/hitobito/modules/_invoice.scss b/app/assets/stylesheets/hitobito/modules/_invoice.scss
new file mode 100644
index 0000000000..46ec55265f
--- /dev/null
+++ b/app/assets/stylesheets/hitobito/modules/_invoice.scss
@@ -0,0 +1,73 @@
+.invoice-state {
+ background: $grayLighter;
+ margin-left: -20px;
+ margin-right: -20px;
+ padding: 10px 21px;
+
+ .invoice-state-table {
+ float: left;
+ margin-left: 50px;
+ margin-bottom: 20px;
+
+ tr th {
+ color: $gray;
+ font-weight: normal;
+ text-align: left;
+ }
+ tr td{
+ padding-right: 30px;
+ }
+ }
+
+ .invoice-history{
+ table {
+ float: left;
+ margin-left: 50px;
+
+ td:first-child { font-size: 6px; padding-right: 5px; }
+ td.red:first-child { color: $red; }
+ td.blue:first-child { color: $blue; }
+ td.green:first-child { color: $green; }
+ td:nth-child(2) {
+ padding-right: 20px;
+ color: $gray;
+ font-weight: normal;
+ }
+ }
+ }
+}
+
+.invoice {
+ margin: 0px 50px;
+
+ .invoice-recipient-address{
+ margin: 30px 0px;
+ }
+
+
+ .invoice-table {
+ table.header {
+ border-bottom: 1px solid $grayLight;
+ width: 100%;
+ font-size: 16px;
+ margin-bottom: 10px;
+ }
+
+ table tr th:last-child,
+ table tr td:last-child {
+ @extend .right;
+ }
+
+ .invoice-items-total {
+ margin-top: -15px;
+ height: 60px;
+
+ table{
+ float: right;
+ width: 250px;
+ tr{ border-bottom: 1px solid $grayLight; }
+ tr:first-child{ border-bottom: none; }
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/hitobito/modules/_note.scss b/app/assets/stylesheets/hitobito/modules/_note.scss
new file mode 100644
index 0000000000..2b0f16f595
--- /dev/null
+++ b/app/assets/stylesheets/hitobito/modules/_note.scss
@@ -0,0 +1,72 @@
+#notes-list .note:first-child {
+ border-top: 0;
+}
+
+.notes-pagination {
+ margin: 0;
+}
+
+#notes-index .pagination {
+ margin-top: 20px;
+ margin-bottom: 0;
+}
+
+#notes-index .pagination:first-child {
+ margin-bottom: 20px;
+ margin-top: 0;
+}
+
+.note {
+ border-top: 1px solid $grayLight;
+ padding: 5px 0 10px;
+ position: relative;
+ display: flex;
+
+ .note-image {
+ flex: 0 0 auto;
+ margin-right: 20px;
+ height: 40px;
+ width: 40px;
+ }
+
+ .note-body {
+ flex: 1 1 auto;
+ width: 100%;
+ }
+
+ .note-subject {
+ font-size: 110%;
+ }
+
+ .note-author {
+ float: right;
+ }
+
+ .note-date {
+ display: inline-block;
+ padding-left: 0.2em;
+ }
+
+ &.is-current-subject .note-author {
+ width: 100%;
+ }
+
+ @include responsive(phone, $mediaTablet) {
+ .note-subject {
+ width: 100%;
+ clear: both;
+ }
+ .note-author {
+ float: left;
+ width: 100%;
+ }
+ }
+
+ i {
+ opacity: 0.7;
+ }
+
+ i:hover {
+ opacity: 1;
+ }
+}
diff --git a/app/assets/stylesheets/hitobito/modules/_person_note.scss b/app/assets/stylesheets/hitobito/modules/_person_note.scss
deleted file mode 100644
index 28c1f57c4a..0000000000
--- a/app/assets/stylesheets/hitobito/modules/_person_note.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-.note {
- border-top: 1px solid $grayLight;
- padding: 5px 0 10px;
- position: relative;
-
- .note-image {
- position: absolute;
- width: 40px;
- }
-
- .note-body {
- position: relative;
- left: 60px;
- padding-right: 60px;
- widht: 100%;
- }
-
- .note-person {
- font-size: 110%;
- }
-
- .note-author {
- float: right;
- }
- @include responsive(phone, $mediaTablet) {
- .note-person {
- width: 100%;
- clear: both;
- }
- .note-author {
- float: left;
- }
- }
-}
diff --git a/app/assets/stylesheets/hitobito/modules/_person_tags.scss b/app/assets/stylesheets/hitobito/modules/_person_tags.scss
index 00f2e4c92b..115eb19a5c 100644
--- a/app/assets/stylesheets/hitobito/modules/_person_tags.scss
+++ b/app/assets/stylesheets/hitobito/modules/_person_tags.scss
@@ -1,5 +1,6 @@
.person-tags-category {
display: table-row;
+ margin-top: 12px;
}
.person-tags-category-title,
.person-tags-category-list {
@@ -12,30 +13,11 @@
}
.person-tags-category-title {
+ padding-bottom: 20px;
padding-right: 20px;
text-align: right;
}
-.person-tag {
- margin-bottom: 4px;
- padding: 3px 6px;
- border: 2px solid $grayDark;
- font-size: 14px;
- line-height: 20px;
-}
-
-.person-tag-add {
- color: $textColor;
- font-weight: normal;
- border: 2px dashed $grayLight;
- background-color: transparent;
- cursor: pointer;
-
- &:hover {
- background-color: $grayLighter;
- }
-}
-
.person-tags-add-form {
display: inline-block;
margin-bottom: 4px;
diff --git a/app/assets/stylesheets/hitobito/modules/_step_wizard.scss b/app/assets/stylesheets/hitobito/modules/_step_wizard.scss
new file mode 100644
index 0000000000..8f51549395
--- /dev/null
+++ b/app/assets/stylesheets/hitobito/modules/_step_wizard.scss
@@ -0,0 +1,94 @@
+.stepwizard {
+ $stepwizard-size: 2em;
+
+ display: flex;
+ flex-direction: row;
+ font-size: 80%;
+ margin-bottom: $stepwizard-size/2;
+
+ background-color: $grayLight;
+ border-radius: 2em;
+
+ .stepwizard-step {
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: row;
+ }
+
+ .stepwizard-link {
+ color: inherit;
+ display: flex;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ align-items: center;
+ }
+
+ .stepwizard-link:hover {
+ .stepwizard-number {
+ background-color: $blue;
+ text-decoration: none;
+ }
+ }
+
+ // .stepwizard-step:not(:first-child):before, .stepwizard-step:not(:last-child):after {
+ // background-color: $gray;
+ // content: "";
+ // height: 1px;
+ // width: 1px;
+ // margin-top: $stepwizard-size / 2;
+ // flex: 1 1 auto;
+ // }
+
+ .stepwizard-number {
+ flex: 1 0 auto;
+ background-color: $gray;
+ color: white;
+ width: $stepwizard-size;
+ height: $stepwizard-size;
+ border-radius: $stepwizard-size;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: $stepwizard-size / 4;
+ }
+
+ .stepwizard-number:not(:first-child) {
+ margin-left: $stepwizard-size / 4;
+ }
+
+ .stepwizard-title {
+ display: none;
+ align-items: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ margin-right: $stepwizard-size;
+
+ }
+
+ @include responsive(mediaPhone) {
+ font-size: 80%;
+ .stepwizard-title {
+ display: flex;
+ }
+ }
+
+ @include responsive(mediaTablet){
+ font-size: 100%;
+ }
+
+ .is-current {
+ .stepwizard-number {
+ background-color: $blue;
+ }
+ .stepwizard-title {
+ color: $blue;
+ }
+ }
+
+ .is-disabled {
+ .stepwizard-link {
+ pointer-events: none;
+ opacity: 0.7;
+ }
+ }
+}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index ba3ad6270f..34c40c548d 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -22,6 +22,7 @@ class ApplicationController < ActionController::Base
hide_action :person_home_path
before_action :set_no_cache
+ before_action :set_paper_trail_whodunnit
alias_method :decorate, :__decorator_for__
@@ -52,6 +53,6 @@ def set_no_cache
end
def html_request?
- request.format.html? || request.format == Mime::ALL
+ request.formats.any? { |f| f.html? || f == Mime::ALL }
end
end
diff --git a/app/controllers/changelog_controller.rb b/app/controllers/changelog_controller.rb
index 1ac1ec7237..98ca3bd035 100644
--- a/app/controllers/changelog_controller.rb
+++ b/app/controllers/changelog_controller.rb
@@ -6,15 +6,11 @@
# https://github.com/hitobito/hitobito.
class ChangelogController < ApplicationController
+
+ skip_before_action :authenticate_person!
skip_authorization_check
def index
-
end
- private
-
- def devise_controller?
- true # hence, no login required
- end
end
diff --git a/app/controllers/concerns/render_people_exports.rb b/app/controllers/concerns/render_people_exports.rb
index 4783851964..386faf4b66 100644
--- a/app/controllers/concerns/render_people_exports.rb
+++ b/app/controllers/concerns/render_people_exports.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -20,6 +20,11 @@ def render_emails(people)
emails = Person.mailing_emails_for(people)
render text: emails.join(',')
end
+
+ def render_vcf(people)
+ vcf = generate_vcf(people)
+ send_data vcf, type: :vcf, disposition: 'inline'
+ end
private
@@ -34,6 +39,10 @@ def condense_people(people)
def generate_pdf(people)
Export::Pdf::Labels.new(find_and_remember_label_format).generate(people)
end
+
+ def generate_vcf(people)
+ Export::Vcf::Vcards.new.generate(people)
+ end
def find_and_remember_label_format
LabelFormat.find(params[:label_format_id]).tap do |label_format|
diff --git a/app/controllers/concerns/searchable.rb b/app/controllers/concerns/searchable.rb
index bbe53b8ead..0d93405115 100644
--- a/app/controllers/concerns/searchable.rb
+++ b/app/controllers/concerns/searchable.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2015, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -8,48 +8,56 @@
# The search functionality for the index table.
# Extracted into an own module for convenience.
module Searchable
- def self.included(controller)
+
+ extend ActiveSupport::Concern
+
+ included do
# Define an array of searchable columns in your subclassing controllers.
- controller.class_attribute :search_columns
- controller.search_columns = []
+ class_attribute :search_columns
+ self.search_columns = []
- controller.helper_method :search_support?
+ helper_method :search_support?
- controller.alias_method_chain :list_entries, :search
+ alias_method_chain :list_entries, :search
end
private
# Enhance the list entries with an optional search criteria
def list_entries_with_search
- list_entries_without_search.where(search_condition(*search_columns))
+ list_entries_without_search.where(search_conditions)
end
- # Compose the search condition with a basic SQL OR query.
- def search_condition(*columns)
- if columns.present? && params[:q].present?
- terms = search_terms
- col_condition = search_column_conditions(columns)
- clause = terms.collect { |_| "(#{col_condition})" }.join(' AND ')
-
- ["(#{clause})"] + terms.collect { |t| [t] * columns.size }.flatten
+ # Concat the word clauses with AND.
+ def search_conditions
+ if search_support? && params[:q].present?
+ search_condition(*self.class.search_tables_and_fields)
end
end
- def search_terms
- params[:q].split(/\s+/).collect { |t| "%#{t}%" }
+ # Returns true if this controller has searchable columns.
+ def search_support?
+ search_columns.present?
end
- def search_column_conditions(columns)
- columns.collect do |f|
- col = f.to_s.include?('.') ? f : "#{model_class.table_name}.#{f}"
- "#{col} LIKE ?"
- end.join(' OR ')
+ def search_condition(*fields)
+ SearchStrategies::SqlConditionBuilder.new(params[:q], fields).search_conditions
end
- # Returns true if this controller has searchable columns.
- def search_support?
- search_columns.present?
+ # Class methods for Searchable.
+ module ClassMethods
+
+ # All search columns divided in table and field names.
+ def search_tables_and_fields
+ @search_tables_and_fields ||= search_columns.map do |f|
+ if f.to_s.include?('.')
+ f
+ else
+ "#{model_class.table_name}.#{f}"
+ end
+ end
+ end
+
end
end
diff --git a/app/controllers/event/application_market_controller.rb b/app/controllers/event/application_market_controller.rb
index f4db3fe703..e740cfe5be 100644
--- a/app/controllers/event/application_market_controller.rb
+++ b/app/controllers/event/application_market_controller.rb
@@ -52,8 +52,7 @@ def load_participants
def load_applications
Event::Participation.
- joins(:event).
- includes(:application, person: [:primary_group]).
+ includes(:application, :event, person: [:primary_group]).
references(:application).
where(filter_applications).
merge(Event::Participation.pending).
diff --git a/app/controllers/event/attachments_controller.rb b/app/controllers/event/attachments_controller.rb
index bc430d33fd..6e19708df2 100644
--- a/app/controllers/event/attachments_controller.rb
+++ b/app/controllers/event/attachments_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2015, Pro Natura Schweiz. This file is part of
+# Copyright (c) 2015-2017, Pro Natura Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -22,7 +22,7 @@ def self.model_class
private
- alias_method :event, :parent
+ alias event parent
def index_path
group_event_path(*parents)
diff --git a/app/controllers/event/kinds_controller.rb b/app/controllers/event/kinds_controller.rb
index 59f2f0f12d..e5fbf3216e 100644
--- a/app/controllers/event/kinds_controller.rb
+++ b/app/controllers/event/kinds_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -9,15 +9,16 @@ class Event::KindsController < SimpleCrudController
self.permitted_attrs = [:label, :short_name, :minimum_age,
:general_information, :application_conditions,
+ precondition_qualification_kinds: [{ qualification_kind_ids: [] }],
qualification_kinds: {
participant: {
- precondition: { qualification_kind_ids: [] },
qualification: { qualification_kind_ids: [] },
- prolongation: { qualification_kind_ids: [] } },
+ prolongation: { qualification_kind_ids: [] }
+ },
leader: {
- precondition: { qualification_kind_ids: [] },
qualification: { qualification_kind_ids: [] },
- prolongation: { qualification_kind_ids: [] } }
+ prolongation: { qualification_kind_ids: [] }
+ }
}]
self.sort_mappings = { label: 'event_kind_translations.label',
@@ -49,9 +50,12 @@ def unlimited_qualifications
def permitted_params
attrs = super
kinds_attrs = attrs.delete(:qualification_kinds) || {}
+ precondition_attrs = attrs.delete(:precondition_qualification_kinds) || {}
+
existing_kinds = entry.event_kind_qualification_kinds.to_a
kinds_attrs = flatten_nested_qualification_kinds(kinds_attrs, existing_kinds)
+ kinds_attrs += flatten_precondition_qualification_kinds(precondition_attrs, existing_kinds)
mark_qualifikation_kinds_for_removal!(kinds_attrs, existing_kinds)
attrs[:event_kind_qualification_kinds_attributes] = kinds_attrs
@@ -63,21 +67,35 @@ def flatten_nested_qualification_kinds(kinds_attrs, existing_kinds)
kinds_attrs.flat_map do |role, categories|
categories.flat_map do |category, ids|
ids.fetch(:qualification_kind_ids, []).collect do |id|
- { id: qualification_kind_assoc_id(id, role, category, existing_kinds),
+ { id: find_qualification_kind_assoc_id(existing_kinds, id, role, category),
role: role,
category: category,
qualification_kind_id: id }
-
end
end
end
end
- def qualification_kind_assoc_id(qualification_kind_id, role, category, existing_kinds)
+ def flatten_precondition_qualification_kinds(grouped_ids, existing_kinds)
+ grouped_ids.each_with_index.flat_map do |(_, ids), index|
+ ids.fetch(:qualification_kind_ids, []).map do |id|
+ { id: find_qualification_kind_assoc_id(existing_kinds, id,
+ 'participant', 'precondition', index + 1),
+ role: 'participant',
+ category: 'precondition',
+ qualification_kind_id: id,
+ grouping: index + 1 }
+ end
+ end
+ end
+
+ def find_qualification_kind_assoc_id(existing_kinds, qualification_kind_id, role,
+ category, grouping = nil)
kind = existing_kinds.find do |k|
k.role == role &&
k.category == category &&
- k.qualification_kind_id == qualification_kind_id
+ k.qualification_kind_id == qualification_kind_id &&
+ k.grouping == grouping
end
kind.try(:id)
end
diff --git a/app/controllers/event/lists_controller.rb b/app/controllers/event/lists_controller.rb
index b7786104f8..b3f4c05a1f 100644
--- a/app/controllers/event/lists_controller.rb
+++ b/app/controllers/event/lists_controller.rb
@@ -11,7 +11,7 @@ class Event::ListsController < ApplicationController
DEFAULT_GROUPING = ->(event) { I18n.l(event.dates.first.start_at, format: :month_year) }
attr_reader :group_id
- helper_method :group_id, :kind_used?, :nav_left
+ helper_method :group_id, :kind_used?, :nav_left, :display_any_booking_info?
skip_authorize_resource only: [:events, :courses]
@@ -31,7 +31,10 @@ def courses
@grouped_events = sorted(grouped(limited_courses_scope, course_grouping))
end
format.csv do
- render_courses_csv(limited_courses_scope)
+ render_tabular(:csv, limited_courses_scope)
+ end
+ format.xlsx do
+ render_tabular(:xlsx, limited_courses_scope)
end
end
end
@@ -48,11 +51,11 @@ def sorted(courses)
courses.values.each do |entries|
entries.sort_by! { |e| e.dates.first.try(:start_at) || Time.zone.now }
end
- courses
+ Hash[courses.sort]
end
- def render_courses_csv(courses)
- send_data Export::Csv::Events::List.export(courses), type: :csv
+ def render_tabular(format, courses)
+ send_data Export::Tabular::Events::List.export(format, courses), type: format
end
def set_group_vars
@@ -65,19 +68,32 @@ def set_group_vars
end
def upcoming_user_events
- Event.upcoming.
- in_hierarchy(current_user).
- includes(:dates, :groups).
- where('events.type != ? OR events.type IS NULL', Event::Course.sti_name).
- order('event_dates.start_at ASC')
+ Event.
+ upcoming.
+ in_hierarchy(current_user).
+ includes(:dates, :groups).
+ where('events.type != ? OR events.type IS NULL', Event::Course.sti_name).
+ order('event_dates.start_at ASC')
end
def default_user_course_group
- Group.course_offerers.
- where(id: current_user.groups_hierarchy_ids).
- where('groups.id <> ?', Group.root.id).
- select(:id).
- first
+ course_group_from_primary_layer || course_group_from_hierarchy
+ end
+
+ def course_group_from_primary_layer
+ Group.
+ course_offerers.
+ where(id: current_user.primary_group.try(:layer_group_id)).
+ first
+ end
+
+ def course_group_from_hierarchy
+ Group.
+ course_offerers.
+ where(id: current_user.groups_hierarchy_ids).
+ where('groups.id <> ?', Group.root.id).
+ select(:id).
+ first
end
def limited_courses_scope(scope = course_scope)
@@ -89,11 +105,11 @@ def limited_courses_scope(scope = course_scope)
end
def course_scope
- Event::Course
- .includes(:groups, additional_course_includes)
- .order(course_ordering)
- .in_year(year)
- .list
+ Event::Course.
+ includes(:groups, additional_course_includes).
+ order(course_ordering).
+ in_year(year).
+ list
end
def course_grouping
@@ -116,6 +132,10 @@ def nav_left
@nav_left || params[:action]
end
+ def display_any_booking_info?
+ @grouped_events.values.flatten.any? { |e| e.display_booking_info? }
+ end
+
def authorize_courses
if request.format.csv?
authorize!(:export_list, Event::Course)
diff --git a/app/controllers/event/participation_contact_datas_controller.rb b/app/controllers/event/participation_contact_datas_controller.rb
new file mode 100644
index 0000000000..c364a94355
--- /dev/null
+++ b/app/controllers/event/participation_contact_datas_controller.rb
@@ -0,0 +1,67 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Pfadibewegung Schweiz. This file is part of
+# hitobito_pbs and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito_pbs.
+
+class Event::ParticipationContactDatasController < ApplicationController
+
+ helper_method :group, :event, :entry
+
+ authorize_resource :entry, class: Event::ParticipationContactData
+
+ decorates :group, :event
+
+ before_action :set_entry, :group
+
+ def edit; end
+
+ def update
+ if entry.save
+ redirect_to new_group_event_participation_path(
+ group,
+ event,
+ event_role: { type: params[:event_role][:type] }
+ )
+ else
+ render :edit
+ end
+ end
+
+ private
+
+ def entry
+ @participation_contact_data
+ end
+
+ def build_entry
+ Event::ParticipationContactData.new(event, current_user)
+ end
+
+ def set_entry
+ @participation_contact_data =
+ if params[:event_participation_contact_data]
+ Event::ParticipationContactData.new(event, current_user, model_params)
+ else
+ build_entry
+ end
+ end
+
+ def event
+ @event ||= Event.find(params[:event_id])
+ end
+
+ def group
+ @group ||= Group.find(params[:group_id])
+ end
+
+ def model_params
+ params.require('event_participation_contact_data').permit(permitted_attrs)
+ end
+
+ def permitted_attrs
+ PeopleController.permitted_attrs
+ end
+
+end
diff --git a/app/controllers/event/participations_controller.rb b/app/controllers/event/participations_controller.rb
index 6aef0da4ff..189af7f4cc 100644
--- a/app/controllers/event/participations_controller.rb
+++ b/app/controllers/event/participations_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -21,11 +21,13 @@ class Event::ParticipationsController < CrudController
first_name: 'people.first_name',
roles: lambda do |event|
Person.order_by_name_statement.unshift(
- Event::Participation.order_by_role_statement(event))
+ Event::Participation.order_by_role_statement(event)
+ )
end,
nickname: 'people.nickname',
zip_code: 'people.zip_code',
- town: 'people.town' }
+ town: 'people.town',
+ birthday: 'people.birthday' }
decorates :group, :event, :participation, :participations, :alternatives
@@ -37,11 +39,13 @@ class Event::ParticipationsController < CrudController
before_action :check_preconditions, only: [:new]
before_render_new :init_answers
+ before_render_edit :load_answers
before_render_form :load_priorities
before_render_show :load_answers
before_render_show :load_precondition_warnings
after_create :send_confirmation_email
+ after_destroy :send_cancel_email
# new and create are only invoked by people who wish to
# apply for an event themselves. A participation for somebody
@@ -64,7 +68,9 @@ def index
@person_add_requests = fetch_person_add_requests
end
format.pdf { render_pdf(entries.collect(&:person)) }
- format.csv { send_data(exporter.export(entries), type: :csv) }
+ format.csv { render_tabular_in_background(:csv) && redirect_to(action: :index) }
+ format.vcf { render_vcf(entries.includes(person: :phone_numbers).collect(&:person)) }
+ format.xlsx { render_tabular_in_background(:xlsx) && redirect_to(action: :index) }
format.email { render_emails(entries.collect(&:person)) }
end
end
@@ -78,7 +84,16 @@ def print
end
def destroy
- super(location: group_event_application_market_index_path(group, event))
+ location = if entry.person_id == current_user.id
+ group_event_path(group, event)
+ else
+ group_event_application_market_index_path(group, event)
+ end
+ super(location: location)
+ end
+
+ def self.model_class
+ Event::Participation
end
private
@@ -103,12 +118,9 @@ def authorize_class
authorize!(:index_participations, event)
end
- def exporter
- if params[:details] && can?(:show_details, entries.first)
- Export::Csv::People::ParticipationsFull
- else
- Export::Csv::People::ParticipationsAddress
- end
+ def render_tabular_in_background(format)
+ Export::EventParticipationsExportJob.new(format, current_person.id, event.id, params).enqueue!
+ flash[:notice] = translate(:export_enqueued, email: current_person.email)
end
def check_preconditions
@@ -157,7 +169,7 @@ def role_type
type = event.class.find_role_type!(role_type)
unless type.participant?
- fail ActiveRecord::RecordNotFound, "No participant role '#{role_type}' found"
+ raise ActiveRecord::RecordNotFound, "No participant role '#{role_type}' found"
end
role_type
end
@@ -174,17 +186,17 @@ def assign_attributes
end
def init_answers
- entry.init_answers
+ @answers = entry.init_answers
entry.init_application
end
def load_priorities
if entry.application && event.priorization && current_user
- @alternatives = event.class.application_possible.
- where(kind_id: event.kind_id).
- in_hierarchy(current_user).
- includes(:groups).
- list
+ @alternatives = event.class.application_possible
+ .where(kind_id: event.kind_id)
+ .in_hierarchy(current_user)
+ .includes(:groups)
+ .list
@priority_2s = @priority_3s = (@alternatives.to_a - [event])
end
end
@@ -216,6 +228,12 @@ def send_confirmation_email
end
end
+ def send_cancel_email
+ if entry.person_id == current_user.id
+ Event::CancelApplicationJob.new(entry.event, entry.person).enqueue!
+ end
+ end
+
def set_success_notice
if action_name.to_s == 'create'
notice = translate(:success, full_entry_label: full_entry_label)
@@ -226,12 +244,8 @@ def set_success_notice
end
end
- def user_course_application?
- entry.person == current_user && event.supports_applications
- end
-
def append_mailing_instructions?
- user_course_application? && event.signature?
+ entry.person == current_user && event.signature?
end
def event
@@ -255,7 +269,4 @@ def fetch_person_add_requests
end
end
- def self.model_class
- Event::Participation
- end
end
diff --git a/app/controllers/event/qualifications_controller.rb b/app/controllers/event/qualifications_controller.rb
index 84690149a0..941db82024 100644
--- a/app/controllers/event/qualifications_controller.rb
+++ b/app/controllers/event/qualifications_controller.rb
@@ -7,48 +7,39 @@
class Event::QualificationsController < ApplicationController
- before_action :authorize
+ before_action :authorize_write, except: :index
+ before_action :authorize_read, only: :index
- decorates :event, :leaders, :participants, :participation, :group
+ decorates :event, :leaders, :participants, :group
- helper_method :event, :participation
+ helper_method :event
def index
entries
end
def update
- qualifier.issue
-
- @nothing_changed = qualifier.nothing_changed?
-
- respond_to do |format|
- format.html { redirect_to group_event_qualifications_path(group, event) }
- format.js { render 'qualification' }
+ Qualification.transaction do
+ entries
+ (@leaders + @participants).uniq.each do |participation|
+ qualifier = Event::Qualifier.for(participation)
+ qualified = Array(params[:participation_ids]).include?(participation.id.to_s)
+ qualified ? qualifier.issue : qualifier.revoke
+ end
end
- end
-
- def destroy
- qualifier.revoke
- respond_to do |format|
- format.html { redirect_to group_event_qualifications_path(group, event) }
- format.js { render 'qualification' }
- end
+ redirect_to group_event_qualifications_path(group, event),
+ notice: t('event.qualifications.update.flash.success')
end
private
def entries
- types = event.class.role_types
+ types = event.role_types
@leaders ||= participations(*types.select(&:leader?))
@participants ||= participations(*types.select(&:participant?))
end
- def qualifier
- @qualifier ||= Event::Qualifier.for(participation)
- end
-
def event
@event ||= group.events.find(params[:event_id])
end
@@ -57,16 +48,22 @@ def group
@group ||= Group.find(params[:group_id])
end
- def participation
- @participation ||= event.participations.find(params[:id])
- end
-
def participations(*role_types)
event.participations_for(*role_types).includes(:roles, :event)
end
- def authorize
- not_found unless event.course_kind? && event.qualifying?
+ def authorize_write
+ event_qualifying
authorize!(:qualify, event)
end
+
+ def authorize_read
+ event_qualifying
+ authorize!(:qualifications_read, event)
+ end
+
+ def event_qualifying
+ not_found unless event.course_kind? && event.qualifying?
+ end
+
end
diff --git a/app/controllers/event/register_controller.rb b/app/controllers/event/register_controller.rb
index 847fd670cd..1a50e8289d 100644
--- a/app/controllers/event/register_controller.rb
+++ b/app/controllers/event/register_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -96,8 +96,8 @@ def person
@person ||= Person.new
end
- alias_method :entry, :person
- alias_method :resource, :person
+ alias entry person
+ alias resource person
def event
@event ||= group.events.find(params[:id])
diff --git a/app/controllers/event/roles_controller.rb b/app/controllers/event/roles_controller.rb
index 0e21f479ab..514cf250a0 100644
--- a/app/controllers/event/roles_controller.rb
+++ b/app/controllers/event/roles_controller.rb
@@ -49,7 +49,7 @@ def build_entry
attrs.delete(:event_id)
attrs.delete(:person)
# assert that type is valid
- event.class.find_role_type!(attrs[:type])
+ event.find_role_type!(attrs[:type])
participation = event.participations.where(person_id: attrs.delete(:person_id)).
first_or_initialize
@@ -104,9 +104,9 @@ def permitted_params
def possible_types
@possible_types ||=
if entry.restricted?
- event.class.participant_types
+ event.participant_types
else
- event.class.role_types.reject(&:restricted?)
+ event.role_types.reject(&:restricted?)
end
end
diff --git a/app/controllers/event/top_controller.rb b/app/controllers/event/top_controller.rb
new file mode 100644
index 0000000000..8cca2fccfb
--- /dev/null
+++ b/app/controllers/event/top_controller.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+# Handles a top-level event route (/event/:id)
+class Event::TopController < ApplicationController
+
+ before_action :authorize_action
+
+ def show
+ redirect_to_group_event
+ end
+
+ private
+
+ def entry
+ @event ||= Event.find(params[:id])
+ end
+
+ def redirect_to_group_event
+ flash.keep if html_request?
+ redirect_to group_event_path(entry.groups.first,
+ entry,
+ format: request.format.to_sym,
+ user_email: params[:user_email],
+ user_token: params[:user_token])
+ end
+
+ def authorize_action
+ authorize!(:show, entry)
+ end
+
+end
diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb
index d020bb9a94..07995d70b1 100644
--- a/app/controllers/events_controller.rb
+++ b/app/controllers/events_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -12,14 +12,20 @@ class EventsController < CrudController
# Respective event attrs are added in corresponding instance method.
self.permitted_attrs = [:signature, :signature_confirmation, :signature_confirmation_text,
+ :display_booking_info,
group_ids: [],
- dates_attributes: [:id, :label, :location, :start_at, :start_at_date,
- :start_at_hour, :start_at_min, :finish_at,
- :finish_at_date, :finish_at_hour, :finish_at_min,
- :_destroy],
- questions_attributes: [:id, :question, :choices, :multiple_choices,
- :required,
- :_destroy]]
+ dates_attributes: [
+ :id, :label, :location, :start_at, :start_at_date,
+ :start_at_hour, :start_at_min, :finish_at,
+ :finish_at_date, :finish_at_hour, :finish_at_min,
+ :_destroy
+ ],
+ application_questions_attributes: [
+ :id, :question, :choices, :multiple_choices, :required, :_destroy
+ ],
+ admin_questions_attributes: [
+ :id, :question, :choices, :multiple_choices, :_destroy
+ ]]
self.remember_params += [:year]
@@ -30,14 +36,23 @@ class EventsController < CrudController
# load group before authorization
prepend_before_action :parent
+ before_render_show :load_user_participation
before_render_form :load_sister_groups
before_render_form :load_kinds
def index
respond_to do |format|
- format.html { entries }
- format.csv { render_csv(entries) }
- format.xlsx { render_xlsx(entries) }
+ format.html { entries }
+ format.csv { render_tabular_in_background(:csv) && redirect_to(action: :index) }
+ format.xlsx { render_tabular_in_background(:xlsx) && redirect_to(action: :index) }
+ format.ics { render_ical(entries) }
+ end
+ end
+
+ def show
+ respond_to do |format|
+ format.html { entry }
+ format.ics { render_ical([entry]) }
end
end
@@ -61,11 +76,15 @@ def list_entries
private
def build_entry
- type = model_params && model_params[:type].presence
- type ||= Event.sti_name
- event = Event.find_event_type!(type).new
- event.groups << parent
- event
+ if params[:source_id]
+ group.events.find(params[:source_id]).duplicate
+ else
+ type = model_params && model_params[:type].presence
+ type ||= Event.sti_name
+ event = Event.find_event_type!(type).new
+ event.groups << parent
+ event
+ end
end
def permitted_params
@@ -97,12 +116,19 @@ def load_kinds
end
end
- def render_csv(entries)
- send_data ::Export::Csv::Events::List.export(entries), type: :csv
+ def load_user_participation
+ if current_user
+ @user_participation = current_user.event_participations.where(event_id: entry.id).first
+ end
+ end
+
+ def render_tabular_in_background(format)
+ Export::EventsExportJob.new(format, current_person.id, params[:type], year, parent).enqueue!
+ flash[:notice] = translate(:export_enqueued, email: current_person.email)
end
- def render_xlsx(entries)
- send_data ::Export::Xlsx::Events::List.export(entries), type: :xlsx
+ def render_ical(entries)
+ send_data ::Export::Ics::Events.new.generate(entries), type: :ics, disposition: :inline
end
def typed_group_events_path(group, event_type, options = {})
@@ -125,4 +151,25 @@ def export?
format = request.format
format.xlsx? || format.csv?
end
+
+ def assign_attributes
+ assign_contact_attrs
+ super
+ end
+
+ def assign_contact_attrs
+ contact_attrs = model_params.delete(:contact_attrs)
+ return unless contact_attrs.present?
+ reset_contact_attrs
+ contact_attrs.each do |a, v|
+ entry.required_contact_attrs << a if v.to_sym == :required
+ entry.hidden_contact_attrs << a if v.to_sym == :hidden
+ end
+ end
+
+ def reset_contact_attrs
+ entry.required_contact_attrs = []
+ entry.hidden_contact_attrs = []
+ end
+
end
diff --git a/app/controllers/full_text_controller.rb b/app/controllers/full_text_controller.rb
index a5d43bedb5..2634146d69 100644
--- a/app/controllers/full_text_controller.rb
+++ b/app/controllers/full_text_controller.rb
@@ -14,91 +14,48 @@ class FullTextController < ApplicationController
respond_to :html
def index
- @people = if params[:q].to_s.size >= 2
- PaginatingDecorator.decorate(list_people)
- else
- Kaminari.paginate_array([]).page(1)
- end
- respond_with(@people)
+ @people = with_query { search_strategy.list_people }
+ @groups = with_query { search_strategy.query_groups }
+ @events = with_query { search_strategy.query_events }
end
def query
- people = query_people.collect { |i| PersonDecorator.new(i).as_quicksearch }
- groups = query_groups.collect { |i| GroupDecorator.new(i).as_quicksearch }
+ people = search_strategy.query_people.collect { |i| PersonDecorator.new(i).as_quicksearch }
+ groups = search_strategy.query_groups.collect { |i| GroupDecorator.new(i).as_quicksearch }
+ events = search_strategy.query_events.collect { |i| EventDecorator.new(i).as_quicksearch }
- render json: result_with_separator(people, groups)
+ render json: results_with_separator(people, groups, events) || []
end
private
- def list_people
- return Person.none.page(1) unless params[:q].present?
- query_accessible_people do |ids|
- entries = Person.search(Riddle::Query.escape(params[:q]),
- page: params[:page],
- order: 'last_name asc, ' \
- 'first_name asc, ' \
- "#{ThinkingSphinx::SphinxQL.weight[:select]} desc",
- star: true,
- with: { sphinx_internal_id: ids })
- entries = Person::PreloadGroups.for(entries)
- entries = Person::PreloadPublicAccounts.for(entries)
- entries
+ def results_with_separator(*sets)
+ sets.select(&:present?).inject do |memo, set|
+ memo + [{ label: '—' * 20 }] + set
end
end
- def query_people
- return Person.none.page(1) unless params[:q].present?
- query_accessible_people do |ids|
- Person.search(Riddle::Query.escape(params[:q]),
- per_page: 10,
- star: true,
- with: { sphinx_internal_id: ids })
- end
- end
-
- def query_accessible_people
- ids = accessible_people_ids
- return Person.none.page(1) if ids.blank?
- yield ids
+ def search_strategy
+ @search_strategy ||= search_strategy_class.new(current_user, params[:q], params[:page])
end
- def query_groups
- return Person.none.page(1) unless params[:q].present?
- Group.search(Riddle::Query.escape(params[:q]),
- per_page: 10,
- star: true,
- include: :parent)
- end
-
- def accessible_people_ids
- key = "accessible_people_ids_for_#{current_user.id}"
- Rails.cache.fetch(key, expires_in: 15.minutes) do
- load_accessible_people_ids
+ def search_strategy_class
+ if sphinx?
+ SearchStrategies::Sphinx
+ else
+ SearchStrategies::Sql
end
end
- def load_accessible_people_ids
- accessible = Person.accessible_by(PersonReadables.new(current_user))
-
- # This still selects all people attributes :(
- # accessible.pluck('people.id')
-
- # rewrite query to only include id column
- sql = accessible.to_sql.gsub(/SELECT (.+) FROM /, 'SELECT DISTINCT people.id FROM ')
- result = Person.connection.execute(sql)
- result.collect { |row| row[0] }
- end
-
- def result_with_separator(people, groups)
- if people.present? && groups.present?
- people + [{ label: '—' * 20 }] + groups
- else
- people + groups
- end
+ def sphinx?
+ Hitobito::Application.sphinx_present?
end
def entries
@people
end
+
+ def with_query
+ params[:q].to_s.size >= 2 ? yield : []
+ end
end
diff --git a/app/controllers/group/deleted_people_controller.rb b/app/controllers/group/deleted_people_controller.rb
new file mode 100644
index 0000000000..8d1f0459d1
--- /dev/null
+++ b/app/controllers/group/deleted_people_controller.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Group::DeletedPeopleController < ListController
+
+ before_action :authorize_action
+
+ self.nesting = Group
+
+ decorates :people, :group
+
+ private
+
+ def group
+ parent
+ end
+
+ def model_class
+ Person
+ end
+
+ def list_entries
+ Group::DeletedPeople.deleted_for(group).
+ includes(:additional_emails, :phone_numbers).
+ order_by_name.
+ page(params[:page])
+ end
+
+ def authorize_action
+ authorize!(:index_deleted_people, group)
+ end
+end
diff --git a/app/controllers/group/person_add_requests_controller.rb b/app/controllers/group/person_add_requests_controller.rb
index 075cb68319..8252fc0441 100644
--- a/app/controllers/group/person_add_requests_controller.rb
+++ b/app/controllers/group/person_add_requests_controller.rb
@@ -36,8 +36,7 @@ def deactivate
def load_entries
Person::AddRequest.
for_layer(group).
- includes(:body,
- :person,
+ includes(:person,
requester: { roles: :group }).
merge(Person.order_by_name)
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 50ef69290a..aa4a2d633e 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -56,7 +56,7 @@ def export_subgroups
without_deleted.
order(:lft).
includes(:contact)
- csv = Export::Csv::Groups::List.export(list)
+ csv = Export::Tabular::Groups::List.csv(list)
send_data csv, type: :csv
end
@@ -105,8 +105,9 @@ def load_sub_groups(scope)
@sub_groups[label] << group
end
end
- # move this entry to the end
- @sub_groups[sub_groups_label] = @sub_groups.delete(sub_groups_label)
+ # move entry with non-layer groups to the end
+ children = @sub_groups.delete(sub_groups_label)
+ @sub_groups[sub_groups_label] = children if children
end
diff --git a/app/controllers/invoice_articles_controller.rb b/app/controllers/invoice_articles_controller.rb
new file mode 100644
index 0000000000..16a40cb85b
--- /dev/null
+++ b/app/controllers/invoice_articles_controller.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class InvoiceArticlesController < CrudController
+
+ respond_to :json, only: [:show]
+
+ self.nesting = Group
+
+ self.permitted_attrs = %i[
+ number name description category unit_cost vat_rate cost_center account
+ ]
+
+ private
+
+ def authorize_class
+ authorize!(:index_invoices, parent)
+ end
+
+end
diff --git a/app/controllers/invoice_configs_controller.rb b/app/controllers/invoice_configs_controller.rb
new file mode 100644
index 0000000000..b2d8c70a86
--- /dev/null
+++ b/app/controllers/invoice_configs_controller.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class InvoiceConfigsController < CrudController
+
+ self.nesting = Group
+ self.permitted_attrs = [:payment_information, :address, :iban, :account_number]
+
+ private
+
+ def build_entry
+ parent.invoice_config
+ end
+
+ def find_entry
+ parent.invoice_config
+ end
+
+ def path_args(_)
+ [parent, :invoice_config]
+ end
+
+end
diff --git a/app/controllers/invoice_lists_controller.rb b/app/controllers/invoice_lists_controller.rb
new file mode 100644
index 0000000000..3a13a19339
--- /dev/null
+++ b/app/controllers/invoice_lists_controller.rb
@@ -0,0 +1,126 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class InvoiceListsController < CrudController
+ self.nesting = Group
+ self.permitted_attrs = [:title,
+ :description,
+ :recipient_ids,
+ invoice_items_attributes: [
+ :name,
+ :description,
+ :unit_cost,
+ :vat_rate,
+ :count,
+ :_destroy
+ ]]
+
+ skip_authorize_resource
+ before_action :authorize
+ before_action :prepare_flash
+ respond_to :js, only: [:new]
+
+ helper_method :cancel_url
+
+ def new
+ assign_attributes
+ session[:invoice_referer] = request.referer
+ end
+
+ def create
+ assign_attributes
+ entry.recipient = parent.people.first
+ succeeded = entry.multi_create if entry.valid?
+
+ if succeeded
+ redirect_with(count: entry.recipients.size, title: entry.title)
+ session.delete :invoice_referer
+ else
+ render :new
+ end
+ end
+
+ def update
+ updated = invoices.includes(:recipient).map do |invoice|
+ alert('not_draft', invoice) && next unless invoice.draft?
+ alert('no_mail', invoice) && next if send_mail? && invoice.recipient_email.blank?
+
+ update_and_send_mail(invoice)
+ end.compact
+
+ redirect_with(count: updated.count)
+ end
+
+ # rubocop:disable Rails/SkipsModelValidations
+ def destroy
+ count = invoices.update_all(state: :cancelled, updated_at: Time.zone.now)
+ redirect_with(count: count)
+ end
+
+ def show
+ redirect_to group_invoices_path(parent)
+ end
+
+ def self.model_class
+ Invoice
+ end
+
+ private
+
+ def send_mail?
+ params[:mail] == 'true'
+ end
+
+ def update_and_send_mail(invoice)
+ invoice.update(state: send_mail? ? :sent : :issued).tap do
+ enqueue_sender_job(invoice) if send_mail?
+ end
+ end
+
+ def enqueue_sender_job(invoice)
+ Invoice::SendNotificationJob.new(invoice, current_person).enqueue!
+ end
+
+ def list_entries
+ super.includes(recipient: [:groups, :roles])
+ end
+
+ def invoices
+ parent.invoices.where(id: params[:ids].to_s.split(','))
+ end
+
+ def redirect_with(attrs)
+ i18n_prefix = "#{controller_name}.#{action_name}"
+ message = I18n.t(i18n_prefix, attrs)
+ key = attrs[:count] > 0 ? :notice : :alert
+ flash[key] << message
+ flash[key] << I18n.t("#{i18n_prefix}.background_send", attrs) if send_mail?
+ redirect_to group_invoices_path(parent)
+ end
+
+ def prepare_flash
+ flash[:notice] = []
+ flash[:alert] = []
+ end
+
+ def alert(key, invoice)
+ flash[:alert] << I18n.t(
+ "#{controller_name}.#{action_name}.error.#{key}",
+ number: invoice.sequence_number,
+ name: invoice.recipient_name
+ )
+ end
+
+ def authorize
+ authorize!(:create, parent.invoices.build)
+ end
+
+ def cancel_url
+ session[:invoice_referer] || group_invoices_path(parent)
+ end
+
+end
diff --git a/app/controllers/invoices_controller.rb b/app/controllers/invoices_controller.rb
new file mode 100644
index 0000000000..8df9f0f659
--- /dev/null
+++ b/app/controllers/invoices_controller.rb
@@ -0,0 +1,128 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class InvoicesController < CrudController
+ decorates :invoice
+
+ self.nesting = Group
+ self.sort_mappings = { recipient: Person.order_by_name_statement }
+ self.search_columns = [:title, :sequence_number, 'people.last_name', 'people.email']
+ self.permitted_attrs = [:title, :description, :state, :due_at,
+ :recipient_id, :recipient_email, :recipient_address,
+ invoice_items_attributes: [
+ :id,
+ :name,
+ :description,
+ :unit_cost,
+ :vat_rate,
+ :count,
+ :_destroy
+ ]]
+
+ def index
+ respond_to do |format|
+ format.html { super }
+ format.pdf { generate_pdf(list_entries.includes(:invoice_items)) }
+ format.csv { render_invoices_csv(list_entries.includes(:invoice_items)) }
+ end
+ end
+
+ def show
+ @invoice_items = InvoiceItemDecorator.decorate_collection(entry.invoice_items)
+ respond_to do |format|
+ format.html { render_html }
+ format.pdf { generate_pdf([entry]) }
+ format.csv { render_invoices_csv([entry]) }
+ end
+ end
+
+ def destroy
+ cancelled = run_callbacks(:destroy) { entry.update(state: :cancelled) }
+ set_failure_notice unless cancelled
+ respond_with(entry, success: cancelled, location: group_invoices_path(parent))
+ end
+
+ private
+
+ def render_html
+ if entry.remindable?
+ @reminder = entry.payment_reminders.build(reminder_attrs)
+ @reminder_valid = reminder_attrs ? @reminder.valid? : true
+
+ @payment = entry.payments.build(payment_attrs)
+ @payment_valid = payment_attrs ? @payment.valid? : true
+ end
+ end
+
+ def generate_pdf(invoices)
+ if params[:label_format_id]
+ render_labels(invoices)
+ else
+ render_invoices_pdf(invoices)
+ end
+ end
+
+ def render_invoices_csv(invoices)
+ csv = Export::Tabular::Invoices::List.csv(invoices)
+ send_data csv, type: :csv, filename: filename(:csv, invoices)
+ end
+
+ def render_invoices_pdf(invoices)
+ pdf = Export::Pdf::Invoice.render_multiple(invoices, pdf_options)
+ send_data pdf, type: :pdf, disposition: 'inline', filename: filename(:pdf, invoices)
+ end
+
+ def filename(extension, invoices)
+ if invoices.size > 1
+ "#{t('activerecord.models.invoice.other').downcase}.#{extension}"
+ else
+ invoices.first.filename(extension)
+ end
+ end
+
+ def render_labels(invoices)
+ recipients = Invoice.to_contactables(invoices)
+ pdf = Export::Pdf::Labels.new(find_and_remember_label_format).generate(recipients)
+ send_data pdf, type: :pdf, disposition: 'inline'
+ rescue Prawn::Errors::CannotFit
+ redirect_to :back, alert: t('people.pdf.cannot_fit')
+ end
+
+ def list_entries
+ scope = super.includes(recipient: [:groups, :roles]).references(:recipient).list
+ scope = scope.page(params[:page]).per(50)
+ Invoice::Filter.new(params).apply(scope)
+ end
+
+ def reminder_attrs
+ @reminder_attrs ||= flash[:payment_reminder]
+ end
+
+ def payment_attrs
+ @payment_attrs ||= flash[:payment] || { amount: entry.amount_open }
+ end
+
+ def pdf_options
+ {
+ articles: params[:articles] != 'false',
+ esr: params[:esr] != 'false'
+ }
+ end
+
+ def find_and_remember_label_format
+ LabelFormat.find(params[:label_format_id]).tap do |label_format|
+ unless current_user.last_label_format_id == label_format.id
+ current_user.update_column(:last_label_format_id, label_format.id)
+ end
+ end
+ end
+
+ def authorize_class
+ authorize!(:index_invoices, parent)
+ end
+
+end
diff --git a/app/controllers/label_format/settings_controller.rb b/app/controllers/label_format/settings_controller.rb
new file mode 100644
index 0000000000..ffa0cdc3ac
--- /dev/null
+++ b/app/controllers/label_format/settings_controller.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+# Copyright (c) 2017 Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class LabelFormat::SettingsController < ApplicationController
+
+ before_action :authorize
+
+ def update
+ current_user.update_column(:show_global_label_formats,
+ params[:show_global_label_formats].present?)
+ end
+
+ private
+
+ def authorize
+ authorize!(:update_settings, current_user)
+ end
+
+end
diff --git a/app/controllers/label_formats_controller.rb b/app/controllers/label_formats_controller.rb
index e1c278aca9..b470a36e1f 100644
--- a/app/controllers/label_formats_controller.rb
+++ b/app/controllers/label_formats_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -8,15 +8,35 @@
class LabelFormatsController < SimpleCrudController
self.permitted_attrs = [:name, :page_size, :landscape, :font_size, :width, :height,
- :padding_top, :padding_left, :count_horizontal, :count_vertical]
+ :padding_top, :padding_left, :count_horizontal, :count_vertical,
+ :nickname, :pp_post]
self.sort_mappings = { name: 'label_format_translations.name',
dimensions: %w(count_horizontal count_vertical) }
+ before_render_index :global_entries
+
private
+ def build_entry
+ super.tap do |entry|
+ entry.person_id = current_user.id unless manage_global?
+ end
+ end
+
+ def manage_global?
+ params[:global] == 'true' && can?(:manage_global, LabelFormat)
+ end
+
def list_entries
- super.list
+ super.list.where(person_id: current_user.id)
+ end
+
+ def global_entries
+ @global_entries = LabelFormat.list.where(person_id: nil)
+ if sorting?
+ @global_entries = @global_entries.reorder(sort_expression)
+ end
end
end
diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb
new file mode 100644
index 0000000000..04ec29fdfc
--- /dev/null
+++ b/app/controllers/notes_controller.rb
@@ -0,0 +1,77 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Dachverband Schweizer Jugendparlamente. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class NotesController < ApplicationController
+
+ decorates :group, :person
+
+ def index
+ authorize!(:index_notes, group)
+ @notes = entries
+ end
+
+ def create
+ @note = subject.notes.build(permitted_params.merge(author_id: current_user.id))
+ authorize!(:create, @note)
+ @note.save
+
+ respond_to do |format|
+ format.html { redirect_to subject_path }
+ format.js { group } # create.js.haml
+ end
+ end
+
+ def destroy
+ @note = Note.find(params[:id])
+ authorize!(:destroy, @note)
+ @note.destroy
+
+ respond_to do |format|
+ format.html { redirect_to subject_path }
+ format.js # destroy.js.haml
+ end
+ end
+
+ private
+
+ def entries
+ Note
+ .includes(:subject, :author)
+ .in_or_layer_below(group)
+ .list
+ .page(params[:notes_page])
+ .tap do |notes|
+ Person::PreloadGroups.for(notes.collect(&:subject).select { |s| s.is_a?(Person) })
+ Person::PreloadGroups.for(notes.collect(&:author))
+ end
+ end
+
+ def group
+ @group ||= Group.find(params[:group_id])
+ end
+
+ def subject
+ if params[:person_id]
+ Person.find(params[:person_id])
+ else
+ group
+ end
+ end
+
+ def permitted_params
+ params.require(:note).permit(:text)
+ end
+
+ def subject_path
+ if @note.subject_type == Group.name
+ group_path(id: group.id)
+ else
+ group_person_path(group_id: group.id, id: subject.id)
+ end
+ end
+
+end
diff --git a/app/controllers/payment_reminders_controller.rb b/app/controllers/payment_reminders_controller.rb
new file mode 100644
index 0000000000..064fb2f8ad
--- /dev/null
+++ b/app/controllers/payment_reminders_controller.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class PaymentRemindersController < CrudController
+ self.nesting = [Group, Invoice]
+ self.permitted_attrs = [:message, :due_at]
+
+ def create
+ assign_attributes
+
+ if entry.save
+ redirect_to(group_invoice_path(*parents), notice: flash_message(:success))
+ else
+ flash[:payment_reminder] = permitted_params.to_h
+ redirect_to(group_invoice_path(*parents))
+ end
+ end
+
+end
diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb
new file mode 100644
index 0000000000..c478b9bb52
--- /dev/null
+++ b/app/controllers/payments_controller.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class PaymentsController < CrudController
+ include FormatHelper
+ include ActionView::Helpers::NumberHelper
+
+ self.nesting = [Group, Invoice]
+ self.permitted_attrs = [:amount, :received_at]
+
+ def create
+ assign_attributes
+
+ if entry.save
+ redirect_to(group_invoice_path(*parents), notice: flash_message)
+ else
+ flash[:payment] = permitted_params.to_h
+ redirect_to(group_invoice_path(*parents))
+ end
+ end
+
+ def flash_message
+ I18n.t("#{controller_name}.#{action_name}.flash.success", amount: f(entry.amount))
+ end
+
+end
diff --git a/app/controllers/people_controller.rb b/app/controllers/people_controller.rb
old mode 100644
new mode 100755
index 57d9ff0db1..1212fb9c99
--- a/app/controllers/people_controller.rb
+++ b/app/controllers/people_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -11,7 +11,7 @@ class PeopleController < CrudController
self.nesting = Group
- self.remember_params += [:name, :kind, :role_type_ids]
+ self.remember_params += [:name, :range, :filters, :filter_id]
self.permitted_attrs = [:first_name, :last_name, :company_name, :nickname, :company,
:gender, :birthday, :additional_information,
@@ -38,11 +38,13 @@ class PeopleController < CrudController
before_render_show :load_grouped_person_tags, if: -> { html_request? }
before_render_index :load_people_add_requests, if: -> { html_request? }
- def index
+ def index # rubocop:disable Metrics/AbcSize we support a lot of format, hence many code-branches
respond_to do |format|
format.html { @people = prepare_entries(filter_entries).page(params[:page]) }
format.pdf { render_pdf(filter_entries) }
- format.csv { render_entries_csv(filter_entries) }
+ format.csv { render_tabular_entries_in_background(:csv) && redirect_to(action: :index) }
+ format.xlsx { render_tabular_entries_in_background(:xlsx) && redirect_to(action: :index) }
+ format.vcf { render_vcf(filter_entries.includes(:phone_numbers)) }
format.email { render_emails(filter_entries) }
format.json { render_entries_json(filter_entries) }
end
@@ -52,7 +54,9 @@ def show
respond_to do |format|
format.html
format.pdf { render_pdf([entry]) }
- format.csv { render_entry_csv }
+ format.csv { render_tabular_entry(:csv) }
+ format.xlsx { render_tabular_entry(:xlsx) }
+ format.vcf { render_vcf([entry]) }
format.json { render_entry_json }
end
end
@@ -72,14 +76,14 @@ def send_password_instructions
# PUT button, ajax
def primary_group
- entry.update_column :primary_group_id, params[:primary_group_id]
+ entry.update!(primary_group_id: params[:primary_group_id])
respond_to do |format|
format.html { redirect_to group_person_path(group, entry) }
format.js
end
end
- private
+ private_class_method
# dont use class level accessor as expression is evaluated whenever constant is
# loaded which might be before wagon that defines groups / roles has been loaded
@@ -88,8 +92,9 @@ def self.sort_mappings_with_indifferent_access
concat(Person.order_by_name_statement) }.with_indifferent_access
end
+ private
- alias_method :group, :parent
+ alias group parent
def find_entry
if group && group.root?
@@ -110,7 +115,7 @@ def assign_attributes
end
def load_people_add_requests
- if params[:kind].blank? && can?(:create, @group.roles.new)
+ if params[:range].blank? && can?(:create, @group.roles.new)
@person_add_requests = @group.person_add_requests.list.includes(person: :primary_group)
end
end
@@ -143,19 +148,18 @@ def set_add_request_status_notification
end
def filter_entries
- filter = list_filter
- entries = filter.filter_entries
+ @person_filter = Person::Filter::List.new(@group, current_user, list_filter_args)
+ entries = @person_filter.entries
entries = entries.reorder(sort_expression) if sorting?
- @multiple_groups = filter.multiple_groups
- @all_count = filter.all_count if html_request?
entries
end
- def list_filter
- if params[:filter] == 'qualification' && index_full_ability?
- Person::QualificationFilter.new(@group, current_user, params)
+ def list_filter_args
+ if params[:filter_id]
+ filter = PeopleFilter.for_group(group).find(params[:filter_id])
+ { name: filter.name, range: filter.range, filters: filter.filter_chain.to_params }
else
- Person::RoleFilter.new(@group, current_user, params)
+ params
end
end
@@ -167,29 +171,41 @@ def prepare_entries(entries)
end
end
- def render_entries_csv(entries)
- full = params[:details].present? && index_full_ability?
- render_csv(prepare_csv_entries(entries, full), full)
+ def render_tabular_entries_in_background(format)
+ email = current_person.email
+ if email
+ full = params[:details].present? && index_full_ability?
+ render_tabular_in_background(format, full)
+ flash[:notice] = translate(:export_enqueued, email: email)
+ else
+ flash[:alert] = translate(:export_email_needed)
+ end
end
- def prepare_csv_entries(entries, full)
+ def prepare_tabular_entries(entries, full)
if full
- entries.select('people.*').preload_accounts.includes(relations_to_tails: :tail)
+ entries
+ .select('people.*')
+ .preload_accounts
+ .includes(relations_to_tails: :tail, qualifications: { qualification_kind: :translations })
+ .includes(:primary_group)
else
- entries.preload_public_accounts
+ entries.preload_public_accounts.includes(:primary_group)
end
end
- def render_entry_csv
- render_csv([entry], params[:details].present? && can?(:show_full, entry))
+ def render_tabular_entry(format)
+ render_tabular(format, [entry], params[:details].present? && can?(:show_full, entry))
end
- def render_csv(entries, full)
- if full
- send_data Export::Csv::People::PeopleFull.export(entries), type: :csv
- else
- send_data Export::Csv::People::PeopleAddress.export(entries), type: :csv
- end
+ def render_tabular_in_background(format, full)
+ person_filter = Person::Filter::List.new(@group, current_user, list_filter_args)
+ Export::PeopleExportJob.new(format, full, current_person.id, person_filter).enqueue!
+ end
+
+ def render_tabular(format, entries, full)
+ exporter = full ? Export::Tabular::People::PeopleFull : Export::Tabular::People::PeopleAddress
+ send_data exporter.export(format, entries), type: format
end
def render_entries_json(entries)
@@ -197,7 +213,7 @@ def render_entries_json(entries)
includes(:social_accounts).
decorate,
group: @group,
- multiple_groups: @multiple_groups,
+ multiple_groups: @person_filter.multiple_groups,
serializer: PeopleSerializer,
controller: self)
end
@@ -207,7 +223,7 @@ def render_entry_json
end
def index_full_ability?
- if params[:kind].blank?
+ if params[:range].blank? || params[:range] == 'group'
can?(:index_full_people, @group)
else
can?(:index_deep_full_people, @group)
diff --git a/app/controllers/people_filters_controller.rb b/app/controllers/people_filters_controller.rb
index 7bd0f3d4d5..6793da8683 100644
--- a/app/controllers/people_filters_controller.rb
+++ b/app/controllers/people_filters_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -9,13 +9,11 @@ class PeopleFiltersController < CrudController
self.nesting = Group
- self.permitted_attrs = [:name, :role_type_ids, role_types: [], role_type_ids: []]
-
decorates :group
- hide_action :index, :show, :edit, :update
+ hide_action :index, :show
- skip_authorize_resource only: [:create, :qualification]
+ skip_authorize_resource only: [:create]
# load group before authorization
prepend_before_action :parent
@@ -24,12 +22,18 @@ class PeopleFiltersController < CrudController
helper_method :people_list_path
+ def new
+ assign_attributes
+ super
+ end
+
def create
if params[:button] == 'save'
authorize!(:create, entry)
- super(location: result_path)
+ super
else
authorize!(:new, entry)
+ assign_attributes
redirect_to result_path
end
end
@@ -38,14 +42,9 @@ def destroy
super(location: people_list_path)
end
- def qualification
- authorize!(:index_full_people, group)
- @qualification_kinds = QualificationKind.list.without_deleted
- end
-
private
- alias_method :group, :parent
+ alias group parent
def build_entry
filter = super
@@ -53,21 +52,31 @@ def build_entry
filter
end
+ def return_path
+ super || people_list_path(filter_id: entry.id)
+ end
+
def result_path
- assign_attributes
- params = {}
- if entry.role_types.present?
- params = { name: entry.name, role_type_ids: entry.role_type_ids_string, kind: :deep }
+ search_params = {}
+ if entry.filter_chain.present?
+ search_params = {
+ name: entry.name,
+ range: entry.range || 'deep',
+ filters: entry.filter_chain.to_params
+ }
end
- people_list_path(params)
+ people_list_path(search_params)
end
def compose_role_lists
@role_types = Role::TypeList.new(group.class)
+ @qualification_kinds = QualificationKind.list.without_deleted
end
- def permitted_params
- model_params ? model_params.permit(permitted_attrs) : {}
+ def assign_attributes
+ entry.name = params[:name] || (params[:people_filter] && params[:people_filter][:name])
+ entry.range = params[:range]
+ entry.filter_chain = params[:filters]
end
def people_list_path(options = {})
diff --git a/app/controllers/person/colleagues_controller.rb b/app/controllers/person/colleagues_controller.rb
new file mode 100644
index 0000000000..f00a4967e8
--- /dev/null
+++ b/app/controllers/person/colleagues_controller.rb
@@ -0,0 +1,58 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Dachverband Schweizer Jugendparlamente. This file is
+# part of hitobito and licensed under the Affero General Public License
+# version 3 or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Person::ColleaguesController < ApplicationController
+
+ before_action :authorize_action
+
+ decorates :group, :person, :colleagues
+
+ respond_to :html
+
+ def index
+ @colleagues = list_entries
+ respond_with(@colleagues)
+ end
+
+ private
+
+ def list_entries
+ return Person.none.page(1) unless person.company_name?
+
+ Person.
+ where(company_name: person.company_name).
+ preload_public_accounts.
+ preload_groups.
+ joins(:roles).
+ order_by_name.
+ distinct.
+ page(params[:page])
+ end
+
+ def person
+ @person ||= group.people.find(params[:id])
+ end
+
+ def group
+ @group ||= Group.find(params[:group_id])
+ end
+
+ def authorize_action
+ authorize!(:show, person)
+ end
+
+ def model_class
+ Person
+ end
+
+ include Sortable
+
+ self.sort_mappings = {
+ roles: [Person.order_by_role_statement].concat(Person.order_by_name_statement)
+ }
+
+end
diff --git a/app/controllers/person/company_name_controller.rb b/app/controllers/person/company_name_controller.rb
new file mode 100644
index 0000000000..e964b174ff
--- /dev/null
+++ b/app/controllers/person/company_name_controller.rb
@@ -0,0 +1,55 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Dachverband Schweizer Jugendparlamente. This file is
+# part of hitobito and licensed under the Affero General Public License
+# version 3 or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Person::CompanyNameController < ApplicationController
+
+ before_action :authorize_action
+
+ delegate :model_class, to: :class
+
+ # GET ajax, for auto complete fields, only the company_name
+ def index
+ render json: entries.map { |name| { id: name, label: name } }
+ end
+
+ private
+
+ def entries
+ if params.key?(:q) && params[:q].size >= 3
+ list_entries.
+ limit(10).
+ pluck(:company_name).
+ map(&:strip)
+ else
+ []
+ end
+ end
+
+ def list_entries
+ Person.
+ where.not(company_name: nil).
+ distinct.
+ order(:company_name)
+ end
+
+ def authorize_action
+ authorize!(:query, Person)
+ end
+
+ include Searchable
+
+ self.search_columns = [:company_name]
+
+ class << self
+
+ def model_class
+ Person
+ end
+
+ end
+
+end
diff --git a/app/controllers/person/csv_imports_controller.rb b/app/controllers/person/csv_imports_controller.rb
index 9beea40fb4..f7182b98f4 100644
--- a/app/controllers/person/csv_imports_controller.rb
+++ b/app/controllers/person/csv_imports_controller.rb
@@ -133,7 +133,8 @@ def load_can_manage_tags
def valid_file?(io)
io.present? &&
io.respond_to?(:content_type) &&
- io.content_type =~ /text\/|excel/ # windows sends csv files as application/vnd.excel
+ # windows sends csv files as application/vnd.excel, windows 10 as application/octet-stream
+ io.content_type =~ /text\/|excel|octet-stream/
end
def parse_or_redirect
@@ -180,8 +181,7 @@ def map_headers_and_import
end
def redirect_params
- filter = PeopleFilter.new(role_type_ids: [role_type.id])
- { role_type_ids: filter.role_type_ids_string, name: importer.human_role_name }
+ { filters: { role: { role_type_ids: [role_type.id] } }, name: importer.human_role_name }
end
def role_type
diff --git a/app/controllers/person/history_controller.rb b/app/controllers/person/history_controller.rb
index 87285e39e5..57a4080106 100644
--- a/app/controllers/person/history_controller.rb
+++ b/app/controllers/person/history_controller.rb
@@ -19,10 +19,10 @@ def index
private
def fetch_roles
- Role.with_deleted.
- where(person_id: entry.id).
- includes(:group).
- order('groups.name', 'roles.deleted_at')
+ Person::PreloadGroups.for([entry]).first.roles.
+ with_deleted.
+ includes(:group).
+ sort_by {|r| GroupDecorator.new(r.group).name_with_layer }
end
def fetch_participations
diff --git a/app/controllers/person/invoices_controller.rb b/app/controllers/person/invoices_controller.rb
new file mode 100644
index 0000000000..b256e81e26
--- /dev/null
+++ b/app/controllers/person/invoices_controller.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Person::InvoicesController < InvoicesController
+
+ private
+
+ def list_entries
+ scope = Invoice.
+ includes(:group, recipient: [:groups, :roles]).
+ joins(:recipient).where(recipient: person).list
+
+ scope = scope.page(params[:page]).per(50)
+ Invoice::Filter.new(params).apply(scope)
+ end
+
+ def person
+ @person ||= group.people.find(params[:id])
+ end
+
+ def group
+ @group ||= Group.find(params[:group_id])
+ end
+
+ def authorize_class
+ authorize!(:index_invoices, person)
+ end
+
+end
diff --git a/app/controllers/person/notes_controller.rb b/app/controllers/person/notes_controller.rb
deleted file mode 100644
index 3e4bc80656..0000000000
--- a/app/controllers/person/notes_controller.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
-
-class Person::NotesController < ApplicationController
-
- class_attribute :permitted_attrs
-
- authorize_resource except: :index
-
- decorates :group, :person
-
- respond_to :html
-
- self.permitted_attrs = [:text]
-
- def index
- @group = Group.find(params[:id])
- authorize!(:index_person_notes, @group)
-
- @notes = Person::Note.
- includes(:author, person: :groups).
- where(person: Person.in_layer(@group)).
- where(person: Person.in_or_below(@group)).
- page(params[:notes_page]).
- per(100)
-
- respond_with(@notes)
- end
-
- def create
- @group = Group.find(params[:group_id])
- @person = Person.find(params[:person_id])
- @note = @person.notes.create(permitted_params.merge(author_id: current_user.id))
-
- respond_to do |format|
- format.html { redirect_to group_person_path(@group, @person) }
- format.js # create.js.haml
- end
- end
-
- private
-
- def permitted_params
- params.require(:person_note).permit(permitted_attrs)
- end
-
-end
diff --git a/app/controllers/person/query_controller.rb b/app/controllers/person/query_controller.rb
index fd5fac986d..3dcc896556 100644
--- a/app/controllers/person/query_controller.rb
+++ b/app/controllers/person/query_controller.rb
@@ -9,6 +9,8 @@ class Person::QueryController < ApplicationController
before_action :authorize_action
+ delegate :model_class, to: :class
+
# GET ajax, for auto complete fields, without @group
def index
people = []
@@ -26,10 +28,6 @@ def list_entries
Person.only_public_data.order_by_name
end
- def model_class
- Person
- end
-
def authorize_action
authorize!(:query, Person)
end
@@ -38,4 +36,12 @@ def authorize_action
self.search_columns = [:first_name, :last_name, :company_name, :nickname, :town]
+ class << self
+
+ def model_class
+ Person
+ end
+
+ end
+
end
diff --git a/app/controllers/person/tags_controller.rb b/app/controllers/person/tags_controller.rb
index 47119c905a..4d641fd1ae 100644
--- a/app/controllers/person/tags_controller.rb
+++ b/app/controllers/person/tags_controller.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
+# https://github.com/hitobito/hitobito.
class Person::TagsController < ApplicationController
diff --git a/app/controllers/roles_controller.rb b/app/controllers/roles_controller.rb
index 16fe9806b1..783f77732c 100644
--- a/app/controllers/roles_controller.rb
+++ b/app/controllers/roles_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -18,8 +18,12 @@ class RolesController < CrudController
skip_authorize_resource only: [:details, :role_types]
+ define_render_callbacks :create
+
before_render_form :set_group_selection
+ before_render_create :set_group_selection
+ before_action :set_person_id, only: [:new]
before_action :remember_primary_group, only: [:destroy]
after_destroy :last_primary_group_role_deleted
@@ -77,7 +81,7 @@ def create_entry_and_person
created = with_callbacks(:create, :save) do
(entry.person.persisted? || entry.person.save) && entry.save
end
- fail ActiveRecord::Rollback unless created
+ raise ActiveRecord::Rollback unless created
end
created
end
@@ -164,8 +168,8 @@ def build_person(role)
role.person_id = person_id
role.person = Person.new unless role.person
else
- attrs = ActionController::Parameters.new(person_attrs).
- permit(*PeopleController.permitted_attrs)
+ attrs = ActionController::Parameters.new(person_attrs)
+ .permit(*PeopleController.permitted_attrs)
role.person = Person.new(attrs)
end
end
@@ -241,4 +245,8 @@ def belongs_to_persons_primary_group?(role)
role.group_id == role.person.primary_group_id
end
+ def set_person_id
+ @person_id = Role.with_deleted.find(params[:role_id]).person_id if params[:role_id]
+ end
+
end
diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb
index 8926f4d0bf..efd7932eff 100644
--- a/app/controllers/subscriptions_controller.rb
+++ b/app/controllers/subscriptions_controller.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -15,17 +15,18 @@ class SubscriptionsController < CrudController
prepend_before_action :parent
- alias_method :mailing_list, :parent
+ alias mailing_list parent
-
- def index
+ def index # rubocop:disable Metrics/MethodLength there are a lof of formats supported
respond_to do |format|
format.html do
@person_add_requests = fetch_person_add_requests
load_grouped_subscriptions
end
format.pdf { render_pdf(ordered_people) }
- format.csv { render_csv(ordered_people) }
+ format.csv { render_tabular_in_background(:csv) && redirect_to(action: :index) }
+ format.xlsx { render_tabular_in_background(:xlsx) && redirect_to(action: :index) }
+ format.vcf { render_vcf(ordered_people) }
format.email { render_emails(ordered_people) }
end
end
@@ -43,9 +44,18 @@ def ordered_people
mailing_list.people.order_by_name
end
- def render_csv(people)
- csv = Export::Csv::People::PeopleAddress.export(people)
- send_data csv, type: :csv
+ def render_tabular_in_background(format)
+ Export::SubscriptionsJob.new(format, mailing_list.id, current_person.id).enqueue!
+ flash[:notice] = translate(:export_enqueued, email: current_person.email)
+ end
+
+ def render_tabular(format, people)
+ data = Export::Tabular::People::PeopleAddress.export(format, prepare_tabular_entries(people))
+ send_data data, type: format
+ end
+
+ def prepare_tabular_entries(people)
+ people.preload_public_accounts.includes(roles: :group)
end
def group_subscriptions
diff --git a/app/decorators/application_decorator.rb b/app/decorators/application_decorator.rb
index dafdf954fb..6ce8ca2eae 100644
--- a/app/decorators/application_decorator.rb
+++ b/app/decorators/application_decorator.rb
@@ -19,7 +19,7 @@ def klass
end
def used_attributes(*attributes)
- attributes.select { |name| klass.attr_used?(name) }.map(&:to_s)
+ attributes.select { |name| model.used_attributes.include?(name) }.map(&:to_s)
end
def used?(attribute)
diff --git a/app/decorators/contactable_decorator.rb b/app/decorators/contactable_decorator.rb
index 847ae6b2fc..3b4f663717 100644
--- a/app/decorators/contactable_decorator.rb
+++ b/app/decorators/contactable_decorator.rb
@@ -52,7 +52,9 @@ def all_additional_emails(only_public = true)
end
def all_phone_numbers(only_public = true)
- nested_values(phone_numbers, only_public)
+ nested_values(phone_numbers, only_public) do |number|
+ h.link_to(number,"tel:#{number}")
+ end
end
def all_social_accounts(only_public = true)
diff --git a/app/decorators/event/participation_decorator.rb b/app/decorators/event/participation_decorator.rb
index b0efda172d..49cb18528f 100644
--- a/app/decorators/event/participation_decorator.rb
+++ b/app/decorators/event/participation_decorator.rb
@@ -13,15 +13,15 @@ class Event::ParticipationDecorator < ApplicationDecorator
decorates_association :application
delegate :to_s, :email, :primary_email, :all_emails, :all_additional_emails,
- :all_phone_numbers, :all_social_accounts, :complete_address, :town, to: :person
- delegate :qualified?, to: :qualifier
+ :all_phone_numbers, :all_social_accounts, :complete_address, :town, :layer_group_label,
+ :layer_group, to: :person
def person_additional_information
h.tag(:br) + h.muted(person.additional_name) + incomplete_label
end
def person_location_information
- [person.town, originating_group].reject(&:blank?).join(', ')
+ [layer_group, town_info].reject(&:blank?).join(' ')
end
def incomplete_label
@@ -37,46 +37,12 @@ def roles_short
end
end
- def issue_action(group)
- if qualified.nil? || !qualified?
- qualify_action_link(group, :put, :ok)
- else
- h.icon(:ok)
- end
- end
-
- def revoke_action(group)
- if qualified.nil? || qualified?
- qualify_action_link(group, :delete, :remove)
- else
- h.icon(:remove)
- end
- end
-
- def qualify_action_link(group, method, icon)
- h.link_to(h.group_event_qualification_path(group, event_id, model),
- method: method, remote: true, title: tooltips[icon]) do
- h.content_tag(:i, '', class: "icon icon-#{icon} disabled")
- end
- end
-
- def qualifier
- Event::Qualifier.for(model)
- end
-
def list_roles
safe_join(roles, h.tag(:br)) { |role| role.to_s }
end
- def originating_group
- person.primary_group
- end
-
- def tooltips
- @tooltips ||= {
- ok: translate('tooltips.ok'),
- remove: translate('tooltips.remove')
- }
+ def town_info
+ "(#{h.t('.town')}: #{person.town})" if person.town
end
end
diff --git a/app/decorators/event_decorator.rb b/app/decorators/event_decorator.rb
index 5c083ff8dc..c929557828 100644
--- a/app/decorators/event_decorator.rb
+++ b/app/decorators/event_decorator.rb
@@ -110,6 +110,16 @@ def as_typeahead
{ id: id, label: "#{model} (#{groups_label})" }
end
+ def as_quicksearch
+ { id: id, label: label_with_group, type: :event }
+ end
+
+ def label_with_group
+ label = to_s
+ label += " (#{number})" if number?
+ h.safe_join([groups.first.to_s, label], ': ')
+ end
+
private
def translate_issued_qualifications_info(qualis, prolongs, variables)
diff --git a/app/decorators/group_decorator.rb b/app/decorators/group_decorator.rb
index 291852dbb0..e233723482 100644
--- a/app/decorators/group_decorator.rb
+++ b/app/decorators/group_decorator.rb
@@ -44,6 +44,12 @@ def link_with_layer
h.safe_join(links, ' / ')
end
+ # compute layers and concat group names using a '/'
+ def name_with_layer
+ group_names = with_layer.map { |g| g.to_s }
+ group_names.join(' / ')
+ end
+
def possible_events
klass.event_types
end
diff --git a/app/decorators/invoice_decorator.rb b/app/decorators/invoice_decorator.rb
new file mode 100644
index 0000000000..982baa35e4
--- /dev/null
+++ b/app/decorators/invoice_decorator.rb
@@ -0,0 +1,38 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+
+class InvoiceDecorator < ApplicationDecorator
+ decorates :invoice
+
+ def cost
+ format_currency(calculated[:cost])
+ end
+
+ def vat
+ format_currency(calculated[:vat])
+ end
+
+ def total
+ format_currency(model.total || calculated[:total])
+ end
+
+ def amount_open
+ format_currency(model.amount_open)
+ end
+
+ def amount_paid
+ format_currency(model.amount_paid)
+ end
+
+ private
+
+ def format_currency(amount)
+ h.number_to_currency(amount, format: '%n %u')
+ end
+
+end
diff --git a/app/decorators/invoice_item_decorator.rb b/app/decorators/invoice_item_decorator.rb
new file mode 100644
index 0000000000..9dc3a776ba
--- /dev/null
+++ b/app/decorators/invoice_item_decorator.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+
+class InvoiceItemDecorator < ApplicationDecorator
+ decorates :invoice_item
+
+ def cost
+ h.number_to_currency(model.cost, format: '%n %u')
+ end
+
+ def unit_cost
+ h.number_to_currency(model.unit_cost, format: '%n %u')
+ end
+
+ def vat_rate
+ h.number_to_percentage(model.vat_rate || 0)
+ end
+
+ def total
+ h.number_to_currency(model.total, format: '%n %u')
+ end
+
+end
+
diff --git a/app/decorators/person_decorator.rb b/app/decorators/person_decorator.rb
index 7442d4ff33..a8a718463c 100644
--- a/app/decorators/person_decorator.rb
+++ b/app/decorators/person_decorator.rb
@@ -55,6 +55,11 @@ def picture_full_url
end
end
+ def layer_group_label
+ group = person.layer_group
+ h.link_to(group, h.group_path(group)) if group
+ end
+
# render a list of all roles
# if a group is given, only render the roles of this group
def roles_short(group = nil)
@@ -92,6 +97,19 @@ def relations
@relations ||= relations_to_tails.list.includes(tail: [:groups, :roles])
end
+ def last_role_new_link(group)
+ path = h.new_group_role_path(restored_group(group), role_id: last_role.id)
+ role_popover_link(path, "role_#{last_role.id}")
+ end
+
+ def last_role
+ @last_role ||= last_non_restricted_role
+ end
+
+ def restored_group(default_group)
+ last_role.group.deleted_at? ? default_group : last_role.group
+ end
+
private
def event_queries
@@ -138,12 +156,16 @@ def function_short(function, scope = nil)
end
def popover_edit_link(function)
- content_tag(:span, style: 'padding-left: 10px') do
+ path = h.edit_group_role_path(function.group, function)
+ role_popover_link(path)
+ end
+
+ def role_popover_link(path, html_id = nil)
+ content_tag(:span, style: 'padding-left: 10px', id: html_id) do
h.link_to(h.icon(:edit),
- h.edit_group_role_path(function.group, function),
+ path,
title: h.t('global.link.edit'),
remote: true)
end
end
-
end
diff --git a/app/domain/event/participant_assigner.rb b/app/domain/event/participant_assigner.rb
index 29aaa8fdee..a3526c3574 100644
--- a/app/domain/event/participant_assigner.rb
+++ b/app/domain/event/participant_assigner.rb
@@ -19,7 +19,7 @@ def initialize(event, participation, user = nil)
def createable?
participation.event.id == event.id ||
- !(participating?(event) || participating?(participation.event))
+ !(applied?(event) || participating?(participation.event))
end
def add_participant
@@ -52,12 +52,20 @@ def remove_participant
private
def participating?(event)
- event.participations.
- active.
- joins(:roles).
- where(event_roles: { type: event.participant_types.map(&:sti_name) }).
- where(person_id: participation.person_id).
- exists?
+ event.participations
+ .active # only active/assigned participations are relevant
+ .joins(:roles)
+ .where(event_roles: { type: event.participant_types.map(&:sti_name) })
+ .where(person_id: participation.person_id)
+ .exists?
+ end
+
+ def applied?(event)
+ event.participations
+ .joins(:roles)
+ .where(event_roles: { type: event.participant_types.map(&:sti_name) })
+ .where(person_id: participation.person_id)
+ .exists?
end
def create_participant_role
diff --git a/app/domain/event/participation_filter.rb b/app/domain/event/participation_filter.rb
index 40a8c145c8..8e3d541717 100644
--- a/app/domain/event/participation_filter.rb
+++ b/app/domain/event/participation_filter.rb
@@ -12,7 +12,9 @@ class Event::ParticipationFilter
class_attribute :load_entries_includes
self.load_entries_includes = [:roles, :event,
answers: [:question],
- person: [:additional_emails, :phone_numbers]]
+ person: [:additional_emails, :phone_numbers,
+ :primary_group]
+ ]
attr_reader :event, :user, :params, :counts
@@ -35,7 +37,7 @@ def predefined_filters
private
def apply_default_sort(records)
- records = records.order_by_role(event.class) if Settings.people.default_sort == 'role'
+ records = records.order_by_role(event) if Settings.people.default_sort == 'role'
records.merge(Person.order_by_name)
end
diff --git a/app/domain/event/precondition_checker.rb b/app/domain/event/precondition_checker.rb
index d6bcb3510c..2350bc12a0 100644
--- a/app/domain/event/precondition_checker.rb
+++ b/app/domain/event/precondition_checker.rb
@@ -1,20 +1,22 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-class Event::PreconditionChecker < Struct.new(:course, :person)
+class Event::PreconditionChecker
+
extend Forwardable
include Translatable
def_delegator 'course.kind', :minimum_age, :course_minimum_age
def_delegator 'errors', :empty?, :valid?
- attr_reader :errors
+ attr_reader :course, :person, :errors
- def initialize(*args)
- super
+ def initialize(course, person)
+ @course = course
+ @person = person
@errors = []
validate
end
@@ -22,11 +24,7 @@ def initialize(*args)
def validate
validate_minimum_age if course_minimum_age
- course_preconditions.each do |qualification_kind|
- unless reactivateable?(qualification_kind)
- errors << qualification_kind.label
- end
- end
+ validate_qualifications
end
def errors_text
@@ -34,6 +32,7 @@ def errors_text
if errors.present?
text << translate(:preconditions_not_fulfilled)
text << birthday_error_text if errors.delete(:birthday)
+ text << some_qualifications_error_text if errors.delete(:some_qualifications)
text << qualifications_error_text if errors.present?
end
text
@@ -45,6 +44,33 @@ def validate_minimum_age
errors << :birthday unless person.birthday && old_enough?
end
+ def validate_qualifications
+ grouped_ids = course.kind.grouped_qualification_kind_ids('precondition', 'participant')
+ if grouped_ids.size == 1
+ validate_simple_qualifications(grouped_ids.first)
+ elsif grouped_ids.size > 1
+ validate_grouped_qualifications(grouped_ids)
+ end
+ end
+
+ def validate_simple_qualifications(precondition_ids)
+ precondition_ids.each do |id|
+ errors << id unless reactivateable?(id)
+ end
+ end
+
+ def validate_grouped_qualifications(grouped_ids)
+ unless any_grouped_qualifications?(grouped_ids)
+ errors << :some_qualifications
+ end
+ end
+
+ def any_grouped_qualifications?(grouped_ids)
+ grouped_ids.any? do |ids|
+ ids.all? { |id| reactivateable?(id) }
+ end
+ end
+
def person_qualifications
@person_qualifications ||=
person.qualifications.where(qualification_kind_id: course_preconditions.map(&:id))
@@ -54,9 +80,9 @@ def course_preconditions
course.kind.qualification_kinds('precondition', 'participant')
end
- def reactivateable?(qualification_kind)
+ def reactivateable?(qualification_kind_id)
person_qualifications.
- select { |q| q.qualification_kind_id == qualification_kind.id }.
+ select { |q| q.qualification_kind_id == qualification_kind_id }.
any? { |qualification| qualification.reactivateable?(course.start_date) }
end
@@ -68,8 +94,13 @@ def birthday_error_text
translate(:below_minimum_age, course_minimum_age: course_minimum_age)
end
+ def some_qualifications_error_text
+ translate(:some_qualifications_missing)
+ end
+
def qualifications_error_text
- translate(:qualifications_missing, missing: errors.join(', '))
+ kinds = QualificationKind.includes(:translations).find(errors)
+ translate(:qualifications_missing, missing: kinds.collect(&:label).join(', '))
end
end
diff --git a/app/domain/event/qualifier.rb b/app/domain/event/qualifier.rb
index 0f3945c3d4..a5f5c0f32b 100644
--- a/app/domain/event/qualifier.rb
+++ b/app/domain/event/qualifier.rb
@@ -25,7 +25,7 @@ def leader?(participation)
attr_reader :created, :prolonged, :participation, :role
- delegate :qualified?, :person, :event, to: :participation
+ delegate :person, :event, to: :participation
delegate :qualification_date, to: :event
def initialize(participation, role)
diff --git a/app/domain/export/base.rb b/app/domain/export/base.rb
deleted file mode 100644
index 5c1a62e60b..0000000000
--- a/app/domain/export/base.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2016, insieme Schweiz. This file is part of
-# hitobito_insieme and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_insieme.
-#
-
-module Export
- # Base class for csv/xlsx export
- class Base
-
- attr_reader :list
-
- def initialize(list)
- @list = list
- end
-
- # The list of all attributes exported to the csv/xlsx.
- # overridde either this or #attribute_labels
- def attributes
- attribute_labels.keys
- end
-
- # A hash of all attributes mapped to their labels exported to the csv/xlsx.
- # overridde either this or #attributes
- def attribute_labels
- @attribute_labels ||= build_attribute_labels
- end
-
- # List of all lables.
- def labels
- attribute_labels.values
- end
-
- private
-
- def build_attribute_labels
- attributes.each_with_object({}) do |attr, labels|
- labels[attr] = attribute_label(attr)
- end
- end
-
- def attribute_label(attr)
- human_attribute(attr)
- end
-
- def human_attribute(attr)
- model_class.human_attribute_name(attr)
- end
-
- def values(entry)
- row = row_for(entry)
- attributes.collect { |attr| row.fetch(attr) }
- end
-
- def row_for(entry)
- row_class.new(entry)
- end
- end
-end
diff --git a/app/domain/export/csv/base.rb b/app/domain/export/csv/base.rb
deleted file mode 100644
index 4a70fa4c47..0000000000
--- a/app/domain/export/csv/base.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2014, Jungwacht Blauring Schweiz, Pfadibewegung Schweiz.
-# This file is part of hitobito and licensed under the Affero General Public
-# License version 3 or later. See the COPYING file at the top-level directory
-# or at https://github.com/hitobito/hitobito.
-
-
-module Export::Csv
- # The base class for all the different csv export files.
- class Base < ::Export::Base
-
- class_attribute :model_class, :row_class
- self.row_class = Row
-
- class << self
- def export(*args)
- Export::Csv::Generator.new(new(*args)).csv
- end
- end
-
- def to_csv(generator)
- generator << labels
- list.each do |entry|
- generator << values(entry)
- end
- end
- end
-end
diff --git a/app/domain/export/csv/generator.rb b/app/domain/export/csv/generator.rb
index 600220501f..ba02498130 100644
--- a/app/domain/export/csv/generator.rb
+++ b/app/domain/export/csv/generator.rb
@@ -1,30 +1,39 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
require 'csv'
-module Export
+module Export
module Csv
def self.export(exportable)
- Generator.new(exportable).csv
+ Generator.new(exportable).call
end
class Generator
- attr_reader :csv
+ attr_reader :exportable
def initialize(exportable)
- @csv = convert(generate(exportable))
+ @exportable = exportable
+ end
+
+ def call
+ convert(generate)
end
private
- def generate(exportable)
- CSV.generate(options) { |generator| exportable.to_csv(generator) }
+ def generate
+ CSV.generate(options) do |generator|
+ generator << exportable.labels
+ exportable.data_rows(:csv) do |row|
+ generator << row
+ end
+ end
end
# convert to 8859 for excel which is too stupid to handle utf-8
@@ -39,6 +48,7 @@ def convert(data)
def options
{ col_sep: Settings.csv.separator.strip }
end
+
end
end
end
diff --git a/app/domain/export/csv/people/person_row.rb b/app/domain/export/csv/people/person_row.rb
deleted file mode 100644
index 285cb04cf4..0000000000
--- a/app/domain/export/csv/people/person_row.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-
-module Export::Csv::People
- # Attributes of a person, handles associations
- class PersonRow < Export::Csv::Row
-
- self.dynamic_attributes = { /^phone_number_/ => :phone_number_attribute,
- /^social_account_/ => :social_account_attribute,
- /^additional_email_/ => :additional_email_attribute,
- /^people_relation_/ => :people_relation_attribute }
-
- def country
- entry.country_label
- end
-
- def gender
- entry.gender_label
- end
-
- def roles
- entry.roles.map { |role| "#{role} #{role.group.with_layer.join(' / ')}" }.join(', ')
- end
-
- def tags
- entry.tag_list.to_s
- end
-
- private
-
- def phone_number_attribute(attr)
- contact_account_attribute(entry.phone_numbers, attr)
- end
-
- def social_account_attribute(attr)
- contact_account_attribute(entry.social_accounts, attr)
- end
-
- def additional_email_attribute(attr)
- contact_account_attribute(entry.additional_emails, attr)
- end
-
- def people_relation_attribute(attr)
- entry.relations_to_tails.
- select { |r| :"people_relation_#{r.kind}" == attr }.
- map { |r| r.tail.to_s }.
- join(', ')
- end
-
- def contact_account_attribute(accounts, attr)
- account = accounts.find { |e| ContactAccounts.key(e.class, e.translated_label) == attr }
- account.value if account
- end
-
- end
-end
diff --git a/app/domain/export/ics/events.rb b/app/domain/export/ics/events.rb
new file mode 100644
index 0000000000..2d40b4b0fb
--- /dev/null
+++ b/app/domain/export/ics/events.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Ics
+ class Events
+
+ def generate(events)
+ ical = Icalendar::Calendar.new
+ ical_events = events.map { |event| generate_ical_events(event) }.flatten
+ ical_events.each { |event| ical.add_event(event) }
+ ical.to_ical
+ end
+
+ def generate_ical_events(event)
+ event.dates.map do |event_date|
+ Icalendar::Event.new.tap do |ical_event|
+ ical_event.dtstart = event_date.start_at
+ ical_event.dtend = event_date.finish_at
+ ical_event.summary = "#{event.name}: #{event_date.label}"
+ ical_event.location = event_date.location || event.location
+ ical_event.description = event.description
+ ical_event.contact = event.contact
+ end
+ end
+ end
+ end
+end
diff --git a/app/domain/export/pdf/invoice.rb b/app/domain/export/pdf/invoice.rb
new file mode 100644
index 0000000000..aa2d246782
--- /dev/null
+++ b/app/domain/export/pdf/invoice.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Pdf
+ module Invoice
+
+ class Runner
+ def render(invoices, options)
+ pdf = Prawn::Document.new(page_size: 'A4',
+ page_layout: :portrait,
+ margin: 2.cm)
+ customize(pdf)
+ invoices.each do |invoice|
+ invoice_page(pdf, invoice, options)
+ pdf.start_new_page unless invoice == invoices.last
+ end
+ pdf.render
+ end
+
+ private
+
+ def invoice_page(pdf, invoice, options)
+ if options[:articles]
+ sections.each { |section| section.new(pdf, invoice).render }
+ end
+ Esr.new(pdf, invoice).render if options[:esr]
+ end
+
+ def customize(pdf)
+ pdf.font_size 10
+ pdf.font 'Helvetica'
+ pdf
+ end
+
+ def sections
+ [Header, InvoiceInformation, ReceiverAddress, Articles, InvoiceText]
+ end
+ end
+
+ mattr_accessor :runner
+
+ self.runner = Runner
+
+ def self.render(invoice, options)
+ runner.new.render([invoice], options)
+ end
+
+ def self.render_multiple(invoices, options)
+ runner.new.render(invoices, options)
+ end
+ end
+end
diff --git a/app/domain/export/pdf/invoice/articles.rb b/app/domain/export/pdf/invoice/articles.rb
new file mode 100644
index 0000000000..138acb817a
--- /dev/null
+++ b/app/domain/export/pdf/invoice/articles.rb
@@ -0,0 +1,81 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Pdf::Invoice
+ class Articles < Section
+
+ def render
+ bounding_box([0, 510], width: bounds.width) do
+ font_size(12) { text invoice.title }
+ pdf.move_down 10
+ pdf.font_size(8) do
+ articles_table
+ end
+ end
+ total_box
+ end
+
+ private
+
+ def articles_table
+ table articles, header: true, column_widths: { 0 => 290, 1 => 50, 2 => 60, 3 => 80 },
+ cell_style: { borders: [:bottom],
+ border_color: 'CCCCCC',
+ border_width: 0.5,
+ padding: [2, 0, 2, 0],
+ inline_format: true } do
+
+ style(row(0), align: :center, font_style: :bold)
+ style(column(0), align: :left)
+ style(columns(1..3), align: :right)
+ end
+ end
+
+
+ def articles
+ [
+ [I18n.t('activerecord.models.invoice_article.one'),
+ I18n.t('activerecord.attributes.invoice_item.count'),
+ I18n.t('activerecord.attributes.invoice_item.unit_cost'),
+ I18n.t('activerecord.attributes.invoice_item.cost')]
+ ] + article_data
+ end
+
+ def article_data
+ invoice_items.collect do |it|
+ [
+ "#{it.name} \n#{it.description}",
+ it.count,
+ helper.number_to_currency(it.unit_cost, unit: ''),
+ helper.number_to_currency(it.cost, unit: '')
+ ]
+ end
+ end
+
+ def total_box
+ bounding_box([0, cursor], width: bounds.width) do
+ font_size(10) do
+ table total_data, position: :right, cell_style: { borders: [:bottom],
+ border_color: 'CCCCCC',
+ border_width: 0.5 } do
+ style(row(1).column(0), size: 8)
+ style(column(1), align: :right)
+ end
+ end
+ end
+ end
+
+ def total_data
+ [
+ [I18n.t('invoices.pdf.total'),
+ helper.number_to_currency(invoice.calculated[:total], format: '%n %u')],
+ [I18n.t('invoices.pdf.total_vat'),
+ helper.number_to_currency(invoice.calculated[:vat], format: '%n %u')]
+ ]
+ end
+ end
+end
diff --git a/app/domain/export/pdf/invoice/esr.rb b/app/domain/export/pdf/invoice/esr.rb
new file mode 100644
index 0000000000..0082dd1d02
--- /dev/null
+++ b/app/domain/export/pdf/invoice/esr.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Pdf::Invoice
+ class Esr < Section
+
+ def render
+ #image "#{Prawn::DATADIR}/images/esr.png", at: [-60, 248], width: 602
+ invoice_address
+ account_number
+ price
+ esr_number
+ receiver_address
+ end
+
+ private
+
+ def invoice_address
+ [-48, 125].each do |x|
+ bounding_box([x, 210], width: 150, height: 80) do
+ text invoice.address
+ end
+ end
+ end
+
+ def account_number
+ [20, 193].each do |x|
+ bounding_box([x, 122], width: 90) do
+ pdf.font('Courier', size: 12) { text invoice.account_number }
+ end
+ end
+ end
+
+ def price
+ [-50, 123].each do |x|
+ bounding_box([x, 96], width: 145) do
+ pdf.font('Courier', size: 12) do
+ text helper.number_to_currency(invoice.calculated[:total],
+ format: '%n',
+ separator: ' '), align: :right
+ end
+ end
+ end
+ end
+
+ def esr_number
+ bounding_box([300, 146], width: 220) do
+ pdf.font('Courier', size: 12) do
+ text invoice.esr_number
+ end
+ end
+ end
+
+ def receiver_address
+ [[300, 100], [-48, 70]].each do |width, height|
+ bounding_box([width, height], width: 150, height: 80) do
+ address_table
+ end
+ end
+ end
+
+ def address_table
+ return unless address.present?
+ address_data = [address.split(/\n/)]
+
+ table(address_data, cell_style: { borders: [], padding: [0, 0, 0, 0] })
+ end
+ end
+end
diff --git a/app/domain/export/pdf/invoice/header.rb b/app/domain/export/pdf/invoice/header.rb
new file mode 100644
index 0000000000..6987ee6d3f
--- /dev/null
+++ b/app/domain/export/pdf/invoice/header.rb
@@ -0,0 +1,17 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Pdf::Invoice
+ class Header < Section
+
+ def render
+ bounding_box([0, cursor + 30], width: bounds.width, height: 40) do
+ text invoice.address
+ end
+ end
+ end
+end
diff --git a/app/domain/export/pdf/invoice/invoice_information.rb b/app/domain/export/pdf/invoice/invoice_information.rb
new file mode 100644
index 0000000000..14a5fac8c4
--- /dev/null
+++ b/app/domain/export/pdf/invoice/invoice_information.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Pdf::Invoice
+ class InvoiceInformation < Section
+
+ def render
+ bounding_box([0, 640], width: bounds.width, height: 80) do
+ table(information, cell_style: { borders: [], padding: [1, 20, 0, 0] })
+ end
+ end
+
+ private
+
+ def information
+ [
+ [I18n.t('invoices.pdf.invoice_number') + ':',
+ invoice.sequence_number],
+ [I18n.t('invoices.pdf.invoice_date') + ':',
+ (I18n.l(invoice.sent_at) if invoice.sent_at)],
+ [I18n.t('invoices.pdf.due_at') + ':',
+ (I18n.l(invoice.due_at) if invoice.due_at)]
+ ]
+ end
+ end
+end
diff --git a/app/domain/export/pdf/invoice/invoice_text.rb b/app/domain/export/pdf/invoice/invoice_text.rb
new file mode 100644
index 0000000000..26cfde0c92
--- /dev/null
+++ b/app/domain/export/pdf/invoice/invoice_text.rb
@@ -0,0 +1,17 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Pdf::Invoice
+ class InvoiceText < Section
+
+ def render
+ bounding_box([0, cursor - 20], width: bounds.width, height: 40) do
+ text invoice.description
+ end
+ end
+ end
+end
diff --git a/app/domain/export/pdf/invoice/receiver_address.rb b/app/domain/export/pdf/invoice/receiver_address.rb
new file mode 100644
index 0000000000..868fb59480
--- /dev/null
+++ b/app/domain/export/pdf/invoice/receiver_address.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Pdf::Invoice
+ class ReceiverAddress < Section
+
+ def render
+ float do
+ bounding_box([290, 640], width: bounds.width, height: 80) do
+ receiver_address_table
+ end
+ end
+ end
+
+ private
+
+ def receiver_address_table
+ if recipient
+ receiver_address = receiver_address_data
+ else
+ return if recipient_address.blank?
+ receiver_address = [recipient_address.split(/\n/)]
+ end
+
+ table(receiver_address, cell_style: { borders: [], padding: [0, 0, 0, 0] })
+ end
+
+ def receiver_address_data
+ [
+ [recipient.full_name],
+ [recipient.address],
+ ["#{recipient.zip_code} #{recipient.town}"],
+ [Countries.label(recipient.country)]
+ ]
+ end
+ end
+end
diff --git a/app/domain/export/pdf/invoice/section.rb b/app/domain/export/pdf/invoice/section.rb
new file mode 100644
index 0000000000..b0305ba981
--- /dev/null
+++ b/app/domain/export/pdf/invoice/section.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Pdf::Invoice
+ class Section
+
+ attr_reader :pdf, :invoice
+
+ class_attribute :model_class
+
+ delegate :bounds, :bounding_box, :table,
+ :text, :cursor, :font_size, :text_box,
+ :fill_and_stroke_rectangle, :fill_color,
+ :image, :group, :move_cursor_to, :float,
+ to: :pdf
+
+ delegate :recipient, :invoice_items, :recipient_address, :address, to: :invoice
+
+ def initialize(pdf, invoice)
+ @pdf = pdf
+ @invoice = invoice
+ end
+
+ private
+
+ def helper
+ @helper ||= Class.new do
+ include ActionView::Helpers::NumberHelper
+ end.new
+ end
+ end
+end
diff --git a/app/domain/export/pdf/labels.rb b/app/domain/export/pdf/labels.rb
index 227f98513c..3f868b9507 100644
--- a/app/domain/export/pdf/labels.rb
+++ b/app/domain/export/pdf/labels.rb
@@ -34,21 +34,32 @@ def print_address_in_bounding_box(pdf, address, pos)
pdf.bounding_box(pos,
width: format.width.mm - min_border,
height: format.height.mm - min_border) do
+ left = format.padding_left.mm
+ top = format.height.mm - format.padding_top.mm - min_border
# pdf.stroke_bounds
- pdf.text_box(address, at: [format.padding_left.mm,
- format.height.mm - format.padding_top.mm - min_border])
+ print_address_with_pp_post(pdf, address, left, top)
end
end
# print without line wrap
def print_address(pdf, address, pos)
- pdf.text_box(address, at: [pos.first + format.padding_left.mm,
- pos.last - format.padding_top.mm])
+ left = pos.first + format.padding_left.mm
+ top = pos.last - format.padding_top.mm
+ print_address_with_pp_post(pdf, address, left, top)
+ end
+
+ def print_address_with_pp_post(pdf, address, left, top)
+ if format.pp_post?
+ print_pp_post(pdf, [left, top])
+ top -= 7.mm
+ end
+ pdf.text_box(address, at: [left, top])
end
def address(contactable)
address = ''
address << contactable.company_name << "\n" if print_company?(contactable)
+ address << contactable.nickname << "\n" if print_nickname?(contactable)
address << contactable.full_name << "\n" if contactable.full_name.present?
address << contactable.address.to_s
address << "\n" unless contactable.address =~ /\n\s*$/
@@ -73,6 +84,17 @@ def print_company?(contactable)
contactable.respond_to?(:company) && contactable.company_name?
end
+ def print_nickname?(contactable)
+ format.nickname? && contactable.respond_to?(:nickname) && contactable.nickname.present?
+ end
+
+ def print_pp_post(pdf, at)
+ pdf.text_box("P.P. " \
+ "#{format.pp_post} Post CH AG ",
+ inline_format: true,
+ at: at)
+ end
+
def min_border
Settings.pdf.labels.min_border.to_i.mm
end
diff --git a/app/domain/export/pdf/participation/confirmation.rb b/app/domain/export/pdf/participation/confirmation.rb
index ad0f40083e..53175e636b 100644
--- a/app/domain/export/pdf/participation/confirmation.rb
+++ b/app/domain/export/pdf/participation/confirmation.rb
@@ -36,10 +36,7 @@ def render_contact_address
pdf.bounding_box([10, cursor], width: bounds.width) do
text(I18n.t('contactable.address_or_email',
- address: [contact.to_s,
- contact.address,
- contact.zip_code,
- contact.town].join(', '),
+ address: contact_address,
email: contact.email))
end
move_down_line
@@ -72,6 +69,16 @@ def location_and_date
Event::Date.model_name.human].join(' / ')
end
+ def contact_address
+ [contact.company_name,
+ contact.full_name,
+ contact.address.present? && contact.address.split("\n"),
+ "#{contact.zip_code} #{contact.town}".strip]
+ .flatten
+ .select { |v| v.present? }
+ .join(', ')
+ end
+
def label_with_dots(content)
text content
move_down_line
diff --git a/app/domain/export/pdf/participation/event_details.rb b/app/domain/export/pdf/participation/event_details.rb
index b093bcee97..a1cf6ea84d 100644
--- a/app/domain/export/pdf/participation/event_details.rb
+++ b/app/domain/export/pdf/participation/event_details.rb
@@ -39,16 +39,27 @@ def render_requirements
end
if course?
- boxed_attr(event_kind, :minimum_age) { translated_minimum_age }
- boxed_attr(event_kind, :qualification_kinds,
- human_attribute_name(:preconditions, event_kind),
- %w(precondition participant))
+ boxed_attr(human_attribute_name(:minimum_age, event_kind)) do
+ translated_minimum_age
+ end
+ boxed_attr(human_attribute_name(:preconditions, event_kind)) do
+ precondition_qualifications_summary
+ end
end
end
end
def translated_minimum_age
- I18n.t('qualifications.in_years', years: event_kind.minimum_age)
+ I18n.t('qualifications.in_years', years: event_kind.minimum_age) if event_kind.minimum_age
+ end
+
+ def precondition_qualifications_summary
+ kinds = event_kind.qualification_kinds('precondition', 'participant').group_by(&:id)
+ grouped_ids = event_kind.grouped_qualification_kind_ids('precondition', 'participant')
+ sentences = grouped_ids.collect do |ids|
+ ids.collect { |id| kinds[id].first.to_s }.sort.to_sentence
+ end
+ sentences.join(' ' + I18n.t('event.kinds.qualifications.or').upcase + ' ')
end
def description_title
@@ -70,14 +81,10 @@ def requirements?
event_kind.qualification_kinds('precondition', 'participant')].any?(&:present?)
end
- def boxed_attr(model, attr, title = nil, args = nil)
- title ||= human_attribute_name(attr, model)
-
- values = Array(model.send(attr, *args)).reject(&:blank?)
- values_text = block_given? ? yield : values.map(&:to_s).join("\n")
-
- if values.present?
- render_columns(-> { text title }, -> { text values_text })
+ def boxed_attr(title)
+ text = yield
+ if text.present?
+ render_columns(-> { text title }, -> { text text })
end
end
diff --git a/app/domain/export/pdf/participation/section.rb b/app/domain/export/pdf/participation/section.rb
index 76bd697ce6..b0184df405 100644
--- a/app/domain/export/pdf/participation/section.rb
+++ b/app/domain/export/pdf/participation/section.rb
@@ -103,7 +103,7 @@ def human_attribute_name(attr, model)
end
def event_with_kind?
- event.class.used_attributes.include?(:kind_id)
+ event.used_attributes.include?(:kind_id)
end
def i18n_event_postfix
diff --git a/app/domain/export/pdf/participation/specifics.rb b/app/domain/export/pdf/participation/specifics.rb
index 7121b299c8..7b8389e471 100644
--- a/app/domain/export/pdf/participation/specifics.rb
+++ b/app/domain/export/pdf/participation/specifics.rb
@@ -12,7 +12,7 @@ def render
data = answers.map { |a| [a.question.question, a.answer] }
if data.present?
- with_header(I18n.t('event.participations.specific_information')) do
+ with_header(I18n.t('event.participations.application_answers')) do
table(data, cell_style: { border_width: 0, padding: 2 })
end
end
@@ -25,7 +25,10 @@ def render
private
def answers
- participation.answers
+ participation.answers.
+ joins(:question).
+ includes(:question).
+ where(event_questions: { admin: false })
end
def additional_information_label
diff --git a/app/domain/export/tabular/base.rb b/app/domain/export/tabular/base.rb
new file mode 100644
index 0000000000..fdb87be8e0
--- /dev/null
+++ b/app/domain/export/tabular/base.rb
@@ -0,0 +1,102 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, insieme Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+#
+
+module Export::Tabular
+ # Base class for csv/xlsx export
+ class Base
+
+ class_attribute :model_class, :row_class, :auto_filter
+ self.row_class = Export::Tabular::Row
+ self.auto_filter = true
+
+ attr_reader :list
+
+ class << self
+ def export(format, *args)
+ generator(format).new(new(*args)).call
+ end
+
+ def xlsx(*args)
+ export(:xlsx, *args)
+ end
+
+ def csv(*args)
+ export(:csv, *args)
+ end
+
+ private
+
+ def generator(format)
+ case format
+ when :csv then Export::Csv::Generator
+ when :xlsx then Export::Xlsx::Generator
+ else raise ArgumentError, "Invalid format #{format}"
+ end
+ end
+ end
+
+ def initialize(list)
+ @list = list
+ end
+
+ # The list of all attributes exported to the csv/xlsx.
+ # overridde either this or #attribute_labels
+ def attributes
+ attribute_labels.keys
+ end
+
+ # A hash of all attributes mapped to their labels exported to the csv/xlsx.
+ # overridde either this or #attributes
+ def attribute_labels
+ @attribute_labels ||= build_attribute_labels
+ end
+
+ # List of all lables.
+ def labels
+ attribute_labels.values
+ end
+
+ def header_rows
+ @header_rows ||= []
+ end
+
+ def data_rows(format = nil)
+ return enum_for(:data_rows) unless block_given?
+
+ list.each do |entry|
+ yield values(entry, format)
+ end
+ end
+
+ private
+
+ def build_attribute_labels
+ attributes.each_with_object({}) do |attr, labels|
+ labels[attr] = attribute_label(attr)
+ end
+ end
+
+ def attribute_label(attr)
+ human_attribute(attr)
+ end
+
+ def human_attribute(attr)
+ model_class.human_attribute_name(attr)
+ end
+
+ def values(entry, format = nil)
+ row = row_for(entry, format)
+ attributes.collect { |attr| row.fetch(attr) }
+ end
+
+ def row_for(entry, format = nil)
+ row_class.new(entry, format)
+ end
+
+ end
+end
diff --git a/app/domain/export/csv/events/list.rb b/app/domain/export/tabular/events/list.rb
similarity index 96%
rename from app/domain/export/csv/events/list.rb
rename to app/domain/export/tabular/events/list.rb
index 5e47137d0e..1c0d35175d 100644
--- a/app/domain/export/csv/events/list.rb
+++ b/app/domain/export/tabular/events/list.rb
@@ -5,13 +5,14 @@
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-module Export::Csv::Events
- class List < Export::Csv::Base
+module Export::Tabular::Events
+ class List < Export::Tabular::Base
+
include Translatable
MAX_DATES = 3
- self.row_class = Export::Csv::Events::Row
+ self.row_class = Export::Tabular::Events::Row
private
diff --git a/app/domain/export/csv/events/row.rb b/app/domain/export/tabular/events/row.rb
similarity index 96%
rename from app/domain/export/csv/events/row.rb
rename to app/domain/export/tabular/events/row.rb
index 30a1e2823f..8be75e2701 100644
--- a/app/domain/export/csv/events/row.rb
+++ b/app/domain/export/tabular/events/row.rb
@@ -5,8 +5,8 @@
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-module Export::Csv::Events
- class Row < Export::Csv::Row
+module Export::Tabular::Events
+ class Row < Export::Tabular::Row
self.dynamic_attributes = {
/^contact_/ => :contactable_attribute,
diff --git a/app/domain/export/csv/groups/list.rb b/app/domain/export/tabular/groups/list.rb
similarity index 74%
rename from app/domain/export/csv/groups/list.rb
rename to app/domain/export/tabular/groups/list.rb
index 7b66e3c059..f36a232baf 100644
--- a/app/domain/export/csv/groups/list.rb
+++ b/app/domain/export/tabular/groups/list.rb
@@ -5,15 +5,15 @@
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-module Export::Csv::Groups
- class List < Export::Csv::Base
+module Export::Tabular::Groups
+ class List < Export::Tabular::Base
EXCLUDED_ATTRS = %w(lft rgt contact_id require_person_add_requests
created_at updated_at deleted_at
- creator_id updater_id deleter_id)
+ creator_id updater_id deleter_id).freeze
self.model_class = Group
- self.row_class = GroupRow
+ self.row_class = Export::Tabular::Groups::Row
def attributes
(model_class.column_names - EXCLUDED_ATTRS).collect(&:to_sym)
diff --git a/app/domain/export/csv/groups/group_row.rb b/app/domain/export/tabular/groups/row.rb
similarity index 60%
rename from app/domain/export/csv/groups/group_row.rb
rename to app/domain/export/tabular/groups/row.rb
index 0fa6670dc1..7d4df7abaa 100644
--- a/app/domain/export/csv/groups/group_row.rb
+++ b/app/domain/export/tabular/groups/row.rb
@@ -1,12 +1,12 @@
# encoding: utf-8
# Copyright (c) 2012-2017, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
+# https://github.com/hitobito/hitobito.
-module Export::Csv::Groups
- class GroupRow < Export::Csv::Row
+module Export::Tabular::Groups
+ class Row < Export::Tabular::Row
def type
entry.class.label
@@ -18,4 +18,3 @@ def country
end
end
-
diff --git a/app/domain/export/tabular/invoices/list.rb b/app/domain/export/tabular/invoices/list.rb
new file mode 100644
index 0000000000..931391d919
--- /dev/null
+++ b/app/domain/export/tabular/invoices/list.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Tabular::Invoices
+ class List < Export::Tabular::Base
+
+ INCLUDED_ATTRS = %w(title sequence_number state esr_number description
+ recipient_email recipient_address sent_at due_at
+ cost vat total amount_paid)
+
+ self.model_class = Invoice
+ self.row_class = Export::Tabular::Invoices::Row
+
+ def attributes
+ (INCLUDED_ATTRS).collect(&:to_sym)
+ end
+ end
+end
diff --git a/app/domain/export/tabular/invoices/row.rb b/app/domain/export/tabular/invoices/row.rb
new file mode 100644
index 0000000000..4beda808f9
--- /dev/null
+++ b/app/domain/export/tabular/invoices/row.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Tabular::Invoices
+ class Row < Export::Tabular::Row
+
+ def initialize(entry, format = nil)
+ @entry = InvoiceDecorator.decorate(entry)
+ @format = format
+ end
+
+ def state
+ entry.state_label
+ end
+
+ end
+end
diff --git a/app/domain/export/csv/people/contact_accounts.rb b/app/domain/export/tabular/people/contact_accounts.rb
old mode 100644
new mode 100755
similarity index 82%
rename from app/domain/export/csv/people/contact_accounts.rb
rename to app/domain/export/tabular/people/contact_accounts.rb
index f79e0dea84..d158029df6
--- a/app/domain/export/csv/people/contact_accounts.rb
+++ b/app/domain/export/tabular/people/contact_accounts.rb
@@ -1,13 +1,14 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-module Export::Csv::People
+module Export::Tabular::People
module ContactAccounts
class << self
+
def key(model, label)
:"#{model.model_name.to_s.underscore}_#{label.downcase}"
end
@@ -15,6 +16,7 @@ def key(model, label)
def human(model, label)
"#{model.model_name.human} #{label}"
end
+
end
end
end
diff --git a/app/domain/export/csv/people/participation_row.rb b/app/domain/export/tabular/people/participation_row.rb
similarity index 72%
rename from app/domain/export/csv/people/participation_row.rb
rename to app/domain/export/tabular/people/participation_row.rb
index 30af67bcbd..7281651e91 100644
--- a/app/domain/export/csv/people/participation_row.rb
+++ b/app/domain/export/tabular/people/participation_row.rb
@@ -1,29 +1,30 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-module Export::Csv::People
- class ParticipationRow < Export::Csv::People::PersonRow
- dynamic_attributes[/^question_\d+$/] = :question_attribute
+module Export::Tabular::People
+ class ParticipationRow < PersonRow
attr_reader :participation
- def initialize(participation)
+ delegate :additional_information, to: :participation, prefix: true
+
+ dynamic_attributes[/^question_\d+$/] = :question_attribute
+
+ def initialize(participation, format = nil)
@participation = participation
- super(participation.person)
+ super(participation.person, format)
end
def roles
participation.roles.map { |role| role }.join(', ')
end
- delegate :additional_information, to: :participation, prefix: true
-
def created_at
- I18n.l(participation.created_at.to_date)
+ normalize(participation.created_at.to_date)
end
def question_attribute(attr)
@@ -31,5 +32,6 @@ def question_attribute(attr)
answer = participation.answers.find { |e| e.question_id == id.to_i }
answer.try(:answer)
end
+
end
end
diff --git a/app/domain/export/csv/people/participations_address.rb b/app/domain/export/tabular/people/participations_address.rb
similarity index 64%
rename from app/domain/export/csv/people/participations_address.rb
rename to app/domain/export/tabular/people/participations_address.rb
index 2f896b918e..ff20c9cac2 100644
--- a/app/domain/export/csv/people/participations_address.rb
+++ b/app/domain/export/tabular/people/participations_address.rb
@@ -1,15 +1,14 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-module Export::Csv::People
- # handles participations
+module Export::Tabular::People
class ParticipationsAddress < PeopleAddress
- self.row_class = Export::Csv::People::ParticipationRow
+ self.row_class = ParticipationRow
def people
list.map(&:person)
diff --git a/app/domain/export/csv/people/participations_full.rb b/app/domain/export/tabular/people/participations_full.rb
similarity index 84%
rename from app/domain/export/csv/people/participations_full.rb
rename to app/domain/export/tabular/people/participations_full.rb
index 09a4f483e4..42bcff6938 100644
--- a/app/domain/export/csv/people/participations_full.rb
+++ b/app/domain/export/tabular/people/participations_full.rb
@@ -1,14 +1,14 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-module Export::Csv::People
+module Export::Tabular::People
class ParticipationsFull < PeopleFull
- self.row_class = Export::Csv::People::ParticipationRow
+ self.row_class = ParticipationRow
def build_attribute_labels
labels = super
diff --git a/app/domain/export/csv/people/people_address.rb b/app/domain/export/tabular/people/people_address.rb
old mode 100644
new mode 100755
similarity index 88%
rename from app/domain/export/csv/people/people_address.rb
rename to app/domain/export/tabular/people/people_address.rb
index 9691f302f2..744d109c8b
--- a/app/domain/export/csv/people/people_address.rb
+++ b/app/domain/export/tabular/people/people_address.rb
@@ -5,35 +5,23 @@
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-module Export::Csv::People
-
- # Attributes of people we want to include
- class PeopleAddress < Export::Csv::Base
+module Export::Tabular::People
+ class PeopleAddress < Export::Tabular::Base
self.model_class = ::Person
self.row_class = PersonRow
-
private
- def build_attribute_labels
- person_attribute_labels.merge(association_attributes)
- end
-
- def person_attribute_labels
- person_attributes.each_with_object({}) do |attr, hash|
- hash[attr] = attribute_label(attr)
- end
- end
-
def person_attributes
[:first_name, :last_name, :nickname, :company_name, :company, :email,
- :address, :zip_code, :town, :country, :gender, :birthday, :roles]
+ :address, :zip_code, :town, :country, :gender, :birthday, :layer_group, :roles]
end
def association_attributes
public_account_labels(:additional_emails, AdditionalEmail).merge(
- public_account_labels(:phone_numbers, PhoneNumber))
+ public_account_labels(:phone_numbers, PhoneNumber)
+ )
end
def public_account_labels(accounts, klass)
@@ -48,8 +36,19 @@ def account_labels(collection, model)
end
end
+ def build_attribute_labels
+ person_attribute_labels.merge(association_attributes)
+ end
+
+ def person_attribute_labels
+ person_attributes.each_with_object({}) do |attr, hash|
+ hash[attr] = attribute_label(attr)
+ end
+ end
+
def people
list
end
+
end
end
diff --git a/app/domain/export/csv/people/people_full.rb b/app/domain/export/tabular/people/people_full.rb
old mode 100644
new mode 100755
similarity index 54%
rename from app/domain/export/csv/people/people_full.rb
rename to app/domain/export/tabular/people/people_full.rb
index be8d5d6508..f597a113e1
--- a/app/domain/export/csv/people/people_full.rb
+++ b/app/domain/export/tabular/people/people_full.rb
@@ -1,26 +1,30 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-module Export::Csv::People
- # adds social_accounts and company related attributes
+module Export::Tabular::People
class PeopleFull < PeopleAddress
def person_attributes
Person.column_names.collect(&:to_sym) -
Person::INTERNAL_ATTRS -
- [:picture, :primary_group_id] +
- [:roles]
+ [:picture, :primary_group_id, :id] +
+ [:layer_group, :roles]
end
def association_attributes
account_labels(people.map(&:additional_emails).flatten, AdditionalEmail).merge(
- account_labels(people.map(&:phone_numbers).flatten, PhoneNumber)).merge(
- account_labels(people.map(&:social_accounts).flatten, SocialAccount)).merge(
- relation_kind_labels)
+ account_labels(people.map(&:phone_numbers).flatten, PhoneNumber)
+ ).merge(
+ account_labels(people.map(&:social_accounts).flatten, SocialAccount)
+ ).merge(
+ qualification_kind_labels
+ ).merge(
+ relation_kind_labels
+ )
end
def relation_kind_labels
@@ -31,5 +35,17 @@ def relation_kind_labels
end
end
end
+
+ def qualification_kind_labels
+ qualification_kinds = people.flat_map do |p|
+ p.qualifications.map { |q| q.qualification_kind.label }
+ end
+ qualification_kinds.uniq.sort.each_with_object({}) do |label, obj|
+ if label.present?
+ obj[ContactAccounts.key(QualificationKind, label)] = label
+ end
+ end
+ end
+
end
end
diff --git a/app/domain/export/tabular/people/person_row.rb b/app/domain/export/tabular/people/person_row.rb
new file mode 100755
index 0000000000..eedf975ce6
--- /dev/null
+++ b/app/domain/export/tabular/people/person_row.rb
@@ -0,0 +1,87 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Export::Tabular::People
+ class PersonRow < Export::Tabular::Row
+
+ self.dynamic_attributes = { /^phone_number_/ => :phone_number_attribute,
+ /^social_account_/ => :social_account_attribute,
+ /^additional_email_/ => :additional_email_attribute,
+ /^people_relation_/ => :people_relation_attribute,
+ /^qualification_kind_/ => :qualification_kind }
+
+ def country
+ entry.country_label
+ end
+
+ def gender
+ entry.gender_label
+ end
+
+ def roles
+ if entry.try(:role_with_layer).present?
+ entry.roles.zip(entry.role_with_layer.split(', ')).map { |arr| arr.join(' ') }.join(', ')
+ else
+ entry.roles.map { |role| "#{role} #{role.group.with_layer.join(' / ')}" }.join(', ')
+ end
+ end
+
+ def tags
+ entry.tag_list.to_s
+ end
+
+ def layer_group
+ entry.layer_group.to_s
+ end
+
+ private
+
+ def phone_number_attribute(attr)
+ contact_account_attribute(entry.phone_numbers, attr)
+ end
+
+ def social_account_attribute(attr)
+ contact_account_attribute(entry.social_accounts, attr)
+ end
+
+ def additional_email_attribute(attr)
+ contact_account_attribute(entry.additional_emails, attr)
+ end
+
+ def people_relation_attribute(attr)
+ entry.relations_to_tails.
+ select { |r| :"people_relation_#{r.kind}" == attr }.
+ map { |r| r.tail.to_s }.
+ join(', ')
+ end
+
+ def qualification_kind(attr)
+ qualification = find_qualification(attr)
+ qualification.finish_at.try(:to_s) || I18n.t('global.yes') if qualification
+ end
+
+ def find_qualification(label)
+ entry.qualifications.find do |q|
+ qualification_active?(q) &&
+ ContactAccounts.key(q.qualification_kind.class, q.qualification_kind.label) == label
+ end
+ end
+
+ def qualification_active?(q)
+ (q.start_at.blank? || q.start_at <= Time.zone.today) &&
+ (q.finish_at.blank? || q.finish_at >= Time.zone.today)
+ end
+
+ def contact_account_attribute(accounts, attr)
+ account = accounts.find do |e|
+ ContactAccounts.key(e.class, e.translated_label) == attr
+ end
+ account.value if account
+ end
+
+ end
+end
diff --git a/app/domain/export/csv/row.rb b/app/domain/export/tabular/row.rb
similarity index 73%
rename from app/domain/export/csv/row.rb
rename to app/domain/export/tabular/row.rb
index a5226eca00..1953b26639 100644
--- a/app/domain/export/csv/row.rb
+++ b/app/domain/export/tabular/row.rb
@@ -1,11 +1,11 @@
# encoding: utf-8
-# Copyright (c) 2012-2014, Jungwacht Blauring Schweiz, Pfadibewegung Schweiz.
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz, Pfadibewegung Schweiz.
# This file is part of hitobito and licensed under the Affero General Public
# License version 3 or later. See the COPYING file at the top-level directory
# or at https://github.com/hitobito/hitobito.
-module Export::Csv
+module Export::Tabular
# Decorator for a row entry.
# Attribute values may be accessed with fetch(attr).
@@ -17,10 +17,11 @@ class Row
class_attribute :dynamic_attributes
self.dynamic_attributes = {}
- attr_reader :entry
+ attr_reader :entry, :format
- def initialize(entry)
+ def initialize(entry, format = nil)
@entry = entry
+ @format = format
end
def fetch(attr)
@@ -51,11 +52,17 @@ def handle_dynamic_attribute(attr)
end
end
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
+ # rubocop:disable Metrics/PerceivedComplexity
def normalize(value)
if value == true
I18n.t('global.yes')
elsif value == false
I18n.t('global.no')
+ elsif value.is_a?(Time)
+ format == :xlsx ? value.to_s : "#{I18n.l(value.to_date)} #{I18n.l(value, format: :time)}"
+ elsif value.is_a?(Date)
+ format == :xlsx ? value.to_s : I18n.l(value)
else
value
end
diff --git a/app/domain/export/vcf/vcards.rb b/app/domain/export/vcf/vcards.rb
new file mode 100644
index 0000000000..c4314127d4
--- /dev/null
+++ b/app/domain/export/vcf/vcards.rb
@@ -0,0 +1,86 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+require 'vcard'
+
+module Export::Vcf
+ class Vcards
+
+ def generate(people)
+ vcards = []
+ people.each do |person|
+ vcards << vcard(person)
+ end
+ vcards.join
+ end
+
+
+ private
+
+ def name(card, person)
+ card.name do |n|
+ n.given = person.first_name.to_s
+ n.family = person.last_name.to_s
+ end
+ if person.nickname.present?
+ card.nickname = person.nickname
+ end
+ end
+
+ def birthday(card, person)
+ if person.birthday.present?
+ card.birthday = person.birthday
+ end
+ end
+
+ def address_empty?(person)
+ !person.address.present? && !person.town.present? &&
+ !person.zip_code.present? && !person.country.present?
+ end
+
+ def address(card, person)
+ unless address_empty?(person)
+ card.add_addr do |a|
+ a.street = person.address.to_s
+ a.locality = person.town.to_s
+ a.postalcode = person.zip_code.to_s
+ a.country = person.country.to_s
+ end
+ end
+ end
+
+ def emails(card, person)
+ if person.email.present?
+ card.add_email(person.email) { |e| e.preferred = true }
+ end
+ person.additional_emails.each do |email|
+ next unless email.public?
+ card.add_email(email.email) do |e|
+ e.location = email.label
+ e.preferred = false
+ end
+ end
+ end
+
+ def phones(card, person)
+ person.phone_numbers.each do |phone|
+ next unless phone.public?
+ card.add_tel(phone.number) { |t| t.location = phone.label }
+ end
+ end
+
+ def vcard(person)
+ Vcard::Vcard::Maker.make2 do |m|
+ name(m, person)
+ birthday(m, person)
+ address(m, person)
+ emails(m, person)
+ phones(m, person)
+ end
+ end
+ end
+end
diff --git a/app/domain/export/xlsx/base.rb b/app/domain/export/xlsx/base.rb
deleted file mode 100644
index 5d7c4e0e2b..0000000000
--- a/app/domain/export/xlsx/base.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2016, insieme Schweiz. This file is part of
-# hitobito_insieme and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_insieme.
-#
-
-module Export::Xlsx
- # The base class for all the different xlsx export files.
- class Base < ::Export::Base
-
- class_attribute :model_class, :row_class, :style_class
- self.row_class = Row
- self.style_class = Style
-
- delegate :column_widths, :style_definitions, :page_setup, :data_row_height, to: :style
-
- class << self
- def export(*args)
- Export::Xlsx::Generator.new(new(*args)).xlsx
- end
- end
-
- def header_rows
- @header_rows ||= []
- end
-
- def data_rows
- rows = []
- list.each.with_index do |entry, index|
- rows << { values: values(entry), style: row_style(index) }
- end
- rows
- end
-
- private
-
- def add_header_row(values = [], style = :default)
- header_rows << { values: values, style: style }
- end
-
- def row_style(index)
- style.row_styles[index] || style.default_style_data_rows
- end
-
- def style
- @style ||= style_class.new
- end
-
- end
-end
diff --git a/app/domain/export/xlsx/events/list.rb b/app/domain/export/xlsx/events/list.rb
deleted file mode 100644
index 95f8680ea6..0000000000
--- a/app/domain/export/xlsx/events/list.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2014, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-
-module Export::Xlsx::Events
- class List < Export::Xlsx::Base
- include Translatable
-
- MAX_DATES = 3
-
- self.row_class = Export::Xlsx::Events::Row
- self.style_class = Export::Xlsx::Events::Style
-
- private
-
- def build_attribute_labels
- {}.tap do |labels|
- add_main_labels(labels)
- add_date_labels(labels)
- add_contact_labels(labels)
- add_additional_labels(labels)
- add_count_labels(labels)
- end
- end
-
- def add_main_labels(labels)
- add_used_attribute_label(labels, :name)
- labels[:group_names] = translate(:group_names)
- add_used_attribute_label(labels, :number)
- labels[:kind] = Event::Kind.model_name.human if attr_used?(:kind_id)
- add_used_attribute_label(labels, :description)
- add_used_attribute_label(labels, :state)
- add_used_attribute_label(labels, :location)
- end
-
- def add_contact_labels(labels)
- add_prefixed_contactable_labels(labels, :contact)
- add_prefixed_contactable_labels(labels, :leader)
- end
-
- def add_additional_labels(labels)
- add_used_attribute_label(labels, :motto)
- add_used_attribute_label(labels, :cost)
- add_used_attribute_label(labels, :application_opening_at)
- add_used_attribute_label(labels, :application_closing_at)
- add_used_attribute_label(labels, :maximum_participants)
- add_used_attribute_label(labels, :external_applications)
- add_used_attribute_label(labels, :priorization)
- end
-
- def add_count_labels(labels)
- labels[:teamer_count] = human_attribute(:teamer_count)
- labels[:participant_count] = human_attribute(:participant_count)
- labels[:applicant_count] = human_attribute(:applicant_count)
- end
-
- def add_date_labels(labels)
- MAX_DATES.times.each do |i|
- prefix = translate('date', index: i + 1)
- labels[:"date_#{i}_label"] = "#{prefix} #{Event::Date.human_attribute_name(:label)}"
- labels[:"date_#{i}_location"] = "#{prefix} #{Event::Date.human_attribute_name(:location)}"
- labels[:"date_#{i}_duration"] = "#{prefix} #{translate('duration')}"
- end
- end
-
- def add_used_attribute_label(labels, attr)
- if attr_used?(attr)
- labels[attr] = human_attribute(attr)
- end
- end
-
- def attr_used?(attr)
- model_class.attr_used?(attr)
- end
-
- def add_prefixed_contactable_labels(labels, prefix)
- contactable_keys.each do |key|
- labels[:"#{prefix}_#{key}"] =
- "#{translated_prefix(prefix)} #{Person.human_attribute_name(key)}"
- end
- end
-
- def contactable_keys
- [:name, :address, :zip_code, :town, :email, :phone_numbers]
- end
-
- def translated_prefix(prefix)
- case prefix
- when :leader then Event::Role::Leader.model_name.human
- when :contact then human_attribute(:contact)
- else prefix
- end
- end
-
- def model_class
- @model_class ||= list.first ? list.first.class : ::Event::Course
- end
- end
-end
diff --git a/app/domain/export/xlsx/events/row.rb b/app/domain/export/xlsx/events/row.rb
deleted file mode 100644
index 06ddbc3e62..0000000000
--- a/app/domain/export/xlsx/events/row.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2014, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-
-module Export::Xlsx::Events
- class Row < Export::Xlsx::Row
-
- self.dynamic_attributes = {
- /^contact_/ => :contactable_attribute,
- /^leader_/ => :contactable_attribute,
- /^date_\d+_/ => :date_attribute
- }
-
- def kind
- entry.kind.try(:label)
- end
-
- def state
- if entry.possible_states.present? && entry.state
- I18n.t("activerecord.attributes.event/course.states.#{entry.state}")
- else
- entry.state
- end
- end
-
- private
-
- def date_attribute(date_attr)
- _, index, attr = date_attr.to_s.split('_', 3)
- date = entry.dates[index.to_i]
- date.try(attr).try(:to_s)
- end
-
- # only the first leader is taken into account
- def leader
- leaders = entry.role_types.select(&:leader?)
- @leader ||= entry.participations_for(*leaders).first.try(:person)
- end
-
- def contact
- entry.contact
- end
-
- def contactable_attribute(contactable_attr)
- subject, attr = contactable_attr.to_s.split('_', 2)
- contactable = send(subject)
- if contactable
- contact_attr = :"contact_#{attr}"
- if respond_to?(contact_attr, true)
- send(contact_attr, contactable)
- else
- contactable.send(attr)
- end
- end
- end
-
- def contact_name(contactable)
- contactable.to_s
- end
-
- def contact_phone_numbers(contactable)
- contactable.phone_numbers.map(&:to_s).join(', ')
- end
-
- end
-end
diff --git a/app/domain/export/xlsx/events/style.rb b/app/domain/export/xlsx/events/style.rb
deleted file mode 100644
index b91834bc8e..0000000000
--- a/app/domain/export/xlsx/events/style.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2016, insieme Schweiz. This file is part of
-# hitobito_insieme and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_insieme.
-#
-
-require 'axlsx'
-module Export::Xlsx::Events
- class Style < ::Export::Xlsx::Style
- end
-end
diff --git a/app/domain/export/xlsx/generator.rb b/app/domain/export/xlsx/generator.rb
index 9e619f36ba..56548e09d2 100644
--- a/app/domain/export/xlsx/generator.rb
+++ b/app/domain/export/xlsx/generator.rb
@@ -1,66 +1,79 @@
# encoding: utf-8
-# Copyright (c) 2012-2016, insieme Schweiz. This file is part of
-# hitobito_insieme and licensed under the Affero General Public License version 3
+# Copyright (c) 2012-2017, insieme Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_insieme.
+# https://github.com/hitobito/hitobito.
#
require 'axlsx'
+
module Export::Xlsx
def self.export(exportable)
- Generator.new(exportable).xls
+ Generator.new(exportable).call
end
class Generator
- attr_reader :xlsx
+ attr_reader :exportable, :style
def initialize(exportable)
- @xlsx = generate(exportable)
+ @exportable = exportable
+ @style = Style.for(exportable.class)
+ end
+
+ def call
+ generate
end
private
- def generate(exportable)
+ def generate
package = Axlsx::Package.new do |p|
p.workbook do |wb|
- build_sheets(wb, exportable)
+ build_sheets(wb)
end
end
package.to_stream.read
end
- def build_sheets(wb, exportable)
- load_style_definitions(wb.styles, exportable)
+ def build_sheets(wb)
+ load_style_definitions(wb.styles)
wb.add_worksheet do |sheet|
- add_header_rows(sheet, exportable)
+ add_header_rows(sheet)
+ add_attribute_label_row(sheet)
+ add_data_rows(sheet)
+ apply_column_widths(sheet)
- add_attribute_label_row(sheet, exportable)
-
- add_data_rows(sheet, exportable)
- apply_column_widths(sheet, exportable)
- sheet.page_setup.set(exportable.page_setup)
+ sheet.page_setup.set(style.page_setup)
+ add_auto_filter(sheet)
end
end
- def add_header_rows(sheet, exportable)
- exportable.header_rows.each do |r|
- sheet.add_row(r[:values], row_style(r))
+ def add_header_rows(sheet)
+ exportable.header_rows.each_with_index do |row, index|
+ sheet.add_row(row, row_style(style.header_style(index)))
end
end
- def add_attribute_label_row(sheet, exportable)
+ def add_auto_filter(sheet)
+ return unless exportable.auto_filter
+ range = "#{sheet.rows[exportable.header_rows.size].cells.first.r}:" \
+ "#{sheet.rows.last.cells.last.r}"
+ sheet.auto_filter = range
+ end
+
+ def add_attribute_label_row(sheet)
sheet.add_row(exportable.labels, style_definition(:attribute_labels))
end
- def add_data_rows(sheet, exportable)
- exportable.data_rows.each do |row|
+ def add_data_rows(sheet)
+ exportable.data_rows(:xlsx).each_with_index do |row, index|
options = {}
- options.merge!(row_style(row))
- options.merge!(data_row_height(exportable.data_row_height))
- sheet.add_row(row[:values], options)
+ options.merge!(row_style(style.row_style(index)))
+ options.merge!(data_row_height(style.data_row_height))
+ sheet.add_row(row, options)
end
end
@@ -68,12 +81,12 @@ def data_row_height(height)
height.nil? ? {} : { height: height }
end
- def apply_column_widths(sheet, exportable)
- sheet.column_widths(*exportable.column_widths)
+ def apply_column_widths(sheet)
+ sheet.column_widths(*style.column_widths)
end
- def load_style_definitions(workbook_styles, exportable)
- definitions = exportable.style_definitions
+ def load_style_definitions(workbook_styles)
+ definitions = style.style_definitions
definitions.each do |k, v|
# pass each style definition through add_style
# as recommended by axlsx
@@ -86,8 +99,7 @@ def style_definition(key)
@style_definitions[key].deep_dup
end
- def row_style(row)
- style = row[:style]
+ def row_style(style)
if style.is_a?(Array)
cell_styles(style)
else
diff --git a/app/domain/export/xlsx/row.rb b/app/domain/export/xlsx/row.rb
deleted file mode 100644
index 50df8a00bf..0000000000
--- a/app/domain/export/xlsx/row.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2016, insieme Schweiz. This file is part of
-# hitobito_insieme and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_insieme.
-#
-
-module Export::Xlsx
-
- # Decorator for a row entry.
- # Attribute values may be accessed with fetch(attr).
- # If a method named #attr is defined on the decorator class, return its value.
- # Otherwise, the attr is delegated to the entry.
- class Row
-
- # regexp for attribute names which are handled dynamically.
- class_attribute :dynamic_attributes
- self.dynamic_attributes = {}
-
- attr_reader :entry
-
- def initialize(entry)
- @entry = entry
- end
-
- def fetch(attr)
- normalize(value_for(attr))
- end
-
- private
-
- def value_for(attr)
- if dynamic_attribute?(attr.to_s)
- handle_dynamic_attribute(attr)
- elsif respond_to?(attr, true)
- send(attr)
- else
- entry.send(attr)
- end
- end
-
- def dynamic_attribute?(attr)
- dynamic_attributes.any? { |regexp, _| attr =~ regexp }
- end
-
- def handle_dynamic_attribute(attr)
- dynamic_attributes.each do |regexp, handler|
- if attr.to_s =~ regexp
- return send(handler, attr)
- end
- end
- end
-
- def normalize(value)
- if value == true
- I18n.t('global.yes')
- elsif value == false
- I18n.t('global.no')
- else
- value
- end
- end
-
- end
-end
diff --git a/app/domain/export/xlsx/style.rb b/app/domain/export/xlsx/style.rb
old mode 100644
new mode 100755
index 73c5088a86..cd9211dc6d
--- a/app/domain/export/xlsx/style.rb
+++ b/app/domain/export/xlsx/style.rb
@@ -1,16 +1,40 @@
# encoding: utf-8
# Copyright (c) 2012-2016, insieme Schweiz. This file is part of
-# hitobito_insieme and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_insieme.
+# https://github.com/hitobito/hitobito.
#
require 'axlsx'
+
module Export::Xlsx
class Style
+
+ class << self
+
+ def register(style_class, *exportables)
+ exportables.each do |e|
+ registry[e] = style_class
+ end
+ end
+
+ def for(exportable)
+ registry.fetch(exportable, self).new
+ end
+
+ private
+
+ def registry
+ @registry ||= {}
+ end
+
+ end
+
LABEL_BACKGROUND = Settings.xlsx.label_background
+
class_attribute :style_definition_labels, :data_row_height
+
# extend in subclass and add your own definitions
self.style_definition_labels = [:default, :attribute_labels, :centered]
@@ -24,7 +48,20 @@ def data_row_height
self.class.data_row_height
end
- # specify styles to apply per row or cell
+ def header_style(index)
+ header_styles[index] || :default
+ end
+
+ def row_style(index)
+ row_styles[index] || default_style_data_rows
+ end
+
+ # specify styles to apply per header row or cell
+ def header_styles
+ []
+ end
+
+ # specify styles to apply per data row or cell
def row_styles
[]
end
@@ -36,13 +73,13 @@ def column_widths
# override in subclass to define page setup
def page_setup
- {paper_size: 9, # Default A4
- fit_to_height: 1,
- orientation: :landscape }
+ { paper_size: 9, # Default A4
+ fit_to_height: 1,
+ orientation: :landscape }
end
def default_style_data_rows
- :centered
+ :default
end
private
@@ -53,11 +90,17 @@ def style_definition_labels
# style definitions
def default_style
- { style: {
- font_name: Settings.xlsx.font_name, alignment: { horizontal: :left } }
+ {
+ style: {
+ font_name: Settings.xlsx.font_name, alignment: { horizontal: :left }
+ }
}
end
+ def date_style
+ default_style.deep_merge(style: { numFmts: NUM_FMT_YYYYMMDD })
+ end
+
def attribute_labels_style
default_style.deep_merge(style: { bg_color: LABEL_BACKGROUND })
end
diff --git a/app/domain/group/deleted_people.rb b/app/domain/group/deleted_people.rb
new file mode 100644
index 0000000000..e65fc3a273
--- /dev/null
+++ b/app/domain/group/deleted_people.rb
@@ -0,0 +1,38 @@
+# encoding: utf-8
+
+# Copyright (c) 2017 Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+#
+
+class Group::DeletedPeople
+
+ class << self
+
+ def deleted_for(layer_group)
+ Person.
+ joins('INNER JOIN roles ON roles.person_id = people.id').
+ joins('INNER JOIN groups ON groups.id = roles.group_id').
+ where("NOT EXISTS (#{undeleted_roles})").
+ where("roles.deleted_at = (#{last_role_deleted})").
+ where('groups.layer_group_id = ?', layer_group.id).
+ uniq
+ end
+
+ private
+
+ def undeleted_roles
+ 'SELECT * FROM roles ' \
+ 'WHERE roles.deleted_at IS NULL ' \
+ 'AND roles.person_id = people.id'
+ end
+
+ def last_role_deleted
+ 'SELECT MAX(roles.deleted_at) FROM roles ' \
+ 'WHERE roles.person_id = people.id '
+ end
+
+ end
+
+end
diff --git a/app/domain/group/merger.rb b/app/domain/group/merger.rb
index f581d7de66..d1de8198d4 100644
--- a/app/domain/group/merger.rb
+++ b/app/domain/group/merger.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -16,14 +16,14 @@ def initialize(group1, group2, new_group_name)
end
def merge!
- fail('Cannot merge these Groups') unless group2_valid?
+ raise('Cannot merge these Groups') unless group2_valid?
::Group.transaction do
if create_new_group
update_events
copy_roles
- move_children(group1)
- move_children(group2)
+ move_children
+ move_invoices_and_articles
delete_old_groups
end
end
@@ -62,14 +62,13 @@ def update_events
end
end
- def move_children(group)
+ def move_children
children = group1.children + group2.children
children.each do |child|
child.parent_id = new_group.id
+ child.parent(true)
child.save!
- child.update_attribute(:layer_group_id, child.layer_group.id)
end
- group.children.update_all(parent_id: new_group.id)
end
def copy_roles
@@ -81,9 +80,24 @@ def copy_roles
end
end
+ def move_invoices_and_articles
+ invoices = group1.invoices + group2.invoices
+ invoices.each do |invoice|
+ invoice.group_id = new_group.id
+ invoice.save!
+ end
+
+ invoice_articles = group1.invoice_articles + group2.invoice_articles
+ invoice_articles.each do |invoice_article|
+ invoice_article.group_id = new_group.id
+ invoice_article.save!
+ end
+ end
+
def delete_old_groups
- group1.destroy
- group2.destroy
+ [group1, group2].each do |group|
+ group.reload.destroy
+ end
end
end
diff --git a/app/domain/invoice/filter.rb b/app/domain/invoice/filter.rb
new file mode 100644
index 0000000000..48c127c201
--- /dev/null
+++ b/app/domain/invoice/filter.rb
@@ -0,0 +1,42 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Invoice::Filter
+
+ attr_reader :params
+
+ def initialize(params = {})
+ @params = params
+ end
+
+ def apply(scope)
+ scope = apply_scope(scope, params[:state], Invoice::STATES)
+ scope = apply_scope(scope, params[:due_since], Invoice::DUE_SINCE)
+ scope = filter_by_ids(scope)
+ cancelled? ? scope : scope.visible
+ end
+
+ private
+
+ def apply_scope(relation, scope, valid_scopes)
+ return relation unless valid_scopes.include?(scope)
+ relation.send(scope)
+ end
+
+ def cancelled?
+ params[:state] == 'cancelled'
+ end
+
+ def filter_by_ids(relation)
+ return relation if invoice_ids.blank?
+ relation.where(id: invoice_ids)
+ end
+
+ def invoice_ids
+ @invoice_ids = params[:ids].to_s.split(',')
+ end
+end
diff --git a/app/domain/person/add_request/approver/event.rb b/app/domain/person/add_request/approver/event.rb
index 1db16a4d06..f935f02562 100644
--- a/app/domain/person/add_request/approver/event.rb
+++ b/app/domain/person/add_request/approver/event.rb
@@ -19,7 +19,7 @@ def build_entity
end
def role_type
- @role_type ||= event.class.find_role_type!(request.role_type)
+ @role_type ||= event.find_role_type!(request.role_type)
end
def event
diff --git a/app/domain/person/add_request/creator/base.rb b/app/domain/person/add_request/creator/base.rb
index 4835cb9182..d8501fdf9d 100644
--- a/app/domain/person/add_request/creator/base.rb
+++ b/app/domain/person/add_request/creator/base.rb
@@ -25,8 +25,7 @@ def handle
end
def required?
- person_layer &&
- person_layer.require_person_add_requests? &&
+ person_layer.try(:require_person_add_requests?) &&
ability.cannot?(:add_without_request, request) &&
entity.valid?
end
@@ -46,7 +45,7 @@ def request
end
def person_layer
- person && person.primary_group.try(:layer_group)
+ person && (person.primary_group.try(:layer_group) || last_layer_group)
end
def request_attrs
@@ -82,5 +81,10 @@ def body_class_name
self.class.name.demodulize
end
+ def last_layer_group
+ last_role = person.last_non_restricted_role
+ last_role && last_role.group.layer_group
+ end
+
end
end
diff --git a/app/domain/person/filter/base.rb b/app/domain/person/filter/base.rb
new file mode 100644
index 0000000000..01eb351c87
--- /dev/null
+++ b/app/domain/person/filter/base.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Person::Filter::Base
+
+ # - has not to be encoded in URLs, ',' must be and thus generate a much longer string.
+ ID_URL_SEPARATOR = '-'.freeze
+
+ class_attribute :required_ability, :permitted_args
+
+ class << self
+ def key
+ name.demodulize.underscore
+ end
+ end
+
+ attr_reader :attr, :args
+
+ def initialize(attr, args)
+ @attr = attr
+ @args = args.slice(*permitted_args)
+ end
+
+ def apply(scope)
+ scope
+ end
+
+ def blank?
+ args.blank?
+ end
+
+ # Returns a serializable, persistable representation of this filter.
+ def to_hash
+ args
+ end
+
+ # Returns a representation of this filter suitable for request url params.
+ def to_params
+ args
+ end
+
+ private
+
+ def id_list(key)
+ args[key] = args[key].to_s.split(ID_URL_SEPARATOR) unless args[key].is_a?(Array)
+ args[key].collect!(&:to_i)
+ end
+
+end
diff --git a/app/domain/person/filter/chain.rb b/app/domain/person/filter/chain.rb
new file mode 100644
index 0000000000..1f3e10044d
--- /dev/null
+++ b/app/domain/person/filter/chain.rb
@@ -0,0 +1,92 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Person::Filter::Chain
+
+ TYPES = [ # rubocop:disable Style/MutableConstant these are meant to be extended in wagons
+ Person::Filter::Role,
+ Person::Filter::Qualification
+ ]
+
+ # Used for `serialize` method in ActiveRecord
+ class << self
+ def load(yaml)
+ new(YAML.load(yaml || ''))
+ end
+
+ def dump(obj)
+ unless obj.is_a?(self)
+ raise ::ActiveRecord::SerializationTypeMismatch,
+ "Attribute was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
+ end
+
+ YAML.dump(obj.to_hash.deep_stringify_keys)
+ end
+ end
+
+ attr_reader :filters
+
+ def initialize(params)
+ @filters = parse(params)
+ end
+
+ def filter(scope)
+ filters.inject(scope) do |s, filter|
+ filter.apply(s)
+ end
+ end
+
+ def [](attr)
+ filters.find { |f| f.attr == attr.to_s }
+ end
+
+ def blank?
+ filters.blank?
+ end
+
+ def to_hash
+ # call #to_hash twice to get a regular hash (without indifferent access)
+ build_hash { |f| f.to_hash.to_hash }
+ end
+
+ def to_params
+ build_hash { |f| f.to_params }
+ end
+
+ def required_abilities
+ filters.map(&:required_ability).uniq
+ end
+
+ private
+
+ def build_hash
+ filters.each_with_object({}) { |f, h| h[f.attr] = yield f }
+ end
+
+ def parse(params)
+ (params || {}).map { |attr, args| build_filter(attr, args) }.compact
+ end
+
+ def build_filter(attr, args)
+ type = filter_type(attr)
+ if type
+ filter = type.new(attr, args.with_indifferent_access)
+ filter.present? ? filter : nil
+ end
+ end
+
+ def filter_type(attr)
+ key = filter_type_key(attr)
+ TYPES.find { |t| t.key == key }
+ end
+
+ def filter_type_key(attr)
+ # TODO: map filter types for regular person attrs
+ attr.to_s
+ end
+
+end
diff --git a/app/domain/person/filter/list.rb b/app/domain/person/filter/list.rb
new file mode 100644
index 0000000000..caff5ed400
--- /dev/null
+++ b/app/domain/person/filter/list.rb
@@ -0,0 +1,78 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Person::Filter::List
+
+ attr_reader :group, :user, :chain, :range, :name, :multiple_groups
+
+ def initialize(group, user, params = {})
+ @group = group
+ @user = user
+ @chain = Person::Filter::Chain.new(params[:filters])
+ @range = params[:range]
+ @name = params[:name]
+ end
+
+ def entries
+ default_order(filter(accessibles).preload_groups)
+ end
+
+ def all_count
+ @all_count ||= filter(all).count
+ end
+
+ private
+
+ def filter(scope)
+ if chain.present?
+ chain.filter(list_range(scope)).uniq
+ else
+ scope.members(group).uniq
+ end
+ end
+
+ def accessibles
+ ability = accessibles_class.new(user, group_range? ? group : nil)
+ Person.accessible_by(ability)
+ end
+
+ def accessibles_class
+ abilities = chain.required_abilities
+ if abilities.include?(:full)
+ PersonFullReadables
+ else
+ PersonReadables
+ end
+ end
+
+ def all
+ chain.blank? || group_range? ? group.people : Person
+ end
+
+ def list_range(scope)
+ case range
+ when 'deep'
+ @multiple_groups = true
+ scope.in_or_below(group)
+ when 'layer'
+ @multiple_groups = true
+ scope.in_layer(group)
+ else
+ scope.to_sql['INNER JOIN `roles`'] ? scope : scope.joins(:roles)
+ end
+ end
+
+ def group_range?
+ !%w(deep layer).include?(range)
+ end
+
+ def default_order(entries)
+ entries = entries.order_by_role if Settings.people.default_sort == 'role'
+ entries.order_by_name
+ end
+
+end
diff --git a/app/domain/person/filter/qualification.rb b/app/domain/person/filter/qualification.rb
new file mode 100644
index 0000000000..fc23b7f3fe
--- /dev/null
+++ b/app/domain/person/filter/qualification.rb
@@ -0,0 +1,97 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Person::Filter::Qualification < Person::Filter::Base
+
+ self.required_ability = :full
+ self.permitted_args = [:qualification_kind_ids, :validity, :match,
+ :start_at_year_from, :start_at_year_until,
+ :finish_at_year_from, :finish_at_year_until]
+
+ def initialize(attr, args)
+ super
+ id_list(:qualification_kind_ids)
+ end
+
+ def apply(scope)
+ if args[:match].to_s == 'all'
+ match_all_qualification_kinds(scope)
+ else
+ match_one_qualification_kind(scope)
+ end
+ end
+
+ def blank?
+ args[:qualification_kind_ids].blank?
+ end
+
+ def to_params
+ args.dup.tap do |hash|
+ hash[:qualification_kind_ids] = hash[:qualification_kind_ids].join(ID_URL_SEPARATOR)
+ end
+ end
+
+ private
+
+ def match_all_qualification_kinds(scope)
+ subquery = qualification_scope(scope).
+ select('1').
+ where('qualifications.person_id = people.id AND ' \
+ 'qualifications.qualification_kind_id = qk.id')
+
+ scope.where('NOT EXISTS (' \
+ ' SELECT 1 FROM qualification_kinds qk' \
+ ' WHERE qk.id IN (?) ' \
+ " AND NOT EXISTS (#{subquery.to_sql}) )",
+ args[:qualification_kind_ids])
+ end
+
+ def match_one_qualification_kind(scope)
+ scope.
+ joins(:qualifications).
+ where(qualifications: { qualification_kind_id: args[:qualification_kind_ids] }).
+ merge(qualification_scope(scope))
+ end
+
+ def qualification_scope(scope)
+ qualification_validity_scope(scope)
+ .merge(start_scope)
+ .merge(finish_scope)
+ end
+
+ def finish_scope
+ qualification_date_year_scope(
+ :finish_at,
+ args[:finish_at_year_from],
+ args[:finish_at_year_until]
+ )
+ end
+
+ def start_scope
+ qualification_date_year_scope(
+ :start_at,
+ args[:start_at_year_from],
+ args[:start_at_year_until]
+ )
+ end
+
+ def qualification_date_year_scope(attr, from, untils)
+ scope = ::Qualification.all
+ scope = scope.where("#{attr} >= ?", Date.new(from, 1, 1)) if from.to_i > 0
+ scope = scope.where("#{attr} <= ?", Date.new(untils, 12, 31)) if untils.to_i > 0
+ scope
+ end
+
+ def qualification_validity_scope(_scope)
+ case args[:validity].to_s
+ when 'active' then ::Qualification.active
+ when 'reactivateable' then ::Qualification.reactivateable
+ else ::Qualification.all
+ end
+ end
+
+end
diff --git a/app/domain/person/filter/role.rb b/app/domain/person/filter/role.rb
new file mode 100644
index 0000000000..29380cb3ef
--- /dev/null
+++ b/app/domain/person/filter/role.rb
@@ -0,0 +1,100 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Person::Filter::Role < Person::Filter::Base
+
+ self.permitted_args = [:role_type_ids, :role_types, :kind, :start_at, :finish_at]
+
+ def initialize(attr, args)
+ super
+ initialize_role_types
+ end
+
+ def apply(scope)
+ with_deleted(scope).where(type_conditions).where(duration_conditions)
+ end
+
+ def blank?
+ args[:role_type_ids].blank?
+ end
+
+ def to_hash
+ merge_duration_args(role_types: args[:role_types])
+ end
+
+ def to_params
+ merge_duration_args(role_type_ids: args[:role_type_ids].join(ID_URL_SEPARATOR))
+ end
+
+ def with_deleted?
+ %w(active deleted).include?(args[:kind])
+ end
+
+ def time_range
+ start_at = args[:start_at].presence || Time.zone.at(0).to_date.to_s
+ finish_at = args[:finish_at].presence || Time.zone.now.to_date.to_s
+
+ Date.parse(start_at).beginning_of_day..Date.parse(finish_at).end_of_day
+ end
+
+ private
+
+ def merge_duration_args(hash)
+ hash.merge(args.slice(:kind, :start_at, :finish_at))
+ end
+
+ def initialize_role_types
+ classes = role_classes
+ args[:role_type_ids] = classes.map(&:id)
+ args[:role_types] = classes.map(&:sti_name)
+ end
+
+ def role_classes
+ if args[:role_types].present?
+ role_classes_from_types
+ else
+ Role.types_by_ids(id_list(:role_type_ids))
+ end
+ end
+
+ def role_classes_from_types
+ map = Role.all_types.each_with_object({}) { |r, h| h[r.sti_name] = r }
+ args[:role_types].map { |t| map[t] }.compact
+ end
+
+ def with_deleted(scope)
+ with_deleted? ? scope.joins(all_roles_join) : scope
+ end
+
+ def role_relation
+ with_deleted? ? :with_deleted_roles : :roles
+ end
+
+ def type_conditions
+ [[role_relation, { type: args[:role_types] }]].to_h
+ end
+
+ def duration_conditions
+ case args[:kind]
+ when 'created' then [[role_relation, { created_at: time_range }]].to_h
+ when 'deleted' then [[role_relation, { deleted_at: time_range }]].to_h
+ when 'active' then [active_role_condition, min: time_range.min, max: time_range.max]
+ end
+ end
+
+ def active_role_condition
+ <<-SQL.strip_heredoc.split.map(&:strip).join(' ')
+ with_deleted_roles.created_at <= :max AND
+ (with_deleted_roles.deleted_at >= :min OR with_deleted_roles.deleted_at IS NULL)
+ SQL
+ end
+
+ def all_roles_join
+ 'INNER JOIN roles AS with_deleted_roles ON with_deleted_roles.person_id = people.id'
+ end
+
+end
diff --git a/app/domain/person/list_filter.rb b/app/domain/person/list_filter.rb
deleted file mode 100644
index 1dfc908a93..0000000000
--- a/app/domain/person/list_filter.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-
-class Person::ListFilter
-
- class_attribute :accessibles_class
- self.accessibles_class = PersonReadables
-
- attr_reader :group, :user, :multiple_groups
-
- def initialize(group, user)
- @group = group
- @user = user
- end
-
- def filter_entries
- entries = filtered_entries { |group| accessibles(group) }.preload_groups.uniq
- entries = entries.order_by_role if Settings.people.default_sort == 'role'
- entries.order_by_name
- end
-
- def all_count
- filtered_entries { |group| all(group) }.uniq.count
- end
-
- private
-
- def unfiltered_entries(&block)
- block.call(group).members(group)
- end
-
- def list_scope(scope_kind, &block)
- case scope_kind
- when 'deep'
- @multiple_groups = true
- block.call.in_or_below(group)
- when 'layer'
- @multiple_groups = true
- block.call.in_layer(group)
- else
- block.call(group)
- end
- end
-
- def all(group = nil)
- group ? group.people : Person
- end
-
- def accessibles(group = nil)
- ability = accessibles_class.new(user, group)
- Person.accessible_by(ability)
- end
-
-end
diff --git a/app/domain/person/qualification_filter.rb b/app/domain/person/qualification_filter.rb
deleted file mode 100644
index c3c19fc496..0000000000
--- a/app/domain/person/qualification_filter.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-
-class Person::QualificationFilter < Person::ListFilter
-
- self.accessibles_class = PersonFullReadables
-
- attr_reader :kind, :validity, :qualification_kind_ids
-
- def initialize(group, user, params)
- super(group, user)
- @kind = params[:kind].to_s
- @validity = params[:validity].to_s
- @qualification_kind_ids = Array(params[:qualification_kind_id])
- end
-
- private
-
- def filtered_entries(&block)
- if qualification_kind_ids.present?
- entries_with_qualifications(list_scope(kind, &block))
- else
- unfiltered_entries(&block)
- end
- end
-
- def entries_with_qualifications(scope)
- scope = scope.joins(:qualifications).
- where(qualifications: { qualification_kind_id: qualification_kind_ids })
-
- case validity
- when 'active' then scope.merge(Qualification.active)
- when 'reactivateable' then scope.merge(Qualification.reactivateable)
- else scope
- end
- end
-
-end
diff --git a/app/domain/person/role_filter.rb b/app/domain/person/role_filter.rb
deleted file mode 100644
index 84e33f5eae..0000000000
--- a/app/domain/person/role_filter.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2014, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-
-class Person::RoleFilter < Person::ListFilter
-
- attr_reader :kind, :filter
-
- def initialize(group, user, params)
- super(group, user)
- @kind = params[:kind].to_s
- @filter = PeopleFilter.new(role_type_ids: params[:role_type_ids])
- end
-
- private
-
- def filtered_entries(&block)
- if filter.role_types.present?
- list_scope(kind, &block).where(roles: { type: filter.role_types })
- else
- unfiltered_entries(&block)
- end
- end
-
-end
diff --git a/app/domain/search_strategies/base.rb b/app/domain/search_strategies/base.rb
new file mode 100644
index 0000000000..fc8baaf99d
--- /dev/null
+++ b/app/domain/search_strategies/base.rb
@@ -0,0 +1,87 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module SearchStrategies
+ class Base
+
+ QUERY_PER_PAGE = 10
+
+ def initialize(user, term, page)
+ @user = user
+ @term = term
+ @page = page
+ end
+
+ def list_people
+ return Person.none.page(1) unless @term.present?
+ query_accessible_people do |ids|
+ entries = fetch_people(ids)
+ entries = Person::PreloadGroups.for(entries)
+ entries = Person::PreloadPublicAccounts.for(entries)
+ entries
+ end
+ end
+
+ def query_people
+ # override
+ Person.none.page(1)
+ end
+
+ def query_groups
+ # override
+ Group.none.page(1)
+ end
+
+ def query_events
+ # override
+ Event.none.page(1)
+ end
+
+ protected
+
+ def fetch_people(_ids)
+ # override
+ Person.none.page(1)
+ end
+
+ def query_accessible_people
+ ids = accessible_people_ids
+ return Person.none.page(1) if ids.blank?
+ yield ids
+ end
+
+ def accessible_people_ids
+ key = "accessible_people_ids_for_#{@user.id}"
+ Rails.cache.fetch(key, expires_in: 15.minutes) do
+ ids = load_accessible_people_ids
+ if Ability.new(@user).can?(:index_people_without_role, Person)
+ ids += load_deleted_people_ids
+ end
+ ids.uniq
+ end
+ end
+
+ def load_accessible_people_ids
+ accessible = Person.accessible_by(PersonReadables.new(@user))
+
+ # This still selects all people attributes :(
+ # accessible.pluck('people.id')
+
+ # rewrite query to only include id column
+ sql = accessible.to_sql.gsub(/SELECT (.+) FROM /, 'SELECT DISTINCT people.id FROM ')
+ result = Person.connection.execute(sql)
+ result.collect { |row| row[0] }
+ end
+
+ def load_deleted_people_ids
+ Person.where('NOT EXISTS (SELECT * FROM roles ' \
+ 'WHERE roles.deleted_at IS NULL AND roles.person_id = people.id)')
+ .pluck(:id)
+ end
+
+ end
+end
diff --git a/app/domain/search_strategies/sphinx.rb b/app/domain/search_strategies/sphinx.rb
new file mode 100644
index 0000000000..bbb6487b5e
--- /dev/null
+++ b/app/domain/search_strategies/sphinx.rb
@@ -0,0 +1,63 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module SearchStrategies
+ class Sphinx < Base
+
+ delegate :star_supported?, to: :class
+
+ def query_people
+ return Person.none.page(1) if @term.blank?
+ query_accessible_people do |ids|
+ Person.search(Riddle::Query.escape(@term),
+ default_search_options.merge(
+ with: { sphinx_internal_id: ids }
+ ))
+ end
+ end
+
+ def query_groups
+ return Group.none.page(1) if @term.blank?
+ Group.search(Riddle::Query.escape(@term),
+ default_search_options)
+ end
+
+ def query_events
+ return Event.none.page(1) if @term.blank?
+ sql = { include: [:groups, :dates] }
+ Event.search(Riddle::Query.escape(@term),
+ default_search_options.merge(sql: sql))
+ end
+
+ protected
+
+ def default_search_options
+ { per_page: QUERY_PER_PAGE,
+ star: star_supported? }
+ end
+
+ def fetch_people(ids)
+ Person.search(Riddle::Query.escape(@term),
+ page: @page,
+ order: 'last_name asc, ' \
+ 'first_name asc, ' \
+ "#{ThinkingSphinx::SphinxQL.weight[:select]} desc",
+ star: star_supported?,
+ with: { sphinx_internal_id: ids })
+ end
+
+ class << self
+
+ def star_supported?
+ version = Rails.application.class.sphinx_version
+ version.nil? || version >= '2.1'
+ end
+
+ end
+
+ end
+end
diff --git a/app/domain/search_strategies/sql.rb b/app/domain/search_strategies/sql.rb
new file mode 100644
index 0000000000..05ba73e4d9
--- /dev/null
+++ b/app/domain/search_strategies/sql.rb
@@ -0,0 +1,85 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module SearchStrategies
+ class Sql < Base
+
+ MIN_TERM_LENGTH = 2
+
+ SEARCH_FIELDS = {
+ 'Person' => {
+ attrs: ['people.first_name', 'people.last_name', 'people.company_name', 'people.nickname',
+ 'people.company', 'people.email', 'people.address', 'people.zip_code',
+ 'people.town', 'people.country', 'people.birthday', 'people.additional_information',
+ 'phone_numbers.number', 'social_accounts.name', 'additional_emails.email'],
+ joins: ['LEFT JOIN phone_numbers ON phone_numbers.contactable_id = people.id AND ' \
+ "phone_numbers.contactable_type = 'Person'",
+ 'LEFT JOIN social_accounts ON social_accounts.contactable_id = people.id AND '\
+ "phone_numbers.contactable_type = 'Person'",
+ 'LEFT JOIN additional_emails ON additional_emails.contactable_id = people.id AND '\
+ "phone_numbers.contactable_type = 'Person'"]
+ },
+ 'Group' => {
+ attrs: ['groups.name', 'groups.short_name', 'groups.email', 'groups.address',
+ 'groups.zip_code', 'groups.town', 'groups.country',
+ 'parent.name', 'parent.short_name', 'phone_numbers.number', 'social_accounts.name',
+ 'additional_emails.email'],
+ joins: ['LEFT JOIN groups parent ON parent.id = groups.parent_id',
+ 'LEFT JOIN phone_numbers ON phone_numbers.contactable_id = groups.id AND ' \
+ "phone_numbers.contactable_type = 'Group'",
+ 'LEFT JOIN social_accounts ON social_accounts.contactable_id = groups.id AND '\
+ "phone_numbers.contactable_type = 'Group'",
+ 'LEFT JOIN additional_emails ON additional_emails.contactable_id = groups.id AND '\
+ "phone_numbers.contactable_type = 'Group'"]
+ },
+ 'Event' => {
+ attrs: ['events.name', 'events.number', 'groups.name'],
+ joins: [:groups]
+ }
+ }
+
+ def list_people
+ return Person.none.page(1) unless term_present?
+ Kaminari.paginate_array(super).page(@page)
+ end
+
+ def query_people
+ return Person.none.page(1) unless term_present?
+ query_accessible_people do |ids|
+ query_entities(Person.where(id: ids)).page(1).per(QUERY_PER_PAGE)
+ end
+ end
+
+ def query_groups
+ return Group.none.page(1) unless term_present?
+ query_entities(Group.all).page(1).per(QUERY_PER_PAGE)
+ end
+
+ def query_events
+ return Event.none.page(1) unless term_present?
+ query_entities(Event.includes(:groups, :dates).all).page(1).per(QUERY_PER_PAGE)
+ end
+
+ protected
+
+ def fetch_people(ids)
+ query_entities(Person.where(id: ids))
+ end
+
+ def query_entities(scope)
+ fields = SEARCH_FIELDS[scope.model.sti_name]
+ scope.joins(fields[:joins])
+ .where(SqlConditionBuilder.new(@term, fields[:attrs]).search_conditions)
+ .uniq
+ end
+
+ def term_present?
+ @term.present? && @term.length > MIN_TERM_LENGTH
+ end
+
+ end
+end
diff --git a/app/domain/search_strategies/sql_condition_builder.rb b/app/domain/search_strategies/sql_condition_builder.rb
new file mode 100644
index 0000000000..4d79d620b3
--- /dev/null
+++ b/app/domain/search_strategies/sql_condition_builder.rb
@@ -0,0 +1,47 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module SearchStrategies
+ class SqlConditionBuilder
+
+ def initialize(search_string, search_tables_and_fields)
+ @search_string = search_string
+ @search_tables_and_fields = search_tables_and_fields
+ end
+
+ # Concat the word clauses with AND.
+ def search_conditions
+ search_word_conditions.reduce do |query, condition|
+ query.and(condition)
+ end
+ end
+
+ private
+
+ # Split the search query in single words and create a list of word clauses.
+ def search_word_conditions
+ @search_string.split(/\s+/).map { |w| search_word_condition(w) }
+ end
+
+ # Create a list of Arel #matches queries for each column and the given
+ # word.
+ def search_word_condition(word)
+ search_column_condition(word).reduce do |query, condition|
+ query.or(condition)
+ end
+ end
+
+ def search_column_condition(word)
+ @search_tables_and_fields.map do |table_field|
+ table_name, field = table_field.split('.', 2)
+ table = Arel::Table.new(table_name)
+ table[field].matches(Arel::Nodes::Quoted.new("%#{word}%"))
+ end
+ end
+
+ end
+end
diff --git a/app/domain/tag_category_parser.rb b/app/domain/tag_category_parser.rb
index 9754d344a0..702d108cf8 100644
--- a/app/domain/tag_category_parser.rb
+++ b/app/domain/tag_category_parser.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
+# https://github.com/hitobito/hitobito.
class TagCategoryParser < ActsAsTaggableOn::DefaultParser
diff --git a/app/domain/translatable.rb b/app/domain/translatable.rb
index 93b88ed638..8592b56d7f 100644
--- a/app/domain/translatable.rb
+++ b/app/domain/translatable.rb
@@ -7,12 +7,12 @@
module Translatable
- private
-
def translate(key, options = {})
I18n.t(full_translation_key(key), options)
end
+ private
+
def full_translation_key(suffix)
[translation_prefix, suffix].join('.').to_sym
end
diff --git a/app/helpers/action_helper.rb b/app/helpers/action_helper.rb
index c9ed901cee..4c3f900bc9 100644
--- a/app/helpers/action_helper.rb
+++ b/app/helpers/action_helper.rb
@@ -28,8 +28,8 @@ def button_action_edit(path = nil, options = {})
# Uses the current record if none is given.
def button_action_destroy(path = nil, options = {})
path ||= path_args(entry)
- options[:data] = { confirm: ti(:confirm_delete),
- method: :delete }
+ options[:data] ||= {}
+ options[:data].reverse_merge!(confirm: ti(:confirm_delete), method: :delete)
action_button ti(:"link.delete"), path, 'trash', options
end
diff --git a/app/helpers/contact_attrs/control_builder.rb b/app/helpers/contact_attrs/control_builder.rb
new file mode 100644
index 0000000000..053b35d5d8
--- /dev/null
+++ b/app/helpers/contact_attrs/control_builder.rb
@@ -0,0 +1,108 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module ContactAttrs
+ class ControlBuilder
+
+ include ActionView::Helpers::OutputSafetyHelper
+
+ def initialize(form, event)
+ @f = form
+ @event = event
+ end
+
+ def render
+ safe_join([mandatory_contact_attrs, configurable_contact_attrs, contact_associations])
+ end
+
+ private
+
+ delegate :t, to: I18n
+
+ attr_reader :f, :event
+
+ def mandatory_contact_attrs
+ Event::ParticipationContactData.mandatory_contact_attrs.collect do |a|
+ f.labeled(a, attr_label(a)) do
+ radio_buttons(a, true, [:required])
+ end
+ end
+ end
+
+ def configurable_contact_attrs
+ non_mandatory_contact_attrs.collect do |a|
+ f.labeled(a, attr_label(a)) do
+ radio_buttons(a)
+ end
+ end
+ end
+
+ def non_mandatory_contact_attrs
+ Event::ParticipationContactData.contact_attrs -
+ Event::ParticipationContactData.mandatory_contact_attrs
+ end
+
+ def contact_associations
+ Event::ParticipationContactData.contact_associations.collect do |a|
+ f.labeled(a, attr_label(a)) do
+ assoc_checkbox(a)
+ end
+ end
+ end
+
+ def radio_buttons(attr, disabled = false, options = [:required, :optional, :hidden])
+ buttons = options.collect do |o|
+ checked = options.size == 1
+ radio_button(attr, disabled, o, checked)
+ end
+ safe_join(buttons)
+ end
+
+ def radio_button(attr, disabled, option, checked = false)
+ f.label("#{for_label(attr)}_#{option}", class: 'radio inline') do
+ checked = checked ? checked : checked?(attr, option)
+ options = {disabled: disabled, checked: checked}
+ f.radio_button(for_label(attr), option, options) +
+ option_label(option)
+ end
+ end
+
+ def assoc_checkbox(assoc)
+ f.label(for_label(assoc), class: 'checkbox inline') do
+ options = {checked: assoc_hidden?(assoc)}
+ f.check_box(for_label(assoc), options, :hidden) +
+ option_label(:hidden)
+ end
+ end
+
+ def assoc_hidden?(assoc)
+ event.hidden_contact_attrs.include?(assoc.to_s)
+ end
+
+ def checked?(attr, option)
+ attr = attr.to_s
+ required = event.required_contact_attrs.include?(attr)
+ hidden = event.hidden_contact_attrs.include?(attr)
+ return required if option == :required
+ return hidden if option == :hidden
+ !required && !hidden
+ end
+
+ def for_label(attr)
+ "contact_attrs[#{attr}]"
+ end
+
+ def option_label(option)
+ t("activerecord.attributes.event/contact_attrs.#{option}")
+ end
+
+ def attr_label(attr)
+ t("activerecord.attributes.person.#{attr}")
+ end
+
+ end
+end
diff --git a/app/helpers/dropdown/base.rb b/app/helpers/dropdown/base.rb
index 9358d98d3d..792ca86727 100644
--- a/app/helpers/dropdown/base.rb
+++ b/app/helpers/dropdown/base.rb
@@ -1,6 +1,7 @@
# encoding: utf-8
+# frozen_string_literal: true
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -79,7 +80,7 @@ def render_items
end
- class Item < Struct.new(:label, :url, :sub_items, :options)
+ Item = Struct.new(:label, :url, :sub_items, :options) do
def initialize(label, url, options = {})
super(label, url, [], options)
diff --git a/app/helpers/dropdown/event/events_export.rb b/app/helpers/dropdown/event/events_export.rb
new file mode 100644
index 0000000000..c1b70c5200
--- /dev/null
+++ b/app/helpers/dropdown/event/events_export.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Dropdown::Event
+ class EventsExport < Dropdown::Base
+
+ attr_reader :user, :params
+
+ def initialize(template, params)
+ super(template, translate(:button), :download)
+ @params = params
+
+ init_items
+ end
+
+ private
+
+ def init_items
+ tabular_links(:csv)
+ tabular_links(:xlsx)
+ end
+
+ def tabular_links(format)
+ add_item(translate(format), params.merge(format: format))
+ end
+
+ end
+
+end
diff --git a/app/helpers/dropdown/event/participant_add.rb b/app/helpers/dropdown/event/participant_add.rb
index ab72a376b1..c3925ca6f9 100644
--- a/app/helpers/dropdown/event/participant_add.rb
+++ b/app/helpers/dropdown/event/participant_add.rb
@@ -56,11 +56,19 @@ def simple_button(url, options = {})
def init_items(url_options)
event.participant_types.each do |type|
opts = url_options.merge(event_role: { type: type.sti_name })
- link = template.new_group_event_participation_path(group, event, opts)
+ link = participate_link(opts)
add_item(translate(:as, role: type.label), link)
end
end
+ def participate_link(opts)
+ if opts[:for_someone_else]
+ template.new_group_event_participation_path(group, event, opts)
+ else
+ template.contact_data_group_event_participations_path(group, event, opts)
+ end
+ end
+
end
end
end
diff --git a/app/helpers/dropdown/event/role_add.rb b/app/helpers/dropdown/event/role_add.rb
index 5c5f36e770..b71901f0e9 100644
--- a/app/helpers/dropdown/event/role_add.rb
+++ b/app/helpers/dropdown/event/role_add.rb
@@ -23,7 +23,7 @@ def initialize(template, group, event)
private
def init_items
- event.klass.role_types.reject(&:restricted?).each do |type|
+ event.role_types.reject(&:restricted?).each do |type|
link = template.new_group_event_role_path(group,
event,
event_role: { type: type.sti_name })
diff --git a/app/helpers/dropdown/invoice_sending.rb b/app/helpers/dropdown/invoice_sending.rb
new file mode 100644
index 0000000000..e99230e7ed
--- /dev/null
+++ b/app/helpers/dropdown/invoice_sending.rb
@@ -0,0 +1,38 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Dropdown
+ class InvoiceSending < Base
+
+ attr_reader :params
+
+ def initialize(template, params, path_method)
+ super(template, translate(:button), :envelope)
+ @params = params
+ @path_method = path_method
+ init_items
+ end
+
+ private
+
+ def init_items
+ send_links
+ end
+
+ def send_links
+ add_item(:state, mail: false)
+ add_item(:mail, mail: true)
+ end
+
+ def add_item(key, options = {})
+ path = @template.send(@path_method, options)
+ super(translate(key), path, data: { method: :put, checkable: true })
+ end
+
+ end
+end
diff --git a/app/helpers/dropdown/invoices.rb b/app/helpers/dropdown/invoices.rb
new file mode 100644
index 0000000000..4fa088d2d3
--- /dev/null
+++ b/app/helpers/dropdown/invoices.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Dropdown
+ class Invoices < Base
+
+ attr_reader :params, :user
+
+ def initialize(template, params, type)
+ super(template, translate(type), type)
+ @params = params
+ @user = template.current_user
+ end
+
+ def print
+ pdf_links
+ self
+ end
+
+ def export
+ label_links
+ csv_links
+ self
+ end
+
+ private
+
+ def pdf_links
+ add_item(translate(:full), export_path(:pdf), item_options)
+ add_item(translate(:articles_only), export_path(:pdf, esr: false), item_options)
+ add_item(translate(:esr_only), export_path(:pdf, articles: false), item_options)
+ end
+
+ def label_links
+ if LabelFormat.exists?
+ Dropdown::LabelItems.new(self, item_options.merge(condense_labels: false)).add
+ end
+ end
+
+ def csv_links
+ add_item(translate(:csv), export_path(:csv), item_options)
+ end
+
+ def item_options
+ { target: :new, data: { checkable: true } }
+ end
+
+ def export_path(format, options = {})
+ params.merge(options).merge(format: format)
+ end
+ end
+end
diff --git a/app/helpers/dropdown/label_items.rb b/app/helpers/dropdown/label_items.rb
new file mode 100644
index 0000000000..b22a369357
--- /dev/null
+++ b/app/helpers/dropdown/label_items.rb
@@ -0,0 +1,95 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Dropdown
+ class LabelItems
+ attr_reader :dropdown, :item_options
+
+ delegate :add_item, :translate, :user, :params, to: :dropdown
+
+ def initialize(dropdown, item_options = {})
+ @dropdown = dropdown
+ @condense_labels = item_options.delete(:condense_labels)
+ @item_options = item_options.reverse_merge(target: :new,
+ class: 'export-label-format')
+ end
+
+ def add
+ label_item = add_item(translate(:labels), main_label_link)
+ add_last_used_format_item(label_item)
+ add_label_format_items(label_item)
+ add_condensed_labels_option_items(label_item) if @condense_labels
+ end
+
+ def main_label_link
+ if user.last_label_format_id
+ export_label_format_path(user.last_label_format_id)
+ else
+ '#'
+ end
+ end
+
+ def add_last_used_format_item(parent)
+ if user.last_label_format_id?
+ last_format = user.last_label_format
+ parent.sub_items << Item.new(last_format.to_s,
+ export_label_format_path(last_format.id),
+ item_options)
+ parent.sub_items << Divider.new
+ end
+ end
+
+ def add_label_format_items(parent)
+ LabelFormat.list.for_person(user).each do |label_format|
+ parent.sub_items << Item.new(label_format,
+ export_label_format_path(label_format.id),
+ item_options)
+ end
+ end
+
+ def add_condensed_labels_option_items(parent)
+ parent.sub_items << Divider.new
+ parent.sub_items << ToggleCondensedLabelsItem.new(dropdown.template)
+ end
+
+ def export_label_format_path(id)
+ params.merge(format: :pdf, label_format_id: id,
+ condense_labels: ToggleCondensedLabelsItem::DEFAULT_STATE)
+ end
+
+
+ class ToggleCondensedLabelsItem < Dropdown::Base
+ DEFAULT_STATE = false
+
+ def initialize(template)
+ super(template, template.t('dropdown/people_export.condense_labels'), :plus)
+ end
+
+ def render(template)
+ template.content_tag(:li) do
+ template.link_to('#', id: 'toggle-condense-labels') do
+ render_checkbox(template)
+ end
+ end
+ end
+
+ def render_checkbox(template)
+ template.content_tag(:div, class: 'checkbox') do
+ template.content_tag(:label, for: :condense) do
+ template.safe_join([
+ template.check_box_tag(:condense, '1', DEFAULT_STATE),
+ template.t('dropdown/people_export.condense_labels'),
+ template.content_tag(:p, template.t('dropdown/people_export.condense_labels_hint'),
+ class: 'help-text')
+ ].compact)
+ end
+ end
+ end
+ end
+ end
+end
+
diff --git a/app/helpers/dropdown/people_export.rb b/app/helpers/dropdown/people_export.rb
old mode 100644
new mode 100755
index 28561883b7..631335122b
--- a/app/helpers/dropdown/people_export.rb
+++ b/app/helpers/dropdown/people_export.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -10,12 +10,13 @@ class PeopleExport < Base
attr_reader :user, :params
- def initialize(template, user, params, details, email_addresses)
+ def initialize(template, user, params, details, email_addresses, labels = true)
super(template, translate(:button), :download)
@user = user
@params = params
@details = details
@email_addresses = email_addresses
+ @labels = labels
init_items
end
@@ -23,23 +24,29 @@ def initialize(template, user, params, details, email_addresses)
private
def init_items
- csv_links
+ tabular_links(:csv)
+ tabular_links(:xlsx)
+ vcard_link
label_links
email_addresses_link
end
- def csv_links
- csv_path = params.merge(format: :csv)
+ def tabular_links(format)
+ path = params.merge(format: format)
if @details
- csv_item = add_item(translate(:csv), '#')
- csv_item.sub_items << Item.new(translate(:addresses), csv_path)
- csv_item.sub_items << Item.new(translate(:everything), csv_path.merge(details: true))
+ item = add_item(translate(format), '#')
+ item.sub_items << Item.new(translate(:addresses), path)
+ item.sub_items << Item.new(translate(:everything), path.merge(details: true))
else
- add_item(translate(:csv), csv_path)
+ add_item(translate(format), path)
end
end
+ def vcard_link
+ add_item(translate(:vcard), params.merge(format: :vcf), target: :new)
+ end
+
def email_addresses_link
if @email_addresses
add_item(translate(:emails), params.merge(format: :email), target: :new)
@@ -47,77 +54,11 @@ def email_addresses_link
end
def label_links
- if LabelFormat.all_as_hash.present?
- label_item = add_item(translate(:labels), main_label_link)
- add_last_used_format_item(label_item)
- add_label_format_items(label_item)
- add_condensed_labels_option_items(label_item)
+ if @labels && LabelFormat.exists?
+ Dropdown::LabelItems.new(self, condense_labels: true).add
end
end
- def main_label_link
- if user.last_label_format_id
- export_label_format_path(user.last_label_format_id)
- else
- '#'
- end
- end
-
- def add_last_used_format_item(parent)
- if user.last_label_format_id?
- last_format = user.last_label_format
- parent.sub_items << Item.new(last_format.to_s,
- export_label_format_path(last_format.id),
- target: :new)
- parent.sub_items << Divider.new
- end
- end
-
- def add_label_format_items(parent)
- LabelFormat.all_as_hash.each do |id, label|
- parent.sub_items << Item.new(label, export_label_format_path(id),
- target: :new, class: 'export-label-format')
- end
- end
-
- def add_condensed_labels_option_items(parent)
- parent.sub_items << Divider.new
- parent.sub_items << ToggleCondensedLabelsItem.new(@template)
- end
-
- def export_label_format_path(id)
- params.merge(format: :pdf, label_format_id: id,
- condense_labels: ToggleCondensedLabelsItem::DEFAULT_STATE)
- end
-
end
- class ToggleCondensedLabelsItem < Base
- DEFAULT_STATE = false
-
- def initialize(template)
- super(template, template.t('dropdown/people_export.condense_labels'), :plus)
- end
-
- def render(template)
- template.content_tag(:li) do
- template.link_to('#', id: 'toggle-condense-labels') do
- render_checkbox(template)
- end
- end
- end
-
- def render_checkbox(template)
- template.content_tag(:div, class: 'checkbox') do
- template.content_tag(:label, for: :condense) do
- template.safe_join([
- template.check_box_tag(:condense, '1', DEFAULT_STATE),
- template.t('dropdown/people_export.condense_labels'),
- template.content_tag(:p, template.t('dropdown/people_export.condense_labels_hint'),
- class: 'help-text')
- ].compact)
- end
- end
- end
- end
end
diff --git a/app/helpers/event_kinds_helper.rb b/app/helpers/event_kinds_helper.rb
index 411e675d0a..4855dbddfd 100644
--- a/app/helpers/event_kinds_helper.rb
+++ b/app/helpers/event_kinds_helper.rb
@@ -10,14 +10,28 @@ module EventKindsHelper
def labeled_qualification_kinds_field(form, collection, category, role, title)
selected = entry.qualification_kinds(category, role)
- # Unify collection with selected, to include them even, if they are marked as deleted.
+ # Unify collection with selected, to include them even if they are marked as deleted.
options = collection | selected
form.labeled(title) do
- select_tag "event_kind[qualification_kinds][#{role}][#{category}][qualification_kind_ids]",
+ select_tag("event_kind[qualification_kinds][#{role}][#{category}][qualification_kind_ids]",
options_from_collection_for_select(options, :id, :to_s,
selected.collect(&:id)),
- multiple: true, class: 'span6'
+ multiple: true,
+ class: 'span6')
+ end
+ end
+
+ def grouped_qualification_kinds_string(kind, category, role)
+ kinds = kind.qualification_kinds(category, role).group_by(&:id)
+ grouped_ids = kind.grouped_qualification_kind_ids(category, role)
+ or_separator = [
+ ' ',
+ content_tag(:span, t('event.kinds.qualifications.or'), class: 'muted'),
+ ' '
+ ]
+ safe_join(grouped_ids, safe_join(or_separator)) do |ids|
+ ids.collect { |id| kinds[id].first.to_s }.sort.to_sentence
end
end
diff --git a/app/helpers/event_participations_helper.rb b/app/helpers/event_participations_helper.rb
index 96c59fd088..87822f56f0 100644
--- a/app/helpers/event_participations_helper.rb
+++ b/app/helpers/event_participations_helper.rb
@@ -47,4 +47,15 @@ def show_application_priorities?(participation)
participation.application.priorities? &&
can?(:show_priorities, participation.application)
end
+
+ def action_button_cancel_participation
+ action_button(
+ t('event.participations.cancel_application.caption'),
+ group_event_participation_path(parent, entry, @user_participation),
+ 'remove-circle',
+ data: {
+ confirm: t('event.participations.cancel_application.confirmation'),
+ method: :delete
+ })
+ end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 1eddd5d187..38967adf3b 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -1,24 +1,50 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
module EventsHelper
- def format_training_days(event)
- number_with_precision(event.training_days, precision: 1)
+ def new_event_button
+ event_type = find_event_type
+ return unless event_type
+
+ event = event_type.new
+ event.groups << @group
+ if can?(:new, event)
+ action_button(t("events.global.link.add_#{event_type.name.underscore}"),
+ new_group_event_path(@group, event: { type: event_type.sti_name }),
+ :plus)
+ end
end
- def button_action_event_apply(event, group = nil)
+ def export_events_button
+ type = params[:type].presence || 'Event'
+ if can?(:"export_#{type.underscore.pluralize}", @group)
+ Dropdown::Event::EventsExport.new(self, params).to_s
+ end
+ end
+
+ def event_user_application_possible?(event)
participation = event.participations.new
participation.person = current_user
- if event.application_possible? && can?(:new, participation)
+ event.application_possible? && can?(:new, participation)
+ end
+
+ def button_action_event_apply(event, group = nil)
+ if event_user_application_possible?(event)
group ||= event.groups.first
- Dropdown::Event::ParticipantAdd.for_user(self, group, event, current_user)
+ button = Dropdown::Event::ParticipantAdd.for_user(self, group, event, current_user)
+ if event.application_closing_at.present?
+ button += content_tag(:div,
+ t('event.lists.apply_until',
+ date: f(event.application_closing_at)))
+ end
+ button
end
end
@@ -31,10 +57,22 @@ def application_approve_role_exists?
Role.types_with_permission(:approve_applications).present?
end
+ def format_training_days(event)
+ number_with_precision(event.training_days, precision: 1)
+ end
+
def format_event_application_conditions(entry)
texts = [entry.application_conditions]
texts.unshift(entry.kind.application_conditions) if entry.course_kind?
safe_join(texts.select(&:present?).map { |text| simple_format(text) })
end
+ private
+
+ def find_event_type
+ @group.event_types.find do |t|
+ (params[:type].blank? && t == Event) || t.sti_name == params[:type]
+ end
+ end
+
end
diff --git a/app/helpers/filter_helper.rb b/app/helpers/filter_helper.rb
new file mode 100644
index 0000000000..bf60328dd3
--- /dev/null
+++ b/app/helpers/filter_helper.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module FilterHelper
+
+ # rubocop:disable Rails/OutputSafety
+ def direct_filter(attr, label = nil, &block)
+ html = ''.html_safe
+ label ||= model_class.human_attribute_name(attr)
+ html += label_tag(attr, label, class: 'control-label').html_safe if label
+ html += capture(&block)
+ content_tag(:div, html, class: 'control-group').html_safe
+ end
+ # rubocop:enable Rails/OutputSafety
+
+ def direct_filter_select(attr, list, label = nil, options = {})
+ options.reverse_merge!(prompt: t('global.all'), value_method: :first, text_method: :second)
+ add_css_class(options, 'control-group')
+ options[:data] ||= {}
+ options[:data][:submit] = true
+ select_options = options_from_collection_for_select(list,
+ options.delete(:value_method),
+ options.delete(:text_method),
+ params[attr])
+ direct_filter(attr, label) { select_tag(attr, select_options, options) }
+ end
+end
diff --git a/app/helpers/filter_navigation/people.rb b/app/helpers/filter_navigation/people.rb
index 1f47edbcdc..bdb40b387d 100644
--- a/app/helpers/filter_navigation/people.rb
+++ b/app/helpers/filter_navigation/people.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -8,14 +8,14 @@
module FilterNavigation
class People < Base
- attr_reader :group
+ attr_reader :group, :filter
delegate :can?, to: :template
- def initialize(template, group, params)
+ def initialize(template, group, filter)
super(template)
@group = group
- @params = params
+ @filter = filter
init_kind_filter_names
init_labels
init_kind_items
@@ -23,23 +23,11 @@ def initialize(template, group, params)
end
def name
- @params[:name]
+ filter.name
end
- def role_type_ids
- @params[:role_type_ids]
- end
-
- def qualification_kind_ids
- @params[:qualification_kind_id]
- end
-
- def deep
- @params[:kind] || false
- end
-
- def validity
- @params[:validity]
+ def match
+ @params[:match]
end
private
@@ -56,7 +44,7 @@ def init_labels
@active_label = name
elsif name.present?
dropdown.activate(name)
- elsif role_type_ids.present? || qualification_kind_ids.present?
+ elsif filter.chain.present?
dropdown.activate(translate(:custom_filter))
else
@active_label = main_filter_name
@@ -66,11 +54,11 @@ def init_labels
def init_kind_items
@kind_filter_names.each do |kind, name|
types = group.role_types.select { |t| t.kind == kind }
- if visible_role_types?(types)
- count = group.people.where(roles: { type: types.collect(&:sti_name) }).uniq.count
- path = kind == :member ? path : fixed_types_path(name, types)
- item(name, path, count)
- end
+ next unless visible_role_types?(types)
+
+ count = group.people.where(roles: { type: types.collect(&:sti_name) }).uniq.count
+ path = kind == :member ? path : fixed_types_path(name, types)
+ item(name, path, count)
end
end
@@ -90,46 +78,40 @@ def init_dropdown_links
else
add_entire_subgroup_filter_link
end
- add_people_role_filter_links
- add_define_people_role_filter_link
- add_define_qualification_filter_link
+ add_people_filter_links
+ add_define_people_filter_link
end
def add_entire_layer_filter_link
name = translate(:entire_layer)
- link = fixed_types_path(name, sub_groups_role_types, kind: 'layer')
+ link = fixed_types_path(name, sub_groups_role_types, range: 'layer')
dropdown.add_item(name, link)
end
def add_entire_subgroup_filter_link
name = translate(:entire_group)
- link = fixed_types_path(name, sub_groups_role_types, kind: 'deep')
+ link = fixed_types_path(name, sub_groups_role_types, range: 'deep')
dropdown.add_item(name, link)
end
- def add_people_role_filter_links
+ def add_people_filter_links
filters = PeopleFilter.for_group(group)
filters.each { |filter| people_filter_link(filter) }
end
- def add_define_people_role_filter_link
+ def add_define_people_filter_link
if can?(:new, group.people_filters.new)
dropdown.add_divider if dropdown.items.present?
- dropdown.add_item(translate(:new_role_filter), new_group_people_filter_path)
- end
- end
-
- def add_define_qualification_filter_link
- if can?(:index_full_people, group)
- dropdown.add_item(translate(:new_qualification_filter),
- qualification_group_people_filter_path)
+ dropdown.add_item(translate(:new_filter), new_group_people_filter_path)
end
end
def new_group_people_filter_path
template.new_group_people_filter_path(
group.id,
- people_filter: { role_type_ids: role_type_ids })
+ range: filter.range,
+ filters: filter.chain.to_params
+ )
end
def qualification_group_people_filter_path
@@ -137,35 +119,53 @@ def qualification_group_people_filter_path
group.id,
qualification_kind_id: qualification_kind_ids,
kind: deep,
- validity: validity)
+ validity: validity,
+ match: match,
+ start_at_year_from: @params[:start_at_year_from],
+ start_at_year_until: @params[:start_at_year_until],
+ finish_at_year_from: @params[:finish_at_year_from],
+ finish_at_year_until: @params[:finish_at_year_until])
end
def people_filter_link(filter)
- item = dropdown.add_item(filter.name, filter_path(filter, kind: 'deep'))
+ item = dropdown.add_item(filter.name, path(filter_id: filter.id))
if can?(:destroy, filter)
+ item.sub_items << edit_filter_item(filter)
item.sub_items << delete_filter_item(filter)
end
end
def delete_filter_item(filter)
- ::Dropdown::Item.new(template.icon(:trash),
- delete_group_people_filter_path(filter),
- data: { confirm: template.ti(:confirm_delete),
- method: :delete })
+ ::Dropdown::Item.new(
+ filter_label(:trash, :delete),
+ delete_group_people_filter_path(filter),
+ data: { confirm: template.ti(:confirm_delete), method: :delete }
+ )
end
def delete_group_people_filter_path(filter)
template.group_people_filter_path(group, filter)
end
- def fixed_types_path(name, types, options = {})
- filter_path(PeopleFilter.new(role_type_ids: types.collect(&:id), name: name), options)
+ def edit_filter_item(filter)
+ ::Dropdown::Item.new(
+ filter_label(:edit, :edit),
+ edit_group_people_filter_path(filter)
+ )
+ end
+
+ def edit_group_people_filter_path(filter)
+ template.edit_group_people_filter_path(group, filter)
+ end
+
+ def filter_label(icon, desc)
+ template.safe_join([template.icon(icon), ' ', template.t("global.link.#{desc}")])
end
- def filter_path(filter, options = {})
- options[:role_type_ids] ||= filter.role_type_ids_string
- options[:name] ||= filter.name
- path(options)
+ def fixed_types_path(name, types, options = {})
+ type_ids = types.collect(&:id).join(Person::Filter::Base::ID_URL_SEPARATOR)
+ path(options.merge(name: name,
+ filters: { role: { role_type_ids: type_ids } }))
end
def path(options = {})
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 14cfff73d7..488d7137f3 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -7,16 +7,11 @@
module GroupsHelper
- def new_event_button
- event_type = find_event_type
- return unless event_type
-
- event = event_type.new
- event.groups << @group
- if can?(:new, event)
- action_button(t("events.global.link.add_#{event_type.name.underscore}"),
- new_group_event_path(@group, event: { type: event_type.sti_name }),
- :plus)
+ def export_events_ical_button
+ type = params[:type].presence || 'Event'
+ if can?(:"export_#{type.underscore.pluralize}", @group)
+ action_button(I18n.t('event.lists.courses.ical_export_button'),
+ params.merge(format: :ics), :calendar)
end
end
@@ -35,12 +30,4 @@ def tab_person_add_request_label(group)
label.html_safe
end
- private
-
- def find_event_type
- @group.event_types.find do |t|
- (params[:type].blank? && t == Event) || t.sti_name == params[:type]
- end
- end
-
end
diff --git a/app/helpers/invoice/history.rb b/app/helpers/invoice/history.rb
new file mode 100644
index 0000000000..4187c513c4
--- /dev/null
+++ b/app/helpers/invoice/history.rb
@@ -0,0 +1,95 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Invoice::History
+ attr_reader :template, :invoice
+
+ delegate :content_tag, :l, :t, :concat, :number_to_currency, to: :template
+
+ def initialize(template, invoice)
+ @template = template
+ @invoice = invoice
+ end
+
+ def to_s
+ content_tag :table do
+ table_rows = [
+ invoice_history_entry(invoice_issued_data, 'blue'),
+ invoice_history_entry(invoice_sent_data, 'blue')
+ ]
+
+ table_rows << invoice_reminder_rows
+ table_rows << invoice_payment_rows
+ table_rows.compact.join.html_safe # rubocop:disable Rails/OutputSafety
+ end
+ end
+
+ private
+
+ def invoice_reminder_rows
+ if invoice.reminder_sent?
+ invoice.payment_reminders.collect.with_index do |reminder, count|
+ next unless reminder.persisted?
+ invoice_history_entry(reminder_sent_data(reminder, count + 1), 'red')
+ end
+ end
+ end
+
+ def invoice_payment_rows
+ if invoice.payments.present?
+ invoice.payments.collect do |payment|
+ next unless payment.persisted?
+ invoice_history_entry(payment_data(payment), 'green')
+ end
+ end
+ end
+
+ def invoice_history_entry(data, color)
+ return unless data
+ content_tag :tr do
+ data.collect do |d|
+ concat content_tag(:td, d, class: color)
+ end.to_s.html_safe # rubocop:disable Rails/OutputSafety
+ end
+ end
+
+ def invoice_issued_data
+ if invoice.issued_at?
+ [
+ '⬤', # Middle Dot
+ l(invoice.issued_at, format: :long),
+ t('invoices.issued')
+ ]
+ end
+ end
+
+ def invoice_sent_data
+ if invoice.sent_at?
+ [
+ '⬤', # Middle Dot
+ l(invoice.sent_at, format: :long),
+ t('invoices.sent')
+ ]
+ end
+ end
+
+ def reminder_sent_data(reminder, count)
+ [
+ '⬤', # Middle Dot
+ l(reminder.created_at.to_date, format: :long),
+ "#{count}. #{t('invoices.reminder_sent')}"
+ ]
+ end
+
+ def payment_data(payment)
+ [
+ '⬤', # Middle Dot
+ l(payment.received_at, format: :long),
+ "#{number_to_currency(payment.amount)} #{t('invoices.payd')}"
+ ]
+ end
+end
diff --git a/app/helpers/invoices_helper.rb b/app/helpers/invoices_helper.rb
new file mode 100644
index 0000000000..b5203ab19a
--- /dev/null
+++ b/app/helpers/invoices_helper.rb
@@ -0,0 +1,54 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module InvoicesHelper
+
+ def format_invoice_state(invoice)
+ type = case invoice.state
+ when /draft|cancelled/ then 'info'
+ when /sent/ then 'warning'
+ when /payed/ then 'success'
+ when /overdue|reminded/ then 'important'
+ end
+ badge(invoice.state_label, type)
+ end
+
+ def invoice_due_since_options
+ [:one_day, :one_week, :one_month].collect do |key|
+ [key, I18n.t("invoices.filter.due_since_list.#{key}")]
+ end
+ end
+
+ def invoices_export_dropdown
+ Dropdown::Invoices.new(self, params, :download).export
+ end
+
+ def invoices_print_dropdown
+ Dropdown::Invoices.new(self, params, :print).print
+ end
+
+ def invoice_sending_dropdown(path_meth)
+ Dropdown::InvoiceSending.new(self, params, path_meth)
+ end
+
+ def invoice_history(invoice)
+ Invoice::History.new(self, invoice)
+ end
+
+ def invoice_receiver_address(invoice)
+ return unless invoice.recipient_address
+ out = ''
+ recipient_address_lines = invoice.recipient_address.split(/\n/)
+ content_tag(:p) do
+ recipient_address_lines.collect do |l|
+ out << (l == recipient_address_lines.first ? "#{l} " : l) + ' '
+ end
+ out << mail_to(entry.recipient_email)
+ out.html_safe # rubocop:disable Rails/OutputSafety
+ end
+ end
+end
diff --git a/app/helpers/navigation_helper.rb b/app/helpers/navigation_helper.rb
index fe273a855b..c917df0f17 100644
--- a/app/helpers/navigation_helper.rb
+++ b/app/helpers/navigation_helper.rb
@@ -10,7 +10,8 @@ module NavigationHelper
MAIN = [
{ label: :groups,
url: :groups_path,
- active_for: %w(groups people) },
+ active_for: %w(groups people),
+ inactive_for: %w(invoices invoice_articles invoice_config) },
{ label: :events,
url: :list_events_path,
@@ -25,7 +26,12 @@ module NavigationHelper
{ label: :admin,
url: :label_formats_path,
active_for: %w(label_formats custom_contents event_kinds qualification_kinds),
- if: ->(_) { can?(:index, LabelFormat) } }
+ if: ->(_) { can?(:index, LabelFormat) } },
+
+ { label: :invoices,
+ url: :first_group_invoices_or_root_path,
+ if: ->(_) { current_user.finance_groups.any? },
+ active_for: %w(invoices invoice_articles invoice_config) }
]
@@ -34,20 +40,30 @@ def render_main_nav
if !options.key?(:if) || instance_eval(&options[:if])
url = options[:url]
url = send(url) if url.is_a?(Symbol)
- nav(I18n.t("navigation.#{options[:label]}"), url, options[:active_for])
+ nav(I18n.t("navigation.#{options[:label]}"),
+ url,
+ options[:active_for],
+ options[:inactive_for])
end
end
end
+ def first_group_invoices_or_root_path
+ return root_path if current_user.finance_groups.blank?
+ group_invoices_path(current_user.finance_groups.first)
+ end
+
# Create a list item for navigations.
# If alternative_paths are given, and they appear in the request url,
# the corresponding item is active.
# If not alternative paths are given, the item is only active if the
# link url equals the request url.
- def nav(label, url, active_for = [])
+ def nav(label, url, active_for = [], inactive_for = [])
options = {}
if current_page?(url) ||
- active_for.any? { |p| request.path =~ %r{/?#{p}/?} }
+ Array(active_for).any? { |p| request.path =~ %r{/?#{p}/?} } &&
+ Array(inactive_for).none? { |p| request.path =~ %r{/?#{p}/?} }
+
options[:class] = 'active'
end
content_tag(:li, link_to(label, url), options)
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
new file mode 100644
index 0000000000..362a49b822
--- /dev/null
+++ b/app/helpers/notes_helper.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Dachverband Schweizer Jugendparlamente. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module NotesHelper
+
+ def note_path(group, note)
+ if note.subject.is_a?(Group)
+ group_note_path(group_id: note.subject_id, id: note.id)
+ else
+ group_person_note_path(group_id: group.id, person_id: note.subject_id, id: note.id)
+ end
+ end
+
+end
diff --git a/app/helpers/people_helper.rb b/app/helpers/people_helper.rb
index c9a6acd9df..401d33be7c 100644
--- a/app/helpers/people_helper.rb
+++ b/app/helpers/people_helper.rb
@@ -11,8 +11,8 @@ def format_gender(person)
person.gender_label
end
- def dropdown_people_export(details = false, emails = true)
- Dropdown::PeopleExport.new(self, current_user, params, details, emails).to_s
+ def dropdown_people_export(details = false, emails = true, labels = true)
+ Dropdown::PeopleExport.new(self, current_user, params, details, emails, labels).to_s
end
def format_birthday(person)
@@ -29,13 +29,15 @@ def format_tags(person)
end
end
- def sortable_grouped_person_attr(t, sortable_attrs, grouping_attr = nil, &block)
- list = sortable_attrs.map do |attr|
- t.sort_header(attr.to_sym, Person.human_attribute_name(attr.to_sym))
+ def sortable_grouped_person_attr(t, attrs, &block)
+ list = attrs.map do |attr, sortable|
+ if sortable
+ t.sort_header(attr.to_sym, Person.human_attribute_name(attr.to_sym))
+ else
+ Person.human_attribute_name(attr.to_sym)
+ end
end
- list.unshift(Person.human_attribute_name(grouping_attr.to_sym)) if grouping_attr
-
header = list[0..-2].collect { |i| content_tag(:span, "#{i} |".html_safe, class: 'nowrap') }
header << list.last
t.col(safe_join(header, ' '), &block)
@@ -50,4 +52,8 @@ def send_login_tooltip_text
def person_link(person)
person ? assoc_link(person) : "(#{t('global.nobody')})"
end
+
+ def format_person_layer_group(person)
+ person.layer_group_label
+ end
end
diff --git a/app/helpers/sheet/event.rb b/app/helpers/sheet/event.rb
index 9ea442deb0..e3d844dd5d 100644
--- a/app/helpers/sheet/event.rb
+++ b/app/helpers/sheet/event.rb
@@ -9,6 +9,16 @@ module Sheet
class Event < Base
self.parent_sheet = Sheet::Group
+ class << self
+
+ private
+
+ def can_view_qualifications?(view, event)
+ view.can?(:qualify, event) || view.can?(:qualifications_read, event)
+ end
+
+ end
+
tab 'global.tabs.info',
:group_event_path,
if: :show,
@@ -31,7 +41,8 @@ class Event < Base
tab 'activerecord.models.qualification.other',
:group_event_qualifications_path,
if: (lambda do |view, _group, event|
- event.course_kind? && event.qualifying? && view.can?(:qualify, event)
+ event.course_kind? && event.qualifying? &&
+ can_view_qualifications?(view, event)
end)
def link_url
diff --git a/app/helpers/sheet/event/participation_contact_data.rb b/app/helpers/sheet/event/participation_contact_data.rb
new file mode 100644
index 0000000000..7b9ef5096e
--- /dev/null
+++ b/app/helpers/sheet/event/participation_contact_data.rb
@@ -0,0 +1,14 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Pfadibewegung Schweiz. This file is part of
+# hitobito_youth and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito_youth.
+
+module Sheet
+ class Event
+ class ParticipationContactData < Base
+ self.parent_sheet = Sheet::Event
+ end
+ end
+end
diff --git a/app/helpers/sheet/group.rb b/app/helpers/sheet/group.rb
index 051216bcc4..bc98c58ed0 100644
--- a/app/helpers/sheet/group.rb
+++ b/app/helpers/sheet/group.rb
@@ -45,9 +45,9 @@ class Group < Base
view.can?(:index_person_add_requests, group)
end)
- tab 'activerecord.models.person/note.other',
- :person_notes_group_path,
- if: :index_person_notes
+ tab 'activerecord.models.note.other',
+ :group_notes_path,
+ if: :index_notes
tab 'groups.tabs.deleted',
:deleted_subgroups_group_path,
diff --git a/app/helpers/sheet/group/deleted_people.rb b/app/helpers/sheet/group/deleted_people.rb
new file mode 100644
index 0000000000..de52fc9252
--- /dev/null
+++ b/app/helpers/sheet/group/deleted_people.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Sheet
+ class Group
+ class DeletedPeople < Group
+
+ self.tabs = []
+
+ def title
+ I18n.t('groups.global.link.deleted_person')
+ end
+
+ def active_tab
+ nil
+ end
+
+ private
+
+ def breadcrumbs?
+ true
+ end
+
+ def breadcrumbs
+ entry.hierarchy.collect do |g|
+ link_to(g.to_s, group_path(g))
+ end
+ end
+
+ def model_name
+ 'group'
+ end
+
+ def translation_prefix
+ 'sheet/group'
+ end
+
+ end
+ end
+end
diff --git a/app/helpers/sheet/group/nav_left.rb b/app/helpers/sheet/group/nav_left.rb
index ee9c3b1db4..adf18e6daf 100644
--- a/app/helpers/sheet/group/nav_left.rb
+++ b/app/helpers/sheet/group/nav_left.rb
@@ -22,7 +22,7 @@ def render
render_upwards +
render_header +
content_tag(:ul, class: 'nav-left-list') do
- render_layer_groups + render_sub_layers
+ render_layer_groups + render_deleted_people_link + render_sub_layers
end
end
@@ -49,7 +49,8 @@ def render_upwards
end
def render_header
- content_tag(:h3, class: "nav-left-title #{'active' if layer == entry}") do
+ active = layer == entry && view.request.path !~ /\/deleted_people$/
+ content_tag(:h3, class: "nav-left-title #{'active' if active}") do
link_to(layer, active_path(layer))
end
end
@@ -96,6 +97,16 @@ def group_link(group)
active_path(group), title: group.to_s)
end
+ def render_deleted_people_link
+ if view.can?(:index_deleted_people, layer)
+ active = view.current_page?(view.group_deleted_people_path(layer.id))
+ content_tag(:li, class: "#{'active' if active}") do
+ link_to(view.t('groups.global.link.deleted_person'),
+ view.group_deleted_people_path(layer.id))
+ end
+ end
+ end
+
def render_sub_layers
safe_join(grouped_sub_layers) do |type, layers|
content_tag(:li, content_tag(:span, type, class: 'divider')) +
@@ -121,8 +132,8 @@ def sub_layers
end
def active_path(group)
- renderer = sheet.active_tab.renderer(view, [group])
- if renderer.show?
+ renderer = sheet.active_tab.try(:renderer, view, [group])
+ if renderer && renderer.show?
renderer.path
else
view.group_path(group)
diff --git a/app/helpers/sheet/invoice.rb b/app/helpers/sheet/invoice.rb
new file mode 100644
index 0000000000..d41cc9b092
--- /dev/null
+++ b/app/helpers/sheet/invoice.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Sheet
+ class Invoice < Base
+
+ def self.parent_sheet
+ nil
+ end
+
+ def left_nav?
+ true
+ end
+
+ def render_left_nav
+ view.render "invoices/nav_left"
+ end
+
+ end
+end
diff --git a/app/helpers/sheet/invoice_article.rb b/app/helpers/sheet/invoice_article.rb
new file mode 100644
index 0000000000..2db708b5d7
--- /dev/null
+++ b/app/helpers/sheet/invoice_article.rb
@@ -0,0 +1,12 @@
+
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Sheet
+ class InvoiceArticle < Sheet::Invoice
+ end
+end
diff --git a/app/helpers/sheet/invoice_config.rb b/app/helpers/sheet/invoice_config.rb
new file mode 100644
index 0000000000..ac4737768d
--- /dev/null
+++ b/app/helpers/sheet/invoice_config.rb
@@ -0,0 +1,11 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Sheet
+ class InvoiceConfig < Sheet::Invoice
+ end
+end
diff --git a/app/helpers/sheet/invoice_list.rb b/app/helpers/sheet/invoice_list.rb
new file mode 100644
index 0000000000..12bb3df37d
--- /dev/null
+++ b/app/helpers/sheet/invoice_list.rb
@@ -0,0 +1,11 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Sheet
+ class InvoiceList < Sheet::Invoice
+ end
+end
diff --git a/app/helpers/sheet/person.rb b/app/helpers/sheet/person.rb
index e2070f075c..f3e248bdc2 100644
--- a/app/helpers/sheet/person.rb
+++ b/app/helpers/sheet/person.rb
@@ -22,6 +22,18 @@ class Person < Base
:log_group_person_path,
if: :log
+ tab 'people.tabs.colleagues',
+ :colleagues_group_person_path,
+ if: (lambda do |_view, _group, person|
+ person.company_name?
+ end)
+
+ tab 'people.tabs.invoices',
+ :invoices_group_person_path,
+ if: (lambda do |view, group, person|
+ view.can?(:index_invoices, group) || view.can?(:index_invoices, person)
+ end)
+
def link_url
view.group_person_path(parent_sheet.entry.id, entry.id)
end
diff --git a/app/helpers/sheet/person/colleague.rb b/app/helpers/sheet/person/colleague.rb
new file mode 100644
index 0000000000..f6089d27af
--- /dev/null
+++ b/app/helpers/sheet/person/colleague.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Dachverband Schweizer Jugendparlamente. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Sheet
+ class Person < Base
+ class Colleague < Base
+
+ self.parent_sheet = Sheet::Person
+
+ end
+ end
+end
diff --git a/app/helpers/sheet/person/invoice.rb b/app/helpers/sheet/person/invoice.rb
new file mode 100644
index 0000000000..7b605f47af
--- /dev/null
+++ b/app/helpers/sheet/person/invoice.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2015, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module Sheet
+ class Person < Base
+ class Invoice < Base
+
+ self.parent_sheet = Sheet::Person
+
+ end
+ end
+end
diff --git a/app/helpers/standard_form_builder.rb b/app/helpers/standard_form_builder.rb
index 33b4aab9be..77ba4b84f1 100644
--- a/app/helpers/standard_form_builder.rb
+++ b/app/helpers/standard_form_builder.rb
@@ -33,8 +33,7 @@ def labeled_input_fields(*attrs)
# Render a corresponding input field for the given attribute.
# The input field is chosen based on the ActiveRecord column type.
# Use additional html_options for the input element.
- # rubocop:disable Metrics/PerceivedComplexity
- def input_field(attr, html_options = {})
+ def input_field(attr, html_options = {}) # rubocop:disable Metrics/PerceivedComplexity
type = column_type(@object, attr)
custom_field_method = :"#{type}_field"
if type == :text
@@ -53,7 +52,6 @@ def input_field(attr, html_options = {})
text_field(attr, html_options)
end
end
- # rubocop:enable Metrics/PerceivedComplexity
# Render a password field
def password_field(attr, html_options = {})
@@ -204,14 +202,12 @@ def belongs_to_field(attr, html_options = {})
end
end
- # rubocop:disable PredicateName
-
# Render a multi select element for a :has_many or :has_and_belongs_to_many
# association defined by attr.
# Use additional html_options for the select element.
# To pass a custom element list, specify the list with the :list key or
# define an instance variable with the pluralized name of the association.
- def has_many_field(attr, html_options = {})
+ def has_many_field(attr, html_options = {}) # rubocop:disable PredicateName
html_options[:multiple] = true
html_options[:class] ||= 'span6'
add_css_class(html_options, 'multiselect')
@@ -225,8 +221,6 @@ def i18n_enum_field(attr, labels, html_options = {})
html_options)
end
- # rubocop:enable PredicateName
-
def person_field(attr, _html_options = {})
attr, attr_id = assoc_and_id_attr(attr)
hidden_field(attr_id) +
@@ -407,6 +401,7 @@ def errors_on?(attr)
# Returns true if the given attribute must be present.
def required?(attr)
+ return true if dynamic_required?(attr)
attr = attr.to_s
attr, attr_id = assoc_and_id_attr(attr)
validators = klass.validators_on(attr) +
@@ -417,6 +412,11 @@ def required?(attr)
end
end
+ def dynamic_required?(attr)
+ return false unless @object.respond_to?(:required_attributes)
+ @object.required_attributes.include?(attr.to_s)
+ end
+
private
def labeled_field_method?(name)
diff --git a/app/helpers/standard_table_builder.rb b/app/helpers/standard_table_builder.rb
index bc26df7ec4..cf993c4547 100644
--- a/app/helpers/standard_table_builder.rb
+++ b/app/helpers/standard_table_builder.rb
@@ -57,7 +57,9 @@ def attrs(*attrs)
# contain the formatted attribute value for the current entry.
def attr(a, header = nil)
header ||= attr_header(a)
- col(header, class: align_class(a)) { |e| format_attr(e, a) }
+ col(header, class: align_class(a)) do |e|
+ block_given? ? yield(e) : format_attr(e, a)
+ end
end
# Renders the table as HTML.
@@ -100,7 +102,7 @@ def entry_class
if entries.respond_to?(:klass)
entries.klass
elsif entries.respond_to?(:decorator_class)
- entries.decorator_class.object_class
+ entries.decorator_class.try(:object_class) || entries.first.model.class
else
entries.first.class
end
diff --git a/app/helpers/version_helper.rb b/app/helpers/version_helper.rb
new file mode 100644
index 0000000000..2a33caf7d6
--- /dev/null
+++ b/app/helpers/version_helper.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+module VersionHelper
+
+ def app_version_changelog_link
+ if app_version
+ link_to app_version, changelog_path
+ end
+ end
+
+ private
+ def app_version
+ app_version = Wagons.app_version.to_s
+ return unless app_version > '0.0'
+ ['Version', app_version, Hitobito::Application.build_info].compact.join(' ')
+ end
+
+end
diff --git a/app/indices/event_index.rb b/app/indices/event_index.rb
new file mode 100644
index 0000000000..fe53f10596
--- /dev/null
+++ b/app/indices/event_index.rb
@@ -0,0 +1,12 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+ThinkingSphinx::Index.define_partial :event do
+ indexes name, number, sortable: true
+
+ indexes groups.name, as: :group_name
+end
diff --git a/app/jobs/event/cancel_application_job.rb b/app/jobs/event/cancel_application_job.rb
new file mode 100644
index 0000000000..4555904f36
--- /dev/null
+++ b/app/jobs/event/cancel_application_job.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito_jubla and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito_jubla.
+
+class Event::CancelApplicationJob < BaseJob
+
+ self.parameters = [:event_id, :person_id]
+
+ def initialize(event, person)
+ @event_id = event.id
+ @person_id = person.id
+ end
+
+ def perform
+ Event::ParticipationMailer.cancel(event, person).deliver_now
+ end
+
+ def event
+ Event.find(@event_id)
+ end
+
+ def person
+ Person.find(@person_id)
+ end
+
+end
diff --git a/app/jobs/export/event_participations_export_job.rb b/app/jobs/export/event_participations_export_job.rb
new file mode 100644
index 0000000000..40cc7fb888
--- /dev/null
+++ b/app/jobs/export/event_participations_export_job.rb
@@ -0,0 +1,49 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Export::EventParticipationsExportJob < Export::ExportBaseJob
+
+ self.parameters = PARAMETERS + [:event_id, :controller_params]
+
+ def initialize(format, user_id, event_id, controller_params)
+ super()
+ @format = format
+ @user_id = user_id
+ @tempfile_name = 'event-participations-export'
+ @event_id = event_id
+ @controller_params = controller_params
+ end
+
+ private
+
+ def send_mail(recipient, file, format)
+ Export::EventParticipationsExportMailer.completed(recipient, file, format).deliver_now
+ end
+
+ def entries
+ @entries ||= Event::ParticipationFilter.new(Event.find(@event_id),
+ user,
+ @controller_params).list_entries
+ end
+
+ def exporter
+ if full_export?
+ Export::Tabular::People::ParticipationsFull
+ else
+ Export::Tabular::People::ParticipationsAddress
+ end
+ end
+
+ def full_export?
+ # This condition has to be in the job because it loads all entries
+ @controller_params[:details] && Ability.new(user).can?(:show_details, entries.first)
+ end
+
+ def user
+ @user ||= Person.find(@user_id)
+ end
+end
diff --git a/app/jobs/export/events_export_job.rb b/app/jobs/export/events_export_job.rb
new file mode 100644
index 0000000000..5f326d2091
--- /dev/null
+++ b/app/jobs/export/events_export_job.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Export::EventsExportJob < Export::ExportBaseJob
+
+ self.parameters = PARAMETERS + [:event_type, :year, :parent]
+
+ def initialize(format, user_id, event_type, year, parent)
+ super()
+ @format = format
+ @exporter = Export::Tabular::Events::List
+ @user_id = user_id
+ @tempfile_name = 'events-export'
+ @event_type = event_type
+ @year = year
+ @parent = parent
+ end
+
+ private
+
+ def send_mail(recipient, file, format)
+ Export::EventsExportMailer.completed(recipient, file, format).deliver_now
+ end
+
+ def entries
+ @parent.events.
+ where(type: @event_type).
+ in_year(@year).
+ order_by_date.
+ preload_all_dates.
+ uniq
+ end
+end
diff --git a/app/jobs/export/export_base_job.rb b/app/jobs/export/export_base_job.rb
new file mode 100644
index 0000000000..e3d1d9ffce
--- /dev/null
+++ b/app/jobs/export/export_base_job.rb
@@ -0,0 +1,68 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+require 'zip'
+
+class Export::ExportBaseJob < BaseJob
+
+ PARAMETERS = [:format, :exporter, :user_id, :tempfile_name].freeze
+
+ attr_reader :exporter
+
+ def perform
+ set_locale
+ file, format = export_file_and_format
+ send_mail(recipient, file, format)
+ ensure
+ if file != export_file
+ file.close
+ file.unlink
+ end
+ export_file.close
+ export_file.unlink
+ end
+
+ def send_mail
+ # override in sub class
+ end
+
+ def entries
+ # override in sub class
+ end
+
+ def recipient
+ @recipient ||= Person.find(@user_id)
+ end
+
+ def export_file_and_format
+ return [export_file, @format] if export_file.size < 512.kilobyte
+
+ # size reduction is by 70-80 %
+ zip = Tempfile.new("#{@tempfile_name}-zip", encoding: 'ascii-8bit')
+ Zip::OutputStream.open(zip.path) do |zos|
+ zos.put_next_entry "entry.#{@format}"
+ zos.write export_file.read
+ end
+
+ [zip, :zip]
+ end
+
+ def export_file
+ @export_file ||= begin
+ file = Tempfile.new("#{@tempfile}-export")
+ file << data
+ file.rewind # make subsequent read-calls start at the beginning
+ file
+ end
+ end
+
+ def data
+ exporter.export(@format, entries)
+ end
+
+end
diff --git a/app/jobs/export/people_export_job.rb b/app/jobs/export/people_export_job.rb
new file mode 100644
index 0000000000..9d37d1260b
--- /dev/null
+++ b/app/jobs/export/people_export_job.rb
@@ -0,0 +1,51 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Export::PeopleExportJob < Export::ExportBaseJob
+
+ self.parameters = PARAMETERS + [:full, :person_filter]
+
+ attr_reader :entries
+
+ def initialize(format, full, user_id, person_filter)
+ super()
+ @format = format
+ @full = full
+ @exporter = exporter
+ @user_id = user_id
+ @tempfile_name = "people-#{format}-zip"
+ @person_filter = person_filter
+ end
+
+ private
+
+ def send_mail(recipient, file, format)
+ Export::PeopleExportMailer.completed(recipient, file, format).deliver_now
+ end
+
+ def entries
+ entries = @person_filter.entries
+ if @full
+ full_entries(entries)
+ else
+ entries.preload_public_accounts.includes(:primary_group)
+ end
+ end
+
+ def full_entries(entries)
+ entries
+ .select('people.*')
+ .preload_accounts
+ .includes(relations_to_tails: :tail, qualifications: { qualification_kind: :translations })
+ .includes(:primary_group)
+ end
+
+ def exporter
+ @full ? Export::Tabular::People::PeopleFull : Export::Tabular::People::PeopleAddress
+ end
+
+end
diff --git a/app/jobs/export/subscriptions_job.rb b/app/jobs/export/subscriptions_job.rb
new file mode 100644
index 0000000000..78391723ac
--- /dev/null
+++ b/app/jobs/export/subscriptions_job.rb
@@ -0,0 +1,38 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Export::SubscriptionsJob < Export::ExportBaseJob
+
+ self.parameters = PARAMETERS + [:mailing_list_id]
+
+ def initialize(format, mailing_list_id, user_id)
+ super()
+ @mailing_list_id = mailing_list_id
+ @format = format
+ @exporter = Export::Tabular::People::PeopleAddress
+ @user_id = user_id
+ @tempfile_name = "subscriptions-#{mailing_list_id}-#{format}-zip"
+ end
+
+ private
+
+ def mailing_list
+ @mailing_list ||= MailingList.find(@mailing_list_id)
+ end
+
+ def send_mail(recipient, file, format)
+ Export::SubscriptionsMailer.completed(recipient, mailing_list, file, format).deliver_now
+ end
+
+ def entries
+ mailing_list.people.includes(:primary_group, :groups)
+ .order_by_name
+ .preload_public_accounts
+ .includes(roles: :group)
+ end
+
+end
diff --git a/app/jobs/invoice/send_notification_job.rb b/app/jobs/invoice/send_notification_job.rb
new file mode 100644
index 0000000000..4bf4019349
--- /dev/null
+++ b/app/jobs/invoice/send_notification_job.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Invoice::SendNotificationJob < BaseJob
+
+ self.parameters = [:invoice_id, :sender_id, :locale]
+
+ def initialize(invoice, sender)
+ super()
+ @invoice_id = invoice.id
+ @sender_id = sender.id
+ end
+
+ def perform
+ set_locale
+
+ pdf_options = { articles: true, esr: false }
+
+ InvoiceMailer.notification(
+ invoice.recipient_name,
+ invoice.recipient_email,
+ sender,
+ invoice,
+ Export::Pdf::Invoice.render(invoice, pdf_options)
+ ).deliver_now
+ end
+
+ def invoice
+ @invoice ||= Invoice.find(@invoice_id)
+ end
+
+ def sender
+ @sender ||= Person.find(@sender_id)
+ end
+end
diff --git a/app/jobs/person/send_add_request_job.rb b/app/jobs/person/send_add_request_job.rb
index 1538882e62..211c11cbf0 100644
--- a/app/jobs/person/send_add_request_job.rb
+++ b/app/jobs/person/send_add_request_job.rb
@@ -40,7 +40,7 @@ def ask_responsibles
end
def load_responsibles
- Person::AddRequest::IgnoredApprover.approvers(person.primary_group.layer_group)
+ Person::AddRequest::IgnoredApprover.approvers(person_layer)
end
def clear_old_ignored_approvers
@@ -55,4 +55,13 @@ def person
request.person
end
+ def person_layer
+ person.primary_group.try(:layer_group) || last_layer_group
+ end
+
+ def last_layer_group
+ last_role = person.last_non_restricted_role
+ last_role && last_role.group.layer_group
+ end
+
end
diff --git a/app/jobs/sphinx_index_job.rb b/app/jobs/sphinx_index_job.rb
index 9d2d0e552a..79db647eb6 100644
--- a/app/jobs/sphinx_index_job.rb
+++ b/app/jobs/sphinx_index_job.rb
@@ -10,6 +10,28 @@ class SphinxIndexJob < RecurringJob
run_every Settings.sphinx.index.interval.minutes
def perform_internal
- ThinkingSphinx::RakeInterface.new.index
+ if sphinx_local?
+ run_rebuild_task
+ end
end
+
+ private
+
+ def sphinx_local?
+ Hitobito::Application.sphinx_local?
+ end
+
+ def reschedule
+ sphinx_local? ? super : disable_job!
+ end
+
+ def disable_job!
+ delayed_jobs.destroy_all
+ end
+
+ def run_rebuild_task
+ Hitobito::Application.load_tasks
+ Rake::Task['ts:rebuild'].invoke
+ end
+
end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index f966ef097e..dcd8a02dca 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -1,17 +1,17 @@
# encoding: utf-8
-# Copyright (c) 2012-2014, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
class ApplicationMailer < ActionMailer::Base
- HEADERS_TO_SANITIZE = [:to, :cc, :bcc, :from, :sender, :return_path, :reply_to]
+ HEADERS_TO_SANITIZE = [:to, :cc, :bcc, :from, :sender, :return_path, :reply_to].freeze
def mail(headers = {}, &block)
HEADERS_TO_SANITIZE.each do |h|
- if headers.key?(h)
+ if headers.key?(h) && headers[h].present?
headers[h] = IdnSanitizer.sanitize(headers[h])
end
end
@@ -20,15 +20,28 @@ def mail(headers = {}, &block)
private
+ def compose(recipients, content_key)
+ values = values_for_placeholders(content_key)
+ custom_content_mail(recipients, content_key, values)
+ end
+
+ # TODO: deprecate/remove values-parameter and call values_for_placeholders instead
def custom_content_mail(recipients, content_key, values, headers = {})
content = CustomContent.get(content_key)
headers[:to] = use_mailing_emails(recipients)
- headers[:subject] ||= content.subject
+ headers[:subject] ||= content.subject_with_values(values)
mail(headers) do |format|
format.html { render text: content.body_with_values(values) }
end
end
+ def values_for_placeholders(content_key)
+ content = CustomContent.get(content_key)
+ content.placeholders_list.each_with_object({}) do |token, hash|
+ hash[token] = send(:"placeholder_#{token.underscore}")
+ end
+ end
+
def use_mailing_emails(recipients)
if Array(recipients).first.is_a?(Person)
Person.mailing_emails_for(recipients)
diff --git a/app/mailers/event/participation_mailer.rb b/app/mailers/event/participation_mailer.rb
index 330dbe4898..2381292a19 100644
--- a/app/mailers/event/participation_mailer.rb
+++ b/app/mailers/event/participation_mailer.rb
@@ -7,8 +7,9 @@
class Event::ParticipationMailer < ApplicationMailer
- CONTENT_CONFIRMATION = 'event_application_confirmation'
- CONTENT_APPROVAL = 'event_application_approval'
+ CONTENT_CONFIRMATION = 'event_application_confirmation'.freeze
+ CONTENT_APPROVAL = 'event_application_approval'.freeze
+ CONTENT_CANCEL = 'event_cancel_application'.freeze
# Include all helpers that are required directly or indirectly (in decorators)
helper :format, :layout, :auto_link_value
@@ -21,30 +22,63 @@ def confirmation(participation)
filename = Export::Pdf::Participation.filename(participation)
attachments[filename] = Export::Pdf::Participation.render(participation)
- compose(person,
- CONTENT_CONFIRMATION,
- 'recipient-name' => person.greeting_name)
+ compose(person, CONTENT_CONFIRMATION)
end
def approval(participation, recipients)
@participation = participation
+ @recipients = recipients
- compose(recipients,
- CONTENT_APPROVAL,
- 'participant-name' => person.to_s,
- 'recipient-names' => recipients.collect(&:greeting_name).join(', '))
+ compose(@recipients, CONTENT_APPROVAL)
+ end
+
+ def cancel(event, person)
+ @event = event
+ @person = person
+
+ custom_content_mail(@person, CONTENT_CANCEL, values_for_placeholders(CONTENT_CANCEL))
end
private
- def compose(recipients, content_key, values = {})
+ def placeholder_recipient_name
+ person.greeting_name
+ end
+
+ def placeholder_participant_name
+ person.to_s
+ end
+
+ def placeholder_recipient_names
+ @recipients.collect(&:greeting_name).join(', ')
+ end
+
+ def placeholder_event_details
+ if participation.nil?
+ event_without_participation
+ else
+ event_details
+ end
+ end
+
+ def placeholder_application_url
+ link_to(participation_url)
+ end
+
+ def compose(recipients, content_key, values = nil)
# Assert the current mailer's view context is stored as Draper::ViewContext.
# This is done in the #view_context method overriden by Draper.
# Otherwise, decorators will not have access to all helper methods.
view_context
- values['event-details'] = event_details
- values['application-url'] = link_to(participation_url)
+ values = if values
+ values.merge(
+ 'event-details' => event_details,
+ 'application-url' => link_to(participation_url)
+ )
+ else
+ values_for_placeholders(content_key)
+ end
custom_content_mail(recipients, content_key, values)
end
@@ -62,7 +96,7 @@ def event_details
infos << labeled(:cost)
infos << labeled(:description) { event.description.gsub("\n", ' ') }
infos << labeled(:location) { event.location.gsub("\n", ' ') }
- infos << labeled(:contact) { "#{event.contact} #{event.contact.email}" }
+ infos << labeled(:contact) { "#{event.contact} #{event.contact.email}" }
infos << answers_details
infos << additional_information_details
infos << participation_details
@@ -70,6 +104,13 @@ def event_details
end
# rubocop:enable MethodLength, Metrics/AbcSize
+ def event_without_participation
+ infos = []
+ infos << event.name
+ infos << labeled(:dates) { event.dates.map(&:to_s).join(' ') }
+ infos.compact.join(' ')
+ end
+
def labeled(key)
value = event.send(key).presence
if value
@@ -80,15 +121,23 @@ def labeled(key)
end
def answers_details
- if participation.answers.present?
+ answers = load_application_answers
+ if answers.present?
text = ["#{Event::Participation.human_attribute_name(:answers)}:"]
- participation.answers.each do |a|
+ answers.each do |a|
text << "#{a.question.question}: #{a.answer}"
end
text.join(' ')
end
end
+ def load_application_answers
+ participation.answers
+ .joins(:question)
+ .includes(:question)
+ .where(event_questions: { admin: false })
+ end
+
def additional_information_details
if participation.additional_information?
t('activerecord.attributes.event/participation.additional_information') +
@@ -103,11 +152,11 @@ def participation_details
end
def person
- participation.person
+ @person ||= participation.person
end
def event
- participation.event
+ @event ||= participation.event
end
end
diff --git a/app/mailers/event/register_mailer.rb b/app/mailers/event/register_mailer.rb
index edd424e511..a2f8d05960 100644
--- a/app/mailers/event/register_mailer.rb
+++ b/app/mailers/event/register_mailer.rb
@@ -7,22 +7,34 @@
class Event::RegisterMailer < ApplicationMailer
- CONTENT_REGISTER_LOGIN = 'event_register_login'
+ CONTENT_REGISTER_LOGIN = 'event_register_login'.freeze
def register_login(recipient, group, event, token)
+ @recipient = recipient
+ @group = group
+ @event = event
+ @token = token
# This email contains sensitive information and thus
# is only sent to the main email address.
- values = register_login_values(recipient, group, event, token)
- custom_content_mail(recipient.email, CONTENT_REGISTER_LOGIN, values)
+ custom_content_mail(
+ recipient.email,
+ CONTENT_REGISTER_LOGIN,
+ values_for_placeholders(CONTENT_REGISTER_LOGIN)
+ )
end
private
- def register_login_values(recipient, group, event, token)
- url = event_url(group, event, token)
- { 'recipient-name' => recipient.greeting_name,
- 'event-name' => event.to_s,
- 'event-url' => link_to(url) }
+ def placeholder_recipient_name
+ @recipient.greeting_name
+ end
+
+ def placeholder_event_name
+ @event.to_s
+ end
+
+ def placeholder_event_url
+ link_to(event_url(@group, @event, @token))
end
def event_url(group, event, token)
diff --git a/app/mailers/export/event_participations_export_mailer.rb b/app/mailers/export/event_participations_export_mailer.rb
new file mode 100644
index 0000000000..957ae23615
--- /dev/null
+++ b/app/mailers/export/event_participations_export_mailer.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Export::EventParticipationsExportMailer < ApplicationMailer
+
+ CONTENT_EVENT_PARTICIPATIONS_EXPORT = 'content_event_participations_export'.freeze
+
+ def completed(recipient, export_file, export_format)
+ @recipient = recipient
+ @export_file = export_file
+ @export_format = export_format
+
+ attachments["event_participations_export.#{export_format}"] = export_file.read
+ compose(recipient, CONTENT_EVENT_PARTICIPATIONS_EXPORT)
+ end
+
+ private
+
+ def placeholder_recipient_name
+ @recipient.greeting_name
+ end
+end
diff --git a/app/mailers/export/events_export_mailer.rb b/app/mailers/export/events_export_mailer.rb
new file mode 100644
index 0000000000..a3399ab31d
--- /dev/null
+++ b/app/mailers/export/events_export_mailer.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Export::EventsExportMailer < ApplicationMailer
+
+ CONTENT_EVENTS_EXPORT = 'content_events_export'.freeze
+
+ def completed(recipient, export_file, export_format)
+ @recipient = recipient
+ @export_file = export_file
+ @export_format = export_format
+
+ attachments["events_export.#{export_format}"] = export_file.read
+ compose(recipient, CONTENT_EVENTS_EXPORT)
+ end
+
+ private
+
+ def placeholder_recipient_name
+ @recipient.greeting_name
+ end
+end
diff --git a/app/mailers/export/people_export_mailer.rb b/app/mailers/export/people_export_mailer.rb
new file mode 100644
index 0000000000..eb8625d663
--- /dev/null
+++ b/app/mailers/export/people_export_mailer.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Export::PeopleExportMailer < ApplicationMailer
+
+ CONTENT_PEOPLE_EXPORT = 'content_people_export'.freeze
+
+ def completed(recipient, export_file, export_format)
+ @recipient = recipient
+ @export_file = export_file
+ @export_format = export_format
+
+ attachments["people_export.#{export_format}"] = export_file.read
+ compose(recipient, CONTENT_PEOPLE_EXPORT)
+ end
+
+ private
+
+ def placeholder_recipient_name
+ @recipient.greeting_name
+ end
+end
diff --git a/app/mailers/export/subscriptions_mailer.rb b/app/mailers/export/subscriptions_mailer.rb
new file mode 100644
index 0000000000..c9ffe3e128
--- /dev/null
+++ b/app/mailers/export/subscriptions_mailer.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Export::SubscriptionsMailer < ApplicationMailer
+
+ CONTENT_SUBSCRIPTIONS_EXPORT = 'content_subscriptions_export'.freeze
+
+ def completed(recipient, mailing_list, export_file, export_format)
+ @recipient = recipient
+ @mailing_list = mailing_list
+ @export_file = export_file
+ @export_format = export_format
+
+ attachments["subscriptions.#{export_format}"] = export_file.read
+ compose(recipient, CONTENT_SUBSCRIPTIONS_EXPORT)
+ end
+
+ private
+
+ def placeholder_recipient_name
+ @recipient.greeting_name
+ end
+
+ def placeholder_mailing_list_name
+ @mailing_list.name
+ end
+
+end
diff --git a/app/mailers/invoice_mailer.rb b/app/mailers/invoice_mailer.rb
new file mode 100644
index 0000000000..b708d01b37
--- /dev/null
+++ b/app/mailers/invoice_mailer.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+# frozen_string_literal: true
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class InvoiceMailer < ApplicationMailer
+
+ CONTENT_INVOICE_NOTIFICATION = 'content_invoice_notification'.freeze
+
+ def notification(recipient_name, recipient_mail, sender, invoice, invoice_file)
+ @recipient_name = recipient_name
+ @recipient_mail = recipient_mail
+ @sender = sender
+ @invoice = InvoiceDecorator.decorate(invoice)
+
+ attachments[invoice.filename] = invoice_file
+
+ custom_content_mail(@recipient_mail, CONTENT_INVOICE_NOTIFICATION,
+ values_for_placeholders(CONTENT_INVOICE_NOTIFICATION),
+ with_personal_sender(@sender))
+ end
+
+ private
+
+ def placeholder_recipient_name
+ @recipient_name
+ end
+
+ def placeholder_invoice_number
+ @invoice.sequence_number
+ end
+
+ def placeholder_invoice_items
+ InvoiceItemDecorator.decorate_collection(@invoice.invoice_items).map do |item|
+ [
+ item.name,
+ item.description,
+ item.total
+ ].join(' ')
+ end.join(' ' * 2)
+ end
+
+ def placeholder_invoice_total
+ content_tag :table do
+ [:total, :vat].map do |key|
+ content_tag :tr do
+ [content_tag(:th, t("activerecord.attributes.invoice.#{key}")),
+ content_tag(:td, @invoice.send(key))].join
+ end
+ end.join
+ end
+ end
+
+ def placeholder_group_name
+ @sender.primary_group.name
+ end
+
+ def placeholder_payment_information
+ @invoice.invoice_config.payment_information
+ end
+
+ def content_tag(name, content = nil)
+ content = yield if block_given?
+ "<#{name}>#{content}#{name}>"
+ end
+
+end
diff --git a/app/mailers/person/add_request_mailer.rb b/app/mailers/person/add_request_mailer.rb
index ce92afb3f1..70fab51aab 100644
--- a/app/mailers/person/add_request_mailer.rb
+++ b/app/mailers/person/add_request_mailer.rb
@@ -7,75 +7,92 @@
class Person::AddRequestMailer < ApplicationMailer
- CONTENT_ADD_REQUEST_PERSON = 'person_add_request_person'
- CONTENT_ADD_REQUEST_RESPONSIBLES = 'person_add_request_responsibles'
- CONTENT_ADD_REQUEST_APPROVED = 'person_add_request_approved'
- CONTENT_ADD_REQUEST_REJECTED = 'person_add_request_rejected'
+ CONTENT_ADD_REQUEST_PERSON = 'person_add_request_person'.freeze
+ CONTENT_ADD_REQUEST_RESPONSIBLES = 'person_add_request_responsibles'.freeze
+ CONTENT_ADD_REQUEST_APPROVED = 'person_add_request_approved'.freeze
+ CONTENT_ADD_REQUEST_REJECTED = 'person_add_request_rejected'.freeze
attr_reader :add_request
delegate :body, :person, :requester, to: :add_request
def ask_person_to_add(add_request)
- @add_request = add_request
- values = person_mail_values
- compose(person, CONTENT_ADD_REQUEST_PERSON, values, requester)
+ @add_request = add_request
+ @answer_request_url = link_to_request
+ @recipient = person
+ compose(person, CONTENT_ADD_REQUEST_PERSON, requester)
end
def ask_responsibles(add_request, responsibles)
- @add_request = add_request
- recipient_names = responsibles.collect(&:greeting_name).join(', ')
- values = responsible_mail_values(recipient_names)
- compose(responsibles, CONTENT_ADD_REQUEST_RESPONSIBLES, values, requester)
+ @add_request = add_request
+ @answer_request_url = link_to_add_requests
+ @recipients = responsibles
+ compose(responsibles, CONTENT_ADD_REQUEST_RESPONSIBLES, requester)
end
def approved(person, body, requester, user)
@add_request = body.person_add_requests.build(person: person, requester: requester)
- values = approved_mail_values(user)
- compose(requester, CONTENT_ADD_REQUEST_APPROVED, values, user)
+ @recipient = requester
+ @user = user
+ compose(requester, CONTENT_ADD_REQUEST_APPROVED, user)
end
def rejected(person, body, requester, user)
@add_request = body.person_add_requests.build(person: person, requester: requester)
- values = rejected_mail_values(user)
- compose(requester, CONTENT_ADD_REQUEST_REJECTED, values, user)
+ @recipient = requester
+ @user = user
+ compose(requester, CONTENT_ADD_REQUEST_REJECTED, user)
end
private
- def compose(recipients, content_key, values, sender)
- values['request-body'] = link_to(add_request.body_label, body_url)
+ def compose(recipients, content_key, sender)
+ values = values_for_placeholders(content_key)
custom_content_mail(recipients, content_key, values, with_personal_sender(sender))
end
+ def placeholder_request_body
+ link_to(add_request.body_label, body_url)
+ end
+
+ def placeholder_recipient_name
+ @recipient.greeting_name
+ end
+
+ def placeholder_requester_name
+ requester.full_name
+ end
+
+ def placeholder_requester_roles
+ roles_as_string(add_request.requester_full_roles)
+ end
+
+ def placeholder_answer_request_url
+ @answer_request_url
+ end
+
+ def placeholder_recipient_names
+ @recipients.collect(&:greeting_name).join(', ')
+ end
+
+ def placeholder_person_name
+ person.full_name
+ end
- def person_mail_values
- { 'recipient-name' => person.greeting_name,
- 'requester-name' => requester.full_name,
- 'requester-roles' => roles_as_string(add_request.requester_full_roles),
- 'answer-request-url' => link_to_request }
+ def placeholder_approver_name
+ @user.full_name
end
- def responsible_mail_values(recipient_names)
- { 'recipient-names' => recipient_names,
- 'person-name' => person.full_name,
- 'requester-name' => requester.full_name,
- 'requester-roles' => roles_as_string(add_request.requester_full_roles),
- 'answer-request-url' => link_to_add_requests }
+ def placeholder_rejecter_name
+ @user.full_name
end
- def approved_mail_values(user)
- { 'recipient-name' => requester.greeting_name,
- 'person-name' => person.full_name,
- 'approver-name' => user.full_name,
- 'approver-roles' => roles_as_string(layer_full_roles(user)) }
+ def placeholder_approver_roles
+ roles_as_string(layer_full_roles(@user))
end
- def rejected_mail_values(user)
- { 'recipient-name' => requester.greeting_name,
- 'person-name' => person.full_name,
- 'rejecter-name' => user.full_name,
- 'rejecter-roles' => roles_as_string(layer_full_roles(user)) }
+ def placeholder_rejecter_roles
+ roles_as_string(layer_full_roles(@user))
end
def roles_as_string(roles)
@@ -85,7 +102,7 @@ def roles_as_string(roles)
def layer_full_roles(person)
person.roles.includes(:group).select do |r|
r.group.layer_group_id == add_request.person_layer.try(:id) &&
- (r.class.permissions & [:layer_and_below_full, :layer_full]).present?
+ (r.class.permissions & [:layer_and_below_full, :layer_full]).present?
end
end
@@ -107,7 +124,7 @@ def body_url
when Group then group_url(id: body.id)
when Event then group_event_url(group_id: body.groups.first.id, id: body.id)
when MailingList then group_mailing_list_url(group_id: body.group_id, id: body.id)
- else fail(ArgumentError, "Unknown body type #{body.class}")
+ else raise ArgumentError, "Unknown body type #{body.class}"
end
end
diff --git a/app/mailers/person/login_mailer.rb b/app/mailers/person/login_mailer.rb
index c8620b7c9d..09668d28ae 100644
--- a/app/mailers/person/login_mailer.rb
+++ b/app/mailers/person/login_mailer.rb
@@ -7,10 +7,14 @@
class Person::LoginMailer < ApplicationMailer
- CONTENT_LOGIN = 'send_login'
+ CONTENT_LOGIN = 'send_login'.freeze
def login(recipient, sender, token)
- values = content_values(recipient, sender, token)
+ @recipient = recipient
+ @sender = sender
+ @token = token
+
+ values = values_for_placeholders(CONTENT_LOGIN)
# This email contains sensitive information and thus
# is only sent to the main email address.
@@ -19,11 +23,16 @@ def login(recipient, sender, token)
private
- def content_values(recipient, sender, token)
- url = login_url(token)
- { 'recipient-name' => recipient.greeting_name,
- 'sender-name' => sender.to_s,
- 'login-url' => link_to(url) }
+ def placeholder_recipient_name
+ @recipient.greeting_name
+ end
+
+ def placeholder_sender_name
+ @sender.to_s
+ end
+
+ def placeholder_login_url
+ link_to(login_url(@token))
end
def login_url(token)
diff --git a/app/models/cantons.rb b/app/models/cantons.rb
index 5a573b970e..a11ed9628b 100644
--- a/app/models/cantons.rb
+++ b/app/models/cantons.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2014, insieme Schweiz. This file is part of
-# hitobito_insieme and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_insieme.
+# https://github.com/hitobito/hitobito.
module Cantons
diff --git a/app/models/concerns/categorized_tags.rb b/app/models/concerns/categorized_tags.rb
index 59e628c274..27a4adb60a 100644
--- a/app/models/concerns/categorized_tags.rb
+++ b/app/models/concerns/categorized_tags.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
+# https://github.com/hitobito/hitobito.
module CategorizedTags
diff --git a/app/models/countries.rb b/app/models/countries.rb
index ee1ba3c500..01239ab365 100644
--- a/app/models/countries.rb
+++ b/app/models/countries.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2015, insieme Schweiz. This file is part of
-# hitobito_insieme and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_insieme.
+# https://github.com/hitobito/hitobito.
module Countries
diff --git a/app/models/custom_content.rb b/app/models/custom_content.rb
index 4687b55432..df6a305163 100644
--- a/app/models/custom_content.rb
+++ b/app/models/custom_content.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -29,7 +29,7 @@ class CustomContent < ActiveRecord::Base
class << self
def get(key)
- find_by_key!(key)
+ find_by!(key: key)
end
end
@@ -53,10 +53,20 @@ def placeholder_token(key)
"{#{key}}"
end
+ def subject_with_values(placeholders = {})
+ replace_placeholders(subject.dup, placeholders)
+ end
+
def body_with_values(placeholders = {})
+ replace_placeholders(body.dup, placeholders)
+ end
+
+ private
+
+ def replace_placeholders(string, placeholders)
check_placeholders_exist(placeholders)
- placeholders_list.each_with_object(body.dup) do |placeholder, output|
+ placeholders_list.each_with_object(string) do |placeholder, output|
token = placeholder_token(placeholder)
if output.include?(token)
output.gsub!(token, placeholders.fetch(placeholder))
@@ -64,15 +74,13 @@ def body_with_values(placeholders = {})
end
end
- private
-
def as_list(placeholders)
placeholders.to_s.split(',').collect(&:strip)
end
def assert_required_placeholders_are_used
placeholders_required_list.each do |placeholder|
- unless body.to_s.include?(placeholder_token(placeholder))
+ unless [subject, body].any? { |str| str.to_s.include?(placeholder_token(placeholder)) }
errors.add(:body, :placeholder_missing, placeholder: placeholder_token(placeholder))
end
end
@@ -81,9 +89,9 @@ def assert_required_placeholders_are_used
def check_placeholders_exist(placeholders)
non_existing = (placeholders.keys - placeholders_list).presence
if non_existing
- fail(ArgumentError,
- "Placeholder(s) #{non_existing.join(', ')} given, " \
- 'but not defined for this custom content')
+ raise(ArgumentError,
+ "Placeholder(s) #{non_existing.join(', ')} given, " \
+ 'but not defined for this custom content')
end
end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index db27322c94..87b90655a9 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -37,9 +37,10 @@
# signature_confirmation_text :string
# creator_id :integer
# updater_id :integer
+# applications_cancelable :boolean default(FALSE), not null
#
-class Event < ActiveRecord::Base
+class Event < ActiveRecord::Base # rubocop:disable Metrics/ClassLength:
# This statement is required because these classes would not be loaded correctly otherwise.
# The price we pay for using classes as namespace.
@@ -64,12 +65,15 @@ class Event < ActiveRecord::Base
self.used_attributes = [:name, :motto, :cost, :maximum_participants, :contact_id,
:description, :location, :application_opening_at,
:application_closing_at, :application_conditions,
- :external_applications]
+ :external_applications, :applications_cancelable,
+ :signature, :signature_confirmation, :signature_confirmation_text,
+ :required_contact_attrs, :hidden_contact_attrs]
# All participation roles that exist for this event
self.role_types = [Event::Role::Leader,
Event::Role::AssistantLeader,
Event::Role::Cook,
+ Event::Role::Helper,
Event::Role::Treasurer,
Event::Role::Speaker,
Event::Role::Participant]
@@ -83,6 +87,7 @@ class Event < ActiveRecord::Base
# The class used for the kind_id
self.kind_class = nil
+
model_stamper
stampable stamper_class_name: :person,
deleter: false
@@ -98,6 +103,9 @@ class Event < ActiveRecord::Base
has_many :dates, -> { order(:start_at) }, dependent: :destroy, validate: true
has_many :questions, dependent: :destroy, validate: true
+ has_many :application_questions, -> { where(admin: false) }, class_name: 'Event::Question'
+ has_many :admin_questions, -> { where(admin: true) }, class_name: 'Event::Question'
+
has_many :participations, dependent: :destroy
has_many :people, through: :participations
@@ -120,14 +128,20 @@ class Event < ActiveRecord::Base
length: { allow_nil: true, maximum: 2**16 - 1 }
validate :assert_type_is_allowed_for_groups
validate :assert_application_closing_is_after_opening
-
+ validate :assert_required_contact_attrs_valid
+ validate :assert_hidden_contact_attrs_valid
### CALLBACKS
before_validation :set_self_in_nested
+ before_validation :set_signature, if: :signature_confirmation?
+ accepts_nested_attributes_for :dates, :application_questions, :admin_questions,
+ allow_destroy: true
- accepts_nested_attributes_for :dates, :questions, allow_destroy: true
+ ### SERIALIZED ATTRIBUTES
+ serialize :required_contact_attrs, Array
+ serialize :hidden_contact_attrs, Array
### CLASS METHODS
@@ -136,9 +150,9 @@ class << self
# Default scope for event lists
def list
order_by_date.
- order(:name).
- preload_all_dates.
- uniq
+ order(:name).
+ preload_all_dates.
+ uniq
end
def preload_all_dates
@@ -171,15 +185,15 @@ def with_group_id(group_ids)
def upcoming
midnight = Time.zone.now.midnight
joins(:dates).
- where('event_dates.start_at >= ? OR event_dates.finish_at >= ?', midnight, midnight)
+ where('event_dates.start_at >= ? OR event_dates.finish_at >= ?', midnight, midnight)
end
# Events that are open for applications.
def application_possible
today = Time.zone.today
where('events.application_opening_at IS NULL OR events.application_opening_at <= ?', today).
- where('events.application_closing_at IS NULL OR events.application_closing_at >= ?', today).
- where('events.maximum_participants IS NULL OR events.maximum_participants <= 0 OR ' \
+ where('events.application_closing_at IS NULL OR events.application_closing_at >= ?', today).
+ where('events.maximum_participants IS NULL OR events.maximum_participants <= 0 OR ' \
'events.participant_count < events.maximum_participants')
end
@@ -207,7 +221,7 @@ def all_types
# Return the event type with the given sti_name or raise an exception if not found
def find_event_type!(sti_name)
type = all_types.detect { |t| t.sti_name == sti_name }
- fail ActiveRecord::RecordNotFound, "No event type '#{sti_name}' found" if type.nil?
+ raise ActiveRecord::RecordNotFound, "No event type '#{sti_name}' found" if type.nil?
type
end
@@ -218,7 +232,7 @@ def participant_types
# Return the role type with the given sti_name or raise an exception if not found
def find_role_type!(sti_name)
type = role_types.detect { |t| t.sti_name == sti_name }
- fail ActiveRecord::RecordNotFound, "No role '#{sti_name}' found" if type.nil?
+ raise ActiveRecord::RecordNotFound, "No role '#{sti_name}' found" if type.nil?
type
end
end
@@ -226,6 +240,8 @@ def find_role_type!(sti_name)
### INSTANCE METHODS
+ delegate :participant_types, :find_role_type!, to: :singleton_class
+
def to_s(_format = :default)
name
end
@@ -257,16 +273,43 @@ def course_kind?
kind_class == Event::Kind && kind.present?
end
+ def duplicate # rubocop:disable Metrics/MethodLength splitting this up does not make it better
+ dup.tap do |event|
+ event.groups = groups
+ event.state = nil
+ event.application_opening_at = nil
+ event.application_closing_at = nil
+ event.participant_count = 0
+ event.applicant_count = 0
+ event.teamer_count = 0
+ application_questions.each do |q|
+ event.application_questions << q.dup
+ end
+ admin_questions.each do |q|
+ event.admin_questions << q.dup
+ end
+ end
+ end
+
+ # Overwrite to handle improper characters
+ def save(*args)
+ super
+ rescue ActiveRecord::StatementInvalid => e
+ raise e unless e.original_exception.message =~ /Incorrect string value/
+ errors.add(:base, :emoji_suspected)
+ false
+ end
+
private
def assert_type_is_allowed_for_groups
- if groups.present?
- master = groups.first
- if groups.any? { |g| g.class != master.class }
- errors.add(:group_ids, :must_have_same_type)
- elsif type && !master.class.event_types.collect(&:sti_name).include?(type)
- errors.add(:type, :type_not_allowed)
- end
+ master = groups.try(:first)
+ return unless master
+
+ if groups.any? { |g| g.class != master.class }
+ errors.add(:group_ids, :must_have_same_type)
+ elsif type && !master.class.event_types.collect(&:sti_name).include?(type)
+ errors.add(:type, :type_not_allowed)
end
end
@@ -278,7 +321,43 @@ def assert_application_closing_is_after_opening
def set_self_in_nested
# don't try to set self in frozen nested attributes (-> marked for destroy)
- (dates + questions).each { |e| e.event = self unless e.frozen? }
+ (dates + application_questions + admin_questions).each do |e|
+ e.event = self unless e.frozen?
+ end
+ end
+
+ def valid_contact_attr?(attr)
+ (ParticipationContactData.contact_attrs +
+ ParticipationContactData.contact_associations).
+ map(&:to_s).include?(attr.to_s)
+ end
+
+ def assert_required_contact_attrs_valid
+ required_contact_attrs.map(&:to_s).each do |a|
+ unless valid_contact_attr?(a) &&
+ ParticipationContactData.contact_associations.
+ map(&:to_s).exclude?(a)
+ errors.add(:base, :contact_attr_invalid, attribute: a)
+ end
+ if hidden_contact_attrs.include?(a)
+ errors.add(:base, :contact_attr_hidden_required, attribute: a)
+ end
+ end
+ end
+
+ def assert_hidden_contact_attrs_valid
+ hidden_contact_attrs.map(&:to_sym).each do |a|
+ unless valid_contact_attr?(a)
+ errors.add(:base, :contact_attr_invalid, attribute: a)
+ end
+ if ParticipationContactData.mandatory_contact_attrs.include?(a)
+ errors.add(:base, :contact_attr_mandatory, attribute: a)
+ end
+ end
+ end
+
+ def set_signature
+ self.signature = true
end
end
diff --git a/app/models/event/answer.rb b/app/models/event/answer.rb
index d3655927b1..6b1bf52181 100644
--- a/app/models/event/answer.rb
+++ b/app/models/event/answer.rb
@@ -18,6 +18,8 @@ class Event::Answer < ActiveRecord::Base
attr_writer :answer_required
+ delegate :admin?, to: :question
+
belongs_to :participation
belongs_to :question
@@ -25,7 +27,7 @@ class Event::Answer < ActiveRecord::Base
validates_by_schema
validates :question_id, uniqueness: { scope: :participation_id }
validates :answer, presence: { if: lambda do
- question.required? && participation.enforce_required_answers
+ question && question.required? && participation.enforce_required_answers
end }
validate :assert_answer_is_in_choice_items
diff --git a/app/models/event/attachment_uploader.rb b/app/models/event/attachment_uploader.rb
index 8648a519a5..41c32e16c4 100644
--- a/app/models/event/attachment_uploader.rb
+++ b/app/models/event/attachment_uploader.rb
@@ -5,29 +5,8 @@
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-class Event::AttachmentUploader < CarrierWave::Uploader::Base
+class Event::AttachmentUploader < Uploader::Base
- EXTENSION_WHITE_LIST = Settings.event.attachments.file_extensions.split(/\s+/)
-
- # Choose what kind of storage to use for this uploader:
- storage :file
-
- class << self
- def accept_extensions
- EXTENSION_WHITE_LIST.collect { |e| ".#{e}" }.join(', ')
- end
- end
-
- # Override the directory where uploaded files will be stored.
- # This is a sensible default for uploaders that are meant to be mounted:
- def store_dir
- "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
- end
-
- # Add a white list of extensions which are allowed to be uploaded.
- # For images you might use something like this:
- def extension_white_list
- EXTENSION_WHITE_LIST
- end
+ self.allowed_extensions = Settings.event.attachments.file_extensions.split(/\s+/)
end
diff --git a/app/models/event/course.rb b/app/models/event/course.rb
index 13abd83997..1834b12e4c 100644
--- a/app/models/event/course.rb
+++ b/app/models/event/course.rb
@@ -37,6 +37,7 @@
# signature_confirmation_text :string
# creator_id :integer
# updater_id :integer
+# applications_cancelable :boolean default(FALSE), not null
#
class Event::Course < Event
@@ -44,12 +45,13 @@ class Event::Course < Event
# This statement is required because this class would not be loaded otherwise.
require_dependency 'event/course/role/participant'
- self.used_attributes += [:number, :kind_id, :state, :priorization, :group_ids, :requires_approval,
- :signature, :signature_confirmation, :signature_confirmation_text]
+ self.used_attributes += [:number, :kind_id, :state, :priorization, :group_ids,
+ :requires_approval, :display_booking_info]
self.role_types = [Event::Role::Leader,
Event::Role::AssistantLeader,
Event::Role::Cook,
+ Event::Role::Helper,
Event::Role::Treasurer,
Event::Role::Speaker,
Event::Course::Role::Participant]
@@ -63,8 +65,6 @@ class Event::Course < Event
validates :kind_id, presence: true, if: -> { used_attributes.include?(:kind_id) }
- before_validation :set_signature, if: :signature_confirmation?
-
def label_detail
label = used_attributes.include?(:kind_id) ? "#{kind.short_name} " : ''
@@ -89,17 +89,11 @@ def start_date
end
def init_questions
- if questions.blank?
- Event::Question.global.each do |q|
- questions << q.dup
+ if application_questions.blank?
+ Event::Question.application.global.each do |q|
+ application_questions << q.dup
end
end
end
- private
-
- def set_signature
- self[:signature] = true
- end
-
end
diff --git a/app/models/event/kind.rb b/app/models/event/kind.rb
index 960b6a2734..611da3c9cf 100644
--- a/app/models/event/kind.rb
+++ b/app/models/event/kind.rb
@@ -58,11 +58,16 @@ def qualifying?
end
def qualification_kinds(category, role)
- QualificationKind.includes(:translations).
- joins(:event_kind_qualification_kinds).
- where(event_kind_qualification_kinds: { event_kind_id: id,
- category: category,
- role: role })
+ QualificationKind.
+ includes(:translations).
+ joins(:event_kind_qualification_kinds).
+ where(event_kind_qualification_kinds: { event_kind_id: id,
+ category: category,
+ role: role })
+ end
+
+ def grouped_qualification_kind_ids(category, role)
+ event_kind_qualification_kinds.grouped_qualification_kind_ids(category, role)
end
# Soft destroy if events exist, otherwise hard destroy
diff --git a/app/models/event/kind_qualification_kind.rb b/app/models/event/kind_qualification_kind.rb
index de5a6cd113..079196bbdf 100644
--- a/app/models/event/kind_qualification_kind.rb
+++ b/app/models/event/kind_qualification_kind.rb
@@ -13,12 +13,24 @@
# qualification_kind_id :integer not null
# category :string not null
# role :string not null
+# grouping :integer
#
class Event::KindQualificationKind < ActiveRecord::Base
- CATEGORIES = %w(qualification precondition prolongation)
- ROLES = %w(participant leader)
+ CATEGORIES = %w(qualification precondition prolongation).freeze
+ ROLES = %w(participant leader).freeze
+
+ class << self
+
+ def grouped_qualification_kind_ids(category, role)
+ where(category: category, role: role).
+ pluck(:grouping, :qualification_kind_id).
+ group_by(&:first).
+ map { |_, v| v.map(&:last) }
+ end
+
+ end
### ASSOCIATIONS
diff --git a/app/models/event/participatable.rb b/app/models/event/participatable.rb
index 1fdbc3ea77..f572e92453 100644
--- a/app/models/event/participatable.rb
+++ b/app/models/event/participatable.rb
@@ -19,7 +19,7 @@ def participations_for(*role_types)
where(event_roles: { type: role_types.map(&:sti_name) }).
includes(:person).
references(:person).
- order_by_role(self.class).
+ order_by_role(self).
merge(Person.order_by_name).
uniq
end
@@ -44,10 +44,6 @@ def participation_role_labels
pluck(:label)
end
- def participant_types
- self.class.participant_types
- end
-
private
# All members of the leading team (non-participants)
diff --git a/app/models/event/participation.rb b/app/models/event/participation.rb
index e4f7692d00..24a60aba17 100644
--- a/app/models/event/participation.rb
+++ b/app/models/event/participation.rb
@@ -94,12 +94,14 @@ def upcoming
### INSTANCE METHODS
def init_answers
- return if answers.present?
-
- event.questions.each do |q|
- a = q.answers.new
- a.question = q # without this, only the id is set
- answers << a
+ answers.tap do |list|
+ event.questions.each do |q|
+ unless list.find { |a| a.question_id == q.id }
+ a = q.answers.new
+ a.question = q # without this, only the id is set
+ list << a
+ end
+ end
end
end
diff --git a/app/models/event/participation_contact_data.rb b/app/models/event/participation_contact_data.rb
new file mode 100644
index 0000000000..1a45b7201d
--- /dev/null
+++ b/app/models/event/participation_contact_data.rb
@@ -0,0 +1,143 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Pfadibewegung Schweiz. This file is part of
+# hitobito_youth and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito_youth.
+
+class Event::ParticipationContactData
+
+ attr_reader :person
+
+ T_PERSON_ATTRS = 'activerecord.attributes.person.'.freeze
+
+ class_attribute :mandatory_contact_attrs,
+ :contact_attrs,
+ :contact_associations
+
+ self.mandatory_contact_attrs = [:email, :first_name, :last_name]
+
+ self.contact_attrs = [:first_name, :last_name, :nickname, :company_name,
+ :email, :address, :zip_code, :town,
+ :country, :gender, :birthday]
+
+ self.contact_associations = [:additional_emails, :phone_numbers, :social_accounts]
+
+ delegate(*contact_attrs, to: :person)
+ delegate(*contact_associations, to: :person)
+
+ delegate :t, to: I18n
+
+ delegate :gender_label, :column_for_attribute, :timeliness_cache_attribute,
+ :has_attribute?, to: :person
+
+ delegate :layer_group, to: :event
+
+ include ActiveModel::Validations
+
+ validate :assert_required_contact_attrs_valid
+ validate :assert_person_attrs_valid
+
+ class << self
+
+ delegate :reflect_on_association, :human_attribute_name, to: Person
+
+ def base_class
+ self
+ end
+
+ def demodulized_route_keys
+ nil
+ end
+
+ end
+
+ def initialize(event, person, model_params = {})
+ @model_params = model_params
+ @event = event
+ @person = person
+ person.attributes = model_params if model_params.present?
+ end
+
+ def save
+ valid? && person.save
+ end
+
+ def parent
+ event
+ end
+
+ def method_missing(method)
+ return person.send(method) if method =~ /^.*_came_from_user\?/
+ return person.send(method) if method =~ /^.*_before_type_cast/
+
+ super(method)
+ end
+
+ def show_attr?(a)
+ attribute_keys.include?(a)
+ end
+
+ def required_attr?(a)
+ required_attributes.include?(a.to_s)
+ end
+
+ def attribute_keys
+ self.class.contact_attrs - hidden_contact_attrs
+ end
+
+ def hidden_contact_attrs
+ event.hidden_contact_attrs.collect(&:to_sym)
+ end
+
+ def respond_to?(attr)
+ responds = super(attr)
+ responds ? true : person.respond_to?(attr)
+ end
+
+ def new_record?
+ true
+ end
+
+ def persisted?
+ false
+ end
+
+ def to_model
+ self
+ end
+
+ def to_key
+ nil
+ end
+
+ def required_attributes
+ @required_attributes ||= event.required_contact_attrs +
+ self.class.mandatory_contact_attrs.map(&:to_s)
+ end
+
+ private
+
+ attr_reader :model_params, :event
+
+ def assert_required_contact_attrs_valid
+ required_attributes.each do |a|
+ if model_params[a.to_s].blank?
+ errors.add(a, t('errors.messages.blank'))
+ end
+ end
+ end
+
+ def assert_person_attrs_valid
+ unless person.valid?
+ collect_person_errors
+ end
+ end
+
+ def collect_person_errors
+ person.errors.full_messages.each do |m|
+ errors.add(:base, m)
+ end
+ end
+
+end
diff --git a/app/models/event/question.rb b/app/models/event/question.rb
index 9c55527ed0..b4f4412b42 100644
--- a/app/models/event/question.rb
+++ b/app/models/event/question.rb
@@ -1,9 +1,10 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
+
# == Schema Information
#
# Table name: event_questions
@@ -12,8 +13,9 @@
# event_id :integer
# question :string
# choices :string
-# multiple_choices :boolean default(FALSE)
-# required :boolean
+# multiple_choices :boolean not null, default(FALSE)
+# required :boolean not null, default(FALSE)
+# admin :boolean not null, default(FALSE)
#
class Event::Question < ActiveRecord::Base
@@ -29,6 +31,9 @@ class Event::Question < ActiveRecord::Base
scope :global, -> { where(event_id: nil) }
+ scope :application, -> { where(admin: false) }
+ scope :admin, -> { where(admin: true) }
+
def choice_items
choices.to_s.split(',').collect(&:strip)
diff --git a/app/models/event/role/helper.rb b/app/models/event/role/helper.rb
new file mode 100644
index 0000000000..68eb7713c5
--- /dev/null
+++ b/app/models/event/role/helper.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, CVJM. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+# == Schema Information
+#
+# Table name: event_roles
+#
+# id :integer not null, primary key
+# type :string not null
+# participation_id :integer not null
+# label :string
+#
+
+# Kueche
+class Event::Role::Helper < Event::Role
+
+ self.permissions = [:participations_read]
+
+ self.kind = :helper
+
+end
diff --git a/app/models/group.rb b/app/models/group.rb
index 2914be0128..765c9b0750 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -1,9 +1,10 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
+
# == Schema Information
#
# Table name: groups
@@ -36,8 +37,8 @@ class Group < ActiveRecord::Base
MINIMAL_SELECT = %w(id name type parent_id lft rgt layer_group_id deleted_at).
collect { |a| "groups.#{a}" }
- include Group::Types
include Group::NestedSet
+ include Group::Types
include Contactable
acts_as_paranoid
@@ -61,6 +62,7 @@ class Group < ActiveRecord::Base
before_save :reset_contact_info
+
# Root group may not be destroyed
protect_if :root?
protect_if :children_without_deleted
@@ -81,12 +83,21 @@ class Group < ActiveRecord::Base
has_many :mailing_lists, dependent: :destroy
has_many :subscriptions, as: :subscriber, dependent: :destroy
+ has_many :notes, as: :subject, dependent: :destroy
+
has_many :person_add_requests,
foreign_key: :body_id,
inverse_of: :body,
class_name: 'Person::AddRequest::Group',
dependent: :destroy
+ has_one :invoice_config, dependent: :destroy
+ has_many :invoices, dependent: :destroy
+ has_many :invoice_articles, dependent: :destroy
+ has_many :invoice_items, through: :invoices
+
+ after_create :create_invoice_config, if: :layer?
+
### VALIDATIONS
validates_by_schema
@@ -151,7 +162,7 @@ def with_layer
end
# create alias to call it again
- alias_method :hard_destroy, :really_destroy!
+ alias hard_destroy really_destroy!
def really_destroy!
# run nested_set callback on hard destroy
destroy_descendants_without_paranoia
@@ -189,4 +200,8 @@ def destroy_descendants_with_paranoia
end
alias_method_chain :destroy_descendants, :paranoia
+ def create_invoice_config
+ create_invoice_config!
+ end
+
end
diff --git a/app/models/group/types.rb b/app/models/group/types.rb
index 8474c0d04b..c70fac4d54 100644
--- a/app/models/group/types.rb
+++ b/app/models/group/types.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -24,9 +24,8 @@ module Group::Types
self.event_types = [Event]
- after_create :set_layer_group_id
- after_update :set_layer_group_id
- after_create :create_default_children
+ after_save :set_layer_group_id
+ after_save :create_default_children
validate :assert_type_is_allowed_for_parent, on: :create
end
@@ -34,6 +33,9 @@ module Group::Types
private
def create_default_children
+ # hack to have after_save ordering for this semantical after_create callback
+ return if created_at < Time.zone.now - 10.seconds
+
default_children.each do |group_type|
child = group_type.new(name: group_type.label)
child.parent = self
@@ -50,7 +52,10 @@ def assert_type_is_allowed_for_parent
def set_layer_group_id
layer_id = self.class.layer ? id : parent.layer_group_id
unless layer_id == layer_group_id
- update_column(:layer_group_id, layer_id)
+ self_and_descendants.
+ where(layer_group_id: layer_group_id).
+ update_all(layer_group_id: layer_id)
+ self.layer_group_id = layer_id
end
end
diff --git a/app/models/invoice.rb b/app/models/invoice.rb
new file mode 100644
index 0000000000..6d4d2e69e1
--- /dev/null
+++ b/app/models/invoice.rb
@@ -0,0 +1,193 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+# == Schema Information
+#
+# Table name: invoices
+#
+# id :integer not null, primary key
+# title :string(255) not null
+# sequence_number :string(255) not null
+# state :string(255) default("draft"), not null
+# esr_number :string(255) not null
+# description :text(65535)
+# recipient_email :string(255)
+# recipient_address :text(65535)
+# sent_at :date
+# due_at :date
+# group_id :integer not null
+# recipient_id :integer not null
+# total :decimal(12, 2)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class Invoice < ActiveRecord::Base
+ include I18nEnums
+
+ attr_accessor :recipient_ids
+
+ STATES = %w(draft issued sent payed overdue reminded cancelled).freeze
+ DUE_SINCE = %w(one_day one_week one_month).freeze
+
+ belongs_to :group
+ belongs_to :recipient, class_name: 'Person'
+ has_many :invoice_items, dependent: :destroy
+ has_many :payments, dependent: :destroy
+ has_many :payment_reminders, dependent: :destroy
+
+ before_validation :set_sequence_number, on: :create, if: :group
+ before_validation :set_esr_number, on: :create, if: :group
+ before_validation :set_dates, on: :update
+ before_validation :set_self_in_nested
+ before_validation :recalculate
+
+ validates :state, inclusion: { in: STATES }
+ validates :due_at, timeliness: { after: :sent_at }, presence: true, if: :sent?
+ validate :assert_sendable?, unless: :recipient_id?
+
+ before_create :set_recipient_fields, if: :recipient
+ after_create :increment_sequence_number
+
+
+ accepts_nested_attributes_for :invoice_items, allow_destroy: true
+
+ i18n_enum :state, STATES
+
+ validates_by_schema
+
+ scope :list, -> { order(:sequence_number) }
+ scope :one_day, -> { where('due_at < ?', 1.day.ago.to_date) }
+ scope :one_week, -> { where('due_at < ?', 1.week.ago.to_date) }
+ scope :one_month, -> { where('due_at < ?', 1.month.ago.to_date) }
+ scope :visible, -> { where.not(state: :cancelled) }
+
+ STATES.each do |state|
+ scope state.to_sym, -> { where(state: state) }
+ define_method "#{state}?" do
+ self.state == state
+ end
+ end
+
+ def self.to_contactables(invoices)
+ invoices.collect do |invoice|
+ next if invoice.recipient_address.blank?
+ Person.new(address: invoice.recipient_address)
+ end.compact
+ end
+
+ def multi_create # rubocop:disable Metrics/MethodLength
+ Invoice.transaction do
+ all_saved = recipients.all? do |recipient|
+ invoice = self.class.new(attributes.merge(recipient_id: recipient.id))
+ invoice_items.each do |invoice_item|
+ invoice.invoice_items.build(invoice_item.attributes)
+ end
+ invoice.save
+ end
+ raise ActiveRecord::Rollback unless all_saved
+ all_saved
+ end
+ end
+
+ def calculated
+ [:total, :cost, :vat].collect do |field|
+ [field, invoice_items.to_a.sum(&field)]
+ end.to_h
+ end
+
+ def recalculate
+ self.total = invoice_items.to_a.sum(&:total) || 0
+ end
+
+ def to_s
+ "#{title}(#{sequence_number}): #{total}"
+ end
+
+ def reminder_sent?
+ payment_reminders.present?
+ end
+
+ def remindable?
+ %w(sent reminded overdue).include?(state)
+ end
+
+ def recipients
+ Person.where(id: recipient_ids.to_s.split(','))
+ end
+
+ def recipient_name
+ recipient.try(:greeting_name) || recipient_address.split("\n").first
+ end
+
+ def filename(extension)
+ format('%s-%s.%s', self.class.model_name.human, sequence_number, extension)
+ end
+
+ def invoice_config
+ group.invoice_config
+ end
+
+ def state
+ ActiveSupport::StringInquirer.new(self[:state])
+ end
+
+ def amount_open
+ total - payments.sum(:amount)
+ end
+
+ def amount_paid
+ payments.sum(:amount)
+ end
+
+ private
+
+ def set_self_in_nested
+ invoice_items.each { |item| item.invoice = self }
+ end
+
+ def set_sequence_number
+ self.sequence_number = [group_id, invoice_config.sequence_number].join('-')
+ end
+
+ def set_esr_number
+ self.esr_number = sequence_number
+ end
+
+ def set_dates
+ self.sent_at ||= Time.zone.today if sent?
+ if sent? || issued?
+ self.issued_at ||= Time.zone.today
+ self.due_at ||= issued_at + invoice_config.due_days.days
+ end
+ end
+
+ def set_recipient_fields
+ self.recipient_email = recipient.email
+ self.recipient_address = build_recipient_address
+ end
+
+ def item_invalid?(attributes)
+ !InvoiceItem.new(attributes.merge(invoice: self)).valid?
+ end
+
+ def increment_sequence_number
+ invoice_config.increment!(:sequence_number) # rubocop:disable Rails/SkipsModelValidations
+ end
+
+ def build_recipient_address
+ [recipient.full_name,
+ recipient.address,
+ [recipient.zip_code, recipient.town].compact.join(' / '),
+ recipient.country].compact.join("\n")
+ end
+
+ def assert_sendable?
+ if recipient_email.blank? && recipient_address.blank?
+ errors.add(:base, :recipient_address_or_email_required)
+ end
+ end
+end
diff --git a/app/models/invoice_article.rb b/app/models/invoice_article.rb
new file mode 100644
index 0000000000..9b763ae7fb
--- /dev/null
+++ b/app/models/invoice_article.rb
@@ -0,0 +1,50 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+# == Schema Information
+#
+# Table name: invoice_articles
+#
+# id :integer not null, primary key
+# number :string(255)
+# name :string(255) not null
+# description :text(65535)
+# category :string(255)
+# unit_cost :decimal(12, 2)
+# vat_rate :decimal(5, 2)
+# cost_center :string(255)
+# account :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+# group_id :integer not null
+#
+
+class InvoiceArticle < ActiveRecord::Base
+
+ belongs_to :group
+
+ validates :name, presence: true, uniqueness: { scope: :group_id }
+ validates :number, presence: true, uniqueness: { scope: :group_id }
+
+ validates_by_schema
+
+ def self.categories
+ pluck(:category).uniq
+ end
+
+ def self.cost_centers
+ pluck(:cost_center).uniq
+ end
+
+ def self.accounts
+ pluck(:account).uniq
+ end
+
+ def to_s
+ [number, name].compact.join(' - ')
+ end
+
+end
diff --git a/app/models/invoice_config.rb b/app/models/invoice_config.rb
new file mode 100644
index 0000000000..eebe914294
--- /dev/null
+++ b/app/models/invoice_config.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+# == Schema Information
+#
+# Table name: invoice_configs
+#
+# id :integer not null, primary key
+# sequence_number :integer default(1), not null
+# due_days :integer default(30), not null
+# group_id :integer not null
+# contact_id :integer
+# page_size :integer default(15)
+# address :text(65535)
+# payment_information :text(65535)
+#
+
+class InvoiceConfig < ActiveRecord::Base
+ belongs_to :group, class_name: 'Group'
+ belongs_to :contact, class_name: 'Person'
+
+ validates :group_id, uniqueness: true
+
+ validates_by_schema
+
+ def to_s
+ "#{group.name} - Invoice Config" # TODO: determine proper string representation
+ end
+
+end
diff --git a/app/models/invoice_item.rb b/app/models/invoice_item.rb
new file mode 100644
index 0000000000..9c11d6a99f
--- /dev/null
+++ b/app/models/invoice_item.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+# == Schema Information
+#
+# Table name: invoice_items
+#
+# id :integer not null, primary key
+# invoice_id :integer not null
+# name :string(255) not null
+# description :text(65535)
+# vat_rate :decimal(5, 2)
+# unit_cost :decimal(12, 2) not null
+# count :integer default(1), not null
+#
+
+class InvoiceItem < ActiveRecord::Base
+
+ belongs_to :invoice
+
+ scope :list, -> { order(:name) }
+
+ validates_by_schema
+
+ def to_s
+ "#{name}: #{total} (#{amount} / #{vat})"
+ end
+
+ def total
+ cost + vat
+ end
+
+ def cost
+ unit_cost ? unit_cost * count : 0
+ end
+
+ def vat
+ vat_rate ? cost * (vat_rate / 100) : 0
+ end
+
+end
diff --git a/app/models/label_format.rb b/app/models/label_format.rb
index 64d17d73a2..c1c767c9fe 100644
--- a/app/models/label_format.rb
+++ b/app/models/label_format.rb
@@ -18,6 +18,9 @@
# count_vertical :integer not null
# padding_top :float not null
# padding_left :float not null
+# person_id :integer
+# nickname :boolean default(FALSE), not null
+# pp_post :string(23)
#
class LabelFormat < ActiveRecord::Base
@@ -33,6 +36,8 @@ def available_page_sizes
has_many :people, foreign_key: :last_label_format_id, dependent: :nullify
+ belongs_to :person
+
validates :name, presence: true, length: { maximum: 255, allow_nil: true }
validates :page_size, inclusion: available_page_sizes
@@ -47,17 +52,17 @@ def available_page_sizes
validates :padding_top, numericality: { less_than: :height, if: :height }
validates :padding_left, numericality: { less_than: :width, if: :width }
- after_save :sweep_cache
- after_destroy :sweep_cache
+ scope :for_user, ->(user) { where('user_id = ? OR user_id IS null', user.id) }
- class << self
- def all_as_hash
- Rails.cache.fetch("label_formats_#{I18n.locale}") do
- LabelFormat.list.each_with_object({}) { |f, result| result[f.id] = f.to_s }
- end
+ def self.for_person(person)
+ if person.show_global_label_formats?
+ where('person_id = ? OR person_id IS NULL', person.id)
+ else
+ where(person_id: person.id)
end
end
+
def to_s(_format = :default)
"#{name} (#{page_size}, #{dimensions})"
end
@@ -70,12 +75,4 @@ def page_layout
landscape ? :landscape : :portrait
end
- private
-
- def sweep_cache
- Settings.application.languages.to_hash.keys.each do |lang|
- Rails.cache.delete("label_formats_#{lang}")
- end
- end
-
end
diff --git a/app/models/location.rb b/app/models/location.rb
index 27951a28fd..283a1b5e01 100644
--- a/app/models/location.rb
+++ b/app/models/location.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
-# hitobito_pbs and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_pbs.
+# https://github.com/hitobito/hitobito.
# == Schema Information
#
diff --git a/app/models/mailing_list.rb b/app/models/mailing_list.rb
index 2c87154fb0..8a3a53775e 100644
--- a/app/models/mailing_list.rb
+++ b/app/models/mailing_list.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -60,7 +60,7 @@ def exclude_person(person)
subscriptions.where(subscriber_id: person.id,
subscriber_type: Person.sti_name,
excluded: false).
- destroy_all
+ destroy_all
if subscribed?(person)
sub = subscriptions.new
@@ -72,36 +72,38 @@ def exclude_person(person)
def people(people_scope = Person.only_public_data)
people_scope.
- joins(people_joins).
- joins(subscription_joins).
- where(subscriptions: { mailing_list_id: id }).
- where("people.id NOT IN (#{excluded_person_subscribers.to_sql})").
- where(suscriber_conditions).
- uniq
+ joins(people_joins).
+ joins(subscription_joins).
+ where(subscriptions: { mailing_list_id: id }).
+ where("people.id NOT IN (#{excluded_person_subscribers.to_sql})").
+ where(suscriber_conditions).
+ uniq
end
private
def people_joins
- 'LEFT JOIN roles ON people.id = roles.person_id ' \
- 'LEFT JOIN groups ON roles.group_id = groups.id ' \
- 'LEFT JOIN event_participations ON event_participations.person_id = people.id ' \
- 'LEFT JOIN taggings AS people_taggings ' \
- "ON people_taggings.taggable_type = 'Person' " \
- 'AND people_taggings.taggable_id = people.id'
+ <<-SQL.strip_heredoc.split.map(&:strip).join(' ')
+ LEFT JOIN roles ON people.id = roles.person_id
+ LEFT JOIN groups ON roles.group_id = groups.id
+ LEFT JOIN event_participations ON event_participations.person_id = people.id
+ LEFT JOIN taggings AS people_taggings
+ ON people_taggings.taggable_type = 'Person'
+ AND people_taggings.taggable_id = people.id
+ SQL
end
def subscription_joins
- ', subscriptions ' \
- 'LEFT JOIN groups sub_groups ' \
- "ON subscriptions.subscriber_type = 'Group'" \
- 'AND subscriptions.subscriber_id = sub_groups.id ' \
- 'LEFT JOIN related_role_types ' \
- "ON related_role_types.relation_type = 'Subscription' " \
- 'AND related_role_types.relation_id = subscriptions.id ' \
- 'LEFT JOIN taggings AS subscriptions_taggings ' \
- "ON subscriptions_taggings.taggable_type = 'Subscription' " \
- 'AND subscriptions_taggings.taggable_id = subscriptions.id'
+ # the comma is needed because it is not a JOIN, but a second "FROM"
+ <<-SQL.strip_heredoc.split.map(&:strip).join(' ')
+ , subscriptions
+ LEFT JOIN groups sub_groups
+ ON subscriptions.subscriber_type = 'Group' AND subscriptions.subscriber_id = sub_groups.id
+ LEFT JOIN related_role_types
+ ON related_role_types.relation_type = 'Subscription' AND related_role_types.relation_id = subscriptions.id
+ LEFT JOIN taggings AS subscriptions_taggings
+ ON subscriptions_taggings.taggable_type = 'Subscription' AND subscriptions_taggings.taggable_id = subscriptions.id
+ SQL
end
def suscriber_conditions
@@ -122,9 +124,9 @@ def person_subscribers(condition)
def excluded_person_subscribers
Subscription.select(:subscriber_id).
- where(mailing_list_id: id,
- excluded: true,
- subscriber_type: Person.sti_name)
+ where(mailing_list_id: id,
+ excluded: true,
+ subscriber_type: Person.sti_name)
end
def group_subscribers(condition)
@@ -148,7 +150,7 @@ def event_subscribers(condition)
def assert_mail_name_is_not_protected
if mail_name? && application_retriever_name
- if mail_name.downcase == application_retriever_name.split('@', 2).first.downcase
+ if mail_name.casecmp(application_retriever_name.split('@', 2).first).zero?
errors.add(:mail_name, :not_allowed, mail_name: mail_name)
end
end
diff --git a/app/models/note.rb b/app/models/note.rb
new file mode 100644
index 0000000000..4ae76dbd54
--- /dev/null
+++ b/app/models/note.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+# == Schema Information
+#
+# Table name: notes
+#
+# id :integer not null, primary key
+# subject_id :integer not null
+# author_id :integer not null
+# text :text
+# created_at :datetime
+# updated_at :datetime
+# subject_type :string
+#
+
+class Note < ActiveRecord::Base
+
+ ### ASSOCIATIONS
+
+ belongs_to :subject, polymorphic: true
+ belongs_to :author, class_name: 'Person'
+
+ ### VALIDATIONS
+
+ validates_by_schema
+ validates :text, presence: true
+
+ scope :list, -> { order(created_at: :desc) }
+
+ class << self
+ def in_or_layer_below(group)
+ joins('LEFT JOIN roles ' \
+ "ON roles.person_id = notes.subject_id AND notes.subject_type = '#{Person.sti_name}'").
+ joins('INNER JOIN groups ' \
+ "ON (groups.id = notes.subject_id AND notes.subject_type = '#{Group.sti_name}') " \
+ 'OR (groups.id = roles.group_id)').
+ where(roles: { deleted_at: nil },
+ groups: { deleted_at: nil, layer_group_id: group.layer_group_id }).
+ where('groups.lft >= :lft AND groups.rgt <= :rgt', lft: group.lft, rgt: group.rgt).
+ uniq
+ end
+ end
+
+ def to_s
+ text.to_s.delete("\n").truncate(10)
+ end
+
+end
diff --git a/app/models/payment.rb b/app/models/payment.rb
new file mode 100644
index 0000000000..aff17061e6
--- /dev/null
+++ b/app/models/payment.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Payment < ActiveRecord::Base
+
+ belongs_to :invoice
+
+ before_validation :set_received_at
+ after_create :update_invoice
+
+ scope :list, -> { order(created_at: :desc) }
+
+ validates_by_schema
+
+ def group
+ invoice.group
+ end
+
+ private
+
+ def update_invoice
+ if amount >= invoice.total
+ invoice.update(state: :payed)
+ end
+ end
+
+ def set_received_at
+ self.received_at ||= Time.zone.today
+ end
+
+end
diff --git a/app/models/payment_reminder.rb b/app/models/payment_reminder.rb
new file mode 100644
index 0000000000..de22bcbfc9
--- /dev/null
+++ b/app/models/payment_reminder.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+# == Schema Information
+#
+# Table name: payment_reminders
+#
+# id :integer not null, primary key
+# invoice_id :integer not null
+# message :text(65535)
+# due_at :date not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class PaymentReminder < ActiveRecord::Base
+
+ belongs_to :invoice
+
+ validate :assert_invoice_remindable
+ validates :due_at, uniqueness: { scope: :invoice_id },
+ timeliness: { after: :invoice_due_at, allow_blank: true, type: :date },
+ if: :invoice_remindable?
+
+ after_create :update_invoice
+
+ validates_by_schema
+
+ delegate :due_at, :remindable?, to: :invoice, prefix: true
+
+ scope :list, -> { order(created_at: :desc) }
+
+ def to_s
+ I18n.l(due_at)
+ end
+
+ def group
+ invoice.group
+ end
+
+ private
+
+ def update_invoice
+ invoice.update(state: :overdue, due_at: due_at)
+ end
+
+ def assert_invoice_remindable
+ unless invoice_remindable?
+ errors.add(:invoice, :invalid)
+ end
+ end
+
+end
diff --git a/app/models/people_filter.rb b/app/models/people_filter.rb
index ec2f04840e..837394e9eb 100644
--- a/app/models/people_filter.rb
+++ b/app/models/people_filter.rb
@@ -1,45 +1,57 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
+
# == Schema Information
#
# Table name: people_filters
#
-# id :integer not null, primary key
-# name :string not null
-# group_id :integer
-# group_type :string
+# id :integer not null, primary key
+# name :string not null
+# group_id :integer
+# group_type :string
+# filter_chain :text
+# range :string
+# created_at :datetime
+# updated_at :datetime
#
class PeopleFilter < ActiveRecord::Base
- include RelatedRoleType::Assigners
+ RANGES = %w(deep layer group).freeze
+ serialize :filter_chain, Person::Filter::Chain
belongs_to :group
- has_many :related_role_types, as: :relation, dependent: :destroy
-
-
validates_by_schema
validates :name, uniqueness: { scope: [:group_id, :group_type] }
+ validates :range, inclusion: { in: RANGES }
-
- default_scope { order(:name).includes(:related_role_types) }
+ scope :list, -> { order(:name) }
def to_s(_format = :default)
name
end
+ def filter_chain=(value)
+ if value.is_a?(Hash)
+ super(Person::Filter::Chain.new(value))
+ else
+ super
+ end
+ end
+
class << self
def for_group(group)
- where('group_id = ? OR group_type = ? OR ' \
- '(group_id IS NULL AND group_type IS NULL)',
- group.id,
- group.type)
+ includes(:group)
+ .where('group_id = ? OR group_type = ? OR ' \
+ '(group_id IS NULL AND group_type IS NULL)',
+ group.id,
+ group.type)
end
end
diff --git a/app/models/person.rb b/app/models/person.rb
index c5f11d3333..c14a9f7019 100644
--- a/app/models/person.rb
+++ b/app/models/person.rb
@@ -8,56 +8,61 @@
#
# Table name: people
#
-# id :integer not null, primary key
-# first_name :string
-# last_name :string
-# company_name :string
-# nickname :string
-# company :boolean default(FALSE), not null
-# email :string
-# address :string(1024)
-# zip_code :string
-# town :string
-# country :string
-# gender :string(1)
-# birthday :date
-# additional_information :text
-# contact_data_visible :boolean default(FALSE), not null
-# created_at :datetime
-# updated_at :datetime
-# encrypted_password :string
-# reset_password_token :string
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string
-# last_sign_in_ip :string
-# picture :string
-# last_label_format_id :integer
-# creator_id :integer
-# updater_id :integer
-# primary_group_id :integer
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# authentication_token :string
+# id :integer not null, primary key
+# first_name :string(255)
+# last_name :string(255)
+# company_name :string(255)
+# nickname :string(255)
+# company :boolean default(FALSE), not null
+# email :string(255)
+# address :string(1024)
+# zip_code :string(255)
+# town :string(255)
+# country :string(255)
+# gender :string(1)
+# birthday :date
+# additional_information :text(65535)
+# contact_data_visible :boolean default(FALSE), not null
+# created_at :datetime
+# updated_at :datetime
+# encrypted_password :string(255)
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# picture :string(255)
+# last_label_format_id :integer
+# creator_id :integer
+# updater_id :integer
+# primary_group_id :integer
+# failed_attempts :integer default(0)
+# locked_at :datetime
+# authentication_token :string(255)
+# show_global_label_formats :boolean default(TRUE), not null
#
class Person < ActiveRecord::Base
- PUBLIC_ATTRS = [:id, :first_name, :last_name, :nickname, :company_name, :company,
- :email, :address, :zip_code, :town, :country, :gender, :birthday,
- :picture, :primary_group_id]
+ PUBLIC_ATTRS = [ # rubocop:disable Style/MutableConstant meant to be extended in wagons
+ :id, :first_name, :last_name, :nickname, :company_name, :company,
+ :email, :address, :zip_code, :town, :country, :gender, :birthday,
+ :picture, :primary_group_id
+ ]
- INTERNAL_ATTRS = [:authentication_token, :contact_data_visible, :created_at, :creator_id,
- :current_sign_in_at, :current_sign_in_ip, :encrypted_password, :id,
- :last_label_format_id, :failed_attempts, :last_sign_in_at, :last_sign_in_ip,
- :locked_at, :remember_created_at, :reset_password_token,
- :reset_password_sent_at, :sign_in_count, :updated_at, :updater_id]
-
- GENDERS = %w(m w)
+ INTERNAL_ATTRS = [ # rubocop:disable Style/MutableConstant meant to be extended in wagons
+ :authentication_token, :contact_data_visible, :created_at, :creator_id,
+ :current_sign_in_at, :current_sign_in_ip, :encrypted_password, :id,
+ :last_label_format_id, :failed_attempts, :last_sign_in_at, :last_sign_in_ip,
+ :locked_at, :remember_created_at, :reset_password_token,
+ :reset_password_sent_at, :sign_in_count, :updated_at, :updater_id,
+ :show_global_label_formats
+ ]
+ GENDERS = %w(m w).freeze
# define devise before other modules
devise :database_authenticatable,
@@ -114,8 +119,7 @@ class Person < ActiveRecord::Base
has_many :add_requests, dependent: :destroy
- has_many :notes, class_name: 'Note',
- dependent: :destroy
+ has_many :notes, dependent: :destroy, as: :subject
has_many :authored_notes, class_name: 'Note',
foreign_key: 'author_id',
@@ -124,6 +128,8 @@ class Person < ActiveRecord::Base
belongs_to :primary_group, class_name: 'Group'
belongs_to :last_label_format, class_name: 'LabelFormat'
+ has_many :label_formats, dependent: :destroy
+
accepts_nested_attributes_for :relations_to_tails, allow_destroy: true
### VALIDATIONS
@@ -217,7 +223,7 @@ def default_group_id
primary_group_id || groups.first.try(:id) || Group.root.id
end
- def years
+ def years # rubocop:disable Metrics/AbcSize Age calculation is complex
return unless birthday?
now = Time.zone.now.to_date
@@ -254,6 +260,15 @@ def save(*args)
false
end
+ def layer_group
+ primary_group.layer_group if primary_group
+ end
+
+ def finance_groups
+ groups_with_permission(:finance).
+ flat_map(&:layer_group)
+ end
+
private
def override_blank_email
diff --git a/app/models/person/add_request.rb b/app/models/person/add_request.rb
index 928cfe40f6..f38ea914f2 100644
--- a/app/models/person/add_request.rb
+++ b/app/models/person/add_request.rb
@@ -35,8 +35,11 @@ class Person::AddRequest < ActiveRecord::Base
class << self
def for_layer(layer_group)
- joins(person: :primary_group).
- where(groups: { layer_group_id: layer_group.id })
+ joins(:person).
+ joins('LEFT JOIN groups AS primary_groups ON primary_groups.id = people.primary_group_id').
+ where('primary_groups.layer_group_id = ? OR people.id IN (?)',
+ layer_group.id,
+ ::Group::DeletedPeople.deleted_for(layer_group).select(:id))
end
end
@@ -56,7 +59,7 @@ def body_label
end
def person_layer
- person.primary_group.try(:layer_group)
+ person.primary_group.try(:layer_group) || last_layer_group
end
def requester_full_roles
@@ -66,4 +69,11 @@ def requester_full_roles
end
end
+ private
+
+ def last_layer_group
+ last_role = person.last_non_restricted_role
+ last_role && last_role.group.layer_group
+ end
+
end
diff --git a/app/models/person/add_request/event.rb b/app/models/person/add_request/event.rb
index acbe20a2bd..9fc72a1629 100644
--- a/app/models/person/add_request/event.rb
+++ b/app/models/person/add_request/event.rb
@@ -24,10 +24,15 @@ class Person::AddRequest::Event < Person::AddRequest
validates :role_type, presence: true
def to_s(_format = :default)
- group = body.groups.first
- event_label = body_label
- group_label = "#{group.model_name.human} #{group}"
- self.class.human_attribute_name(:label, body: event_label, group: group_label)
+ if body
+ group = body.groups.first
+ event_label = body_label
+ group_label = "#{group.model_name.human} #{group}"
+ self.class.human_attribute_name(:label, body: event_label, group: group_label)
+ else
+ # event was deleted in the mean time
+ self.class.human_attribute_name(:deleted_event)
+ end
end
end
diff --git a/app/models/person/groups.rb b/app/models/person/groups.rb
index 30f7f33211..65cf3efbc5 100644
--- a/app/models/person/groups.rb
+++ b/app/models/person/groups.rb
@@ -28,7 +28,7 @@ def non_restricted_groups
roles_with_groups.to_a.reject { |r| r.class.restricted? }.collect(&:group).uniq
end
- # All groups where this person has the given permission(s).
+ # All groups where this person has the given permission.
def groups_with_permission(permission)
@groups_with_permission ||= {}
@groups_with_permission[permission] ||= begin
@@ -55,6 +55,13 @@ def above_groups_where_visible_from
groups_where_visible_from_above.collect(&:hierarchy).flatten.uniq
end
+ def last_non_restricted_role
+ return nil if roles.exists?
+
+ restricted = Role.all_types.select(&:restricted?).collect(&:sti_name)
+ roles.with_deleted.where.not(type: restricted).order(deleted_at: :desc).first
+ end
+
private
# Helper method to access the roles association,
diff --git a/app/models/person/note.rb b/app/models/person/note.rb
deleted file mode 100644
index 3efc8d84f5..0000000000
--- a/app/models/person/note.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# encoding: utf-8
-
-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
-
-# == Schema Information
-#
-# Table name: person_notes
-#
-# id :integer not null, primary key
-# person_id :integer not null
-# author_id :integer not null
-# text :text
-# created_at :datetime
-# updated_at :datetime
-#
-class Person::Note < ActiveRecord::Base
-
- default_scope { order(created_at: :desc) }
-
- ### ASSOCIATIONS
-
- belongs_to :person
- belongs_to :author, class_name: 'Person'
-
- ### VALIDATIONS
-
- validates :text, presence: true
-
- def to_s
- text.present? && text.sub("\n", ' ')[0..9] + (text.length > 10 ? '...' : '')
- end
-
-end
diff --git a/app/models/person/picture_uploader.rb b/app/models/person/picture_uploader.rb
index 4e0ee30d5c..42fbb1a9be 100644
--- a/app/models/person/picture_uploader.rb
+++ b/app/models/person/picture_uploader.rb
@@ -5,15 +5,13 @@
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
-class Person::PictureUploader < CarrierWave::Uploader::Base
+class Person::PictureUploader < Uploader::Base
MAX_DIMENSION = 8000
- EXTENSION_WHITE_LIST = %w(jpg jpeg gif png)
- include CarrierWave::MiniMagick
+ self.allowed_extensions = %w(jpg jpeg gif png)
- # Choose what kind of storage to use for this uploader:
- storage :file
+ include CarrierWave::MiniMagick
# Process files as they are uploaded:
process :validate_dimensions
@@ -24,17 +22,6 @@ class Person::PictureUploader < CarrierWave::Uploader::Base
process resize_to_fill: [32, 32]
end
- class << self
- def accept_extensions
- EXTENSION_WHITE_LIST.collect { |e| ".#{e}" }.join(',')
- end
- end
-
- # Override the directory where uploaded files will be stored.
- # This is a sensible default for uploaders that are meant to be mounted:
- def store_dir
- "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
- end
# Provide a default URL as a default if there hasn't been a file uploaded:
def default_url
@@ -45,12 +32,6 @@ def png_name
['profil', version_name].compact.join('_') + '.png'
end
- # Add a white list of extensions which are allowed to be uploaded.
- # For images you might use something like this:
- def extension_white_list
- EXTENSION_WHITE_LIST
- end
-
private
# check for images that are larger than you probably want
diff --git a/app/models/qualification.rb b/app/models/qualification.rb
index 2f8b91fd81..b37331f845 100644
--- a/app/models/qualification.rb
+++ b/app/models/qualification.rb
@@ -54,7 +54,7 @@ def active(date = nil)
def reactivateable(date = nil)
date ||= Time.zone.today
joins(:qualification_kind).
- where('qualifications.start_at <= ?', date).
+ where('qualifications.start_at <= ?', date).
where('qualifications.finish_at IS NULL OR ' \
'(qualification_kinds.reactivateable IS NULL AND ' \
' qualifications.finish_at >= ?) OR ' \
diff --git a/app/models/role/types.rb b/app/models/role/types.rb
index 33ff2589ea..90c2ac8288 100644
--- a/app/models/role/types.rb
+++ b/app/models/role/types.rb
@@ -14,7 +14,7 @@ module Role::Types
Permissions = [:admin,
:layer_and_below_full, :layer_and_below_read, :layer_full, :layer_read,
:group_and_below_full, :group_and_below_read, :group_full, :group_read,
- :contact_data, :approve_applications]
+ :contact_data, :approve_applications, :finance]
# If a role contains the first permission, the second one is automatically active as well
PermissionImplications = { layer_and_below_full: :layer_and_below_read,
diff --git a/app/models/uploader/base.rb b/app/models/uploader/base.rb
new file mode 100644
index 0000000000..f7f3e33b61
--- /dev/null
+++ b/app/models/uploader/base.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class Uploader::Base < CarrierWave::Uploader::Base
+
+ class_attribute :allowed_extensions
+
+ # Choose what kind of storage to use for this uploader
+ storage :file
+
+ class << self
+ def accept_extensions
+ allowed_extensions.collect { |e| ".#{e}" }.join(', ')
+ end
+ end
+
+ # Add a white list of extensions which are allowed to be uploaded.
+ # For images you might use something like this:
+ def extension_white_list
+ allowed_extensions
+ end
+
+ def base_store_dir
+ 'uploads'
+ end
+
+ # Override the directory where uploaded files will be stored.
+ # This is a sensible default for uploaders that are meant to be mounted:
+ def store_dir
+ "#{base_store_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ end
+
+end
diff --git a/app/serializers/person_serializer.rb b/app/serializers/person_serializer.rb
index 7a5316ef2e..33eb86f487 100644
--- a/app/serializers/person_serializer.rb
+++ b/app/serializers/person_serializer.rb
@@ -3,40 +3,41 @@
#
# Table name: people
#
-# id :integer not null, primary key
-# first_name :string
-# last_name :string
-# company_name :string
-# nickname :string
-# company :boolean default(FALSE), not null
-# email :string
-# address :string(1024)
-# zip_code :string
-# town :string
-# country :string
-# gender :string(1)
-# birthday :date
-# additional_information :text
-# contact_data_visible :boolean default(FALSE), not null
-# created_at :datetime
-# updated_at :datetime
-# encrypted_password :string
-# reset_password_token :string
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string
-# last_sign_in_ip :string
-# picture :string
-# last_label_format_id :integer
-# creator_id :integer
-# updater_id :integer
-# primary_group_id :integer
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# authentication_token :string
+# id :integer not null, primary key
+# first_name :string
+# last_name :string
+# company_name :string
+# nickname :string
+# company :boolean default(FALSE), not null
+# email :string
+# address :string(1024)
+# zip_code :string
+# town :string
+# country :string
+# gender :string(1)
+# birthday :date
+# additional_information :text
+# contact_data_visible :boolean default(FALSE), not null
+# created_at :datetime
+# updated_at :datetime
+# encrypted_password :string
+# reset_password_token :string
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string
+# last_sign_in_ip :string
+# picture :string
+# last_label_format_id :integer
+# creator_id :integer
+# updater_id :integer
+# primary_group_id :integer
+# failed_attempts :integer default(0)
+# locked_at :datetime
+# authentication_token :string
+# show_global_label_formats :boolean default(TRUE), not null
#
# Copyright (c) 2014, CEVI Regionalverband ZH-SH-GL. This file is part of
diff --git a/app/utils/changelog_reader.rb b/app/utils/changelog_reader.rb
index cbe28c5130..4f608bc57d 100644
--- a/app/utils/changelog_reader.rb
+++ b/app/utils/changelog_reader.rb
@@ -22,6 +22,7 @@ def changelogs
end
private
+
def collect_changelog_data
changelog_files_content = read_changelog_files(changelog_file_paths)
parse_changelog_lines(changelog_files_content)
@@ -49,7 +50,7 @@ def read_changelog_files(files_path)
end
def changelog_file_paths
- file_paths = ["CHANGELOG.md"]
+ file_paths = ['CHANGELOG.md']
Wagons.all.each do |w|
file_paths << "#{w.root}/CHANGELOG.md"
end
@@ -58,7 +59,7 @@ def changelog_file_paths
def changelog_header_line(h)
h.strip!
- h[/^## [^\s]+ ((\d+\.)?(\*|\d+))$/, 1]
+ h[/^## [^\s]+ ((\d+\.)?(\*|x|\d+))$/i, 1]
end
def changelog_entry_line(e)
diff --git a/app/utils/changelog_version.rb b/app/utils/changelog_version.rb
index 51e9241ff7..9c68016005 100644
--- a/app/utils/changelog_version.rb
+++ b/app/utils/changelog_version.rb
@@ -6,24 +6,21 @@
# https://github.com/hitobito/hitobito.
class ChangelogVersion
- attr_accessor :major_version, :minor_version, :log_entries
+ attr_accessor :major_version, :minor_version, :log_entries, :version
def initialize(header_line)
- values = header_line.split(".")
+ values = header_line.split('.')
+ @version = header_line
@major_version = values.first.to_i
- @minor_version = values.second.to_i
- @log_entries = []
+ @minor_version = values.second.casecmp('x') == 0 ? Float::INFINITY : values.second.to_i
+ @log_entries = []
end
def <=>(other)
- [self.major_version, self.minor_version] <=> [other.major_version, other.minor_version]
+ [major_version, minor_version] <=> [other.major_version, other.minor_version]
end
def label
"Version #{version}"
end
-
- def version
- "#{major_version}.#{minor_version}"
- end
-end
\ No newline at end of file
+end
diff --git a/app/views/changelog/index.html.haml b/app/views/changelog/index.html.haml
index b0e9726fdd..dd8e9e2733 100644
--- a/app/views/changelog/index.html.haml
+++ b/app/views/changelog/index.html.haml
@@ -7,6 +7,6 @@
- ChangelogReader.changelog.each do |changelog_entry|
%h2 #{changelog_entry.label}
- - changelog_entry.log_entries.each do |change|
- %ul
- %li #{change}
\ No newline at end of file
+ %ul
+ - changelog_entry.log_entries.each do |change|
+ %li #{change}
diff --git a/app/views/event/application_market/_popover_waiting_list.html.haml b/app/views/event/application_market/_popover_waiting_list.html.haml
index 1d6a764af9..787a04b3ce 100644
--- a/app/views/event/application_market/_popover_waiting_list.html.haml
+++ b/app/views/event/application_market/_popover_waiting_list.html.haml
@@ -1,8 +1,7 @@
-- # encoding: utf-8
- # Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
-- # hitobito_pbs and licensed under the Affero General Public License version 3
+- # hitobito and licensed under the Affero General Public License version 3
- # or later. See the COPYING file at the top-level directory or at
-- # https://github.com/hitobito/hitobito_pbs.
+- # https://github.com/hitobito/hitobito.
%p.muted= t('.waiting_list_info', event_kind: @event.kind)
%br/
diff --git a/app/views/event/kinds/_form.html.haml b/app/views/event/kinds/_form.html.haml
index 45938c1fd9..91fc057f00 100644
--- a/app/views/event/kinds/_form.html.haml
+++ b/app/views/event/kinds/_form.html.haml
@@ -12,8 +12,8 @@
= field_set_tag(t('.qualifications.for_participants')) do
%p= t('.help_select_qualifications')
- = labeled_qualification_kinds_field(f, @preconditions, :precondition, :participant,
- Event::Kind.human_attribute_name(:preconditions))
+ = render 'precondition_fields', f: f
+
= labeled_qualification_kinds_field(f, @qualification_kinds, :qualification, :participant,
Event::Kind.human_attribute_name(:qualification_kinds))
= labeled_qualification_kinds_field(f, @prolongations, :prolongation, :participant,
diff --git a/app/views/event/kinds/_precondition_fields.html.haml b/app/views/event/kinds/_precondition_fields.html.haml
new file mode 100644
index 0000000000..77397e2a10
--- /dev/null
+++ b/app/views/event/kinds/_precondition_fields.html.haml
@@ -0,0 +1,28 @@
+= f.labeled(Event::Kind.human_attribute_name(:preconditions)) do
+ - kinds = entry.qualification_kinds('precondition', 'participant').group_by(&:id)
+
+ #precondition_summary.inline{style: 'padding-bottom: 5px;',
+ data: { and: t('event.kinds.qualifications.and'),
+ or: t('event.kinds.qualifications.or') } }
+ - entry.grouped_qualification_kind_ids('precondition', 'participant').each_with_index do |ids, index|
+ .precondition-grouping
+ - ids.each do |id|
+ = hidden_field_tag("event_kind[precondition_qualification_kinds][#{index}][qualification_kind_ids][]", id)
+
+ - if index > 0
+ %span.muted= t('event.kinds.qualifications.or')
+ = ids.collect { |id| kinds[id].first.to_s }.sort.to_sentence
+ = link_to(icon(:trash), '#', class: 'remove-precondition-grouping')
+
+
+ = link_to(t('.add_precondition_grouping'), '#', id: 'add_precondition_grouping')
+
+ #precondition_fields.hide
+ = select_tag('event_kind_precondition_kind_ids',
+ options_from_collection_for_select(@preconditions, :id, :to_s),
+ multiple: true,
+ class: 'span6')
+ .help-block
+ .btn-group
+ = button_tag(t('.add_precondition'), class: 'btn btn-default')
+ = link_to(t('global.button.cancel'), '#', class: 'link cancel')
diff --git a/app/views/event/lists/_nav_left_courses.html.haml b/app/views/event/lists/_nav_left_courses.html.haml
index a14f5ffcb2..2224a4e3b3 100644
--- a/app/views/event/lists/_nav_left_courses.html.haml
+++ b/app/views/event/lists/_nav_left_courses.html.haml
@@ -1,7 +1,7 @@
%ul.nav-left-list
- if kind_used?
- @grouped_events.keys.each do |kind|
- %li= link_to(kind, "##{CGI.escape(kind)}")
+ %li= link_to(kind, "##{CGI.escape(kind)}", data: { turbolinks: false })
- else
= render 'nav_left_events'
diff --git a/app/views/event/lists/_nav_left_events.html.haml b/app/views/event/lists/_nav_left_events.html.haml
index baedfc7956..ad5f0e15fd 100644
--- a/app/views/event/lists/_nav_left_events.html.haml
+++ b/app/views/event/lists/_nav_left_events.html.haml
@@ -2,4 +2,4 @@
- @grouped_events.keys.collect(&:split).group_by(&:last).each do |year, months|
%li.divider= year
- months.each do |month, year|
- %li= link_to(month, "##{CGI.escape("#{month} #{year}")}")
+ %li= link_to(month, "##{CGI.escape("#{month} #{year}")}", data: { turbolinks: false })
diff --git a/app/views/event/lists/courses.html.haml b/app/views/event/lists/courses.html.haml
index ff20a21895..481f5de63b 100644
--- a/app/views/event/lists/courses.html.haml
+++ b/app/views/event/lists/courses.html.haml
@@ -1,4 +1,4 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
@@ -6,7 +6,7 @@
- title t('.title')
- manager = can?(:list_all, Event::Course)
-%p= t('.explanation')
+%p= t('.explanation') unless can?(:list_all, Event::Course)
- content_for :toolbar do
- if manager
@@ -16,19 +16,20 @@
%p
- if can?(:export_list, Event::Course)
- = action_button(t('event.lists.courses.csv_export_button'), params.merge(format: :csv), :download)
+ = Dropdown::Event::EventsExport.new(self, params).to_s
= render_extensions :buttons
#main
%section
- = grouped_table(@grouped_events, manager ? 4 : 3) do |event|
+ = grouped_table(@grouped_events, manager || display_any_booking_info? ? 4 : 3) do |event|
%td
%strong
= event.labeled_link
%td= event.dates_full
+ - if manager || display_any_booking_info?
+ %td= manager || event.display_booking_info? ? event.booking_info : ''
- if manager
- %td= event.booking_info
%td= event.state_translated
- else
- %td= button_action_event_apply(event)
+ %td.center= button_action_event_apply(event)
diff --git a/app/views/event/lists/events.html.haml b/app/views/event/lists/events.html.haml
index 85cdea0da0..1fb327bd53 100644
--- a/app/views/event/lists/events.html.haml
+++ b/app/views/event/lists/events.html.haml
@@ -15,4 +15,4 @@
= event.labeled_link
%td= event.dates_full
%td= event.description_short
- %td= button_action_event_apply(event)
+ %td.center= button_action_event_apply(event)
diff --git a/app/views/event/participation_contact_datas/_address_fields.html.haml b/app/views/event/participation_contact_datas/_address_fields.html.haml
new file mode 100644
index 0000000000..8311df6a86
--- /dev/null
+++ b/app/views/event/participation_contact_datas/_address_fields.html.haml
@@ -0,0 +1,19 @@
+-# Copyright (c) 2012-2017, Pfadibewegung Schweiz. This file is part of
+-# hitobito_pbs and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito_pbs.
+
+= f.labeled_text_area(:address, rows: 2) if entry.show_attr?(:address)
+= f.labeled_input_field(:zip_code, class: 'span2') if entry.show_attr?(:zip_code)
+= f.labeled_input_field(:town, class: 'span4') if entry.show_attr?(:town)
+- if entry.show_attr?(:country)
+ = f.labeled(:country) do
+ .span6{style: 'margin-left: 0px'}
+ = country_select(f.object.class.model_name.param_key,
+ 'country',
+ { priority_countries: Settings.countries.prioritized,
+ include_blank: true },
+ { class: 'chosen-select',
+ data: { placeholder: ' ',
+ chosen_no_results: t('global.chosen_no_results') } })
+ = entry.required_attr?(:country) ? content_tag(:span, class: 'required') { '*' } : ''
diff --git a/app/views/event/participation_contact_datas/edit.html.haml b/app/views/event/participation_contact_datas/edit.html.haml
new file mode 100644
index 0000000000..f2472f0a0d
--- /dev/null
+++ b/app/views/event/participation_contact_datas/edit.html.haml
@@ -0,0 +1,43 @@
+-# Copyright (c) 2012-2017, Pfadibewegung Schweiz. This file is part of
+-# hitobito_pbs and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito_pbs.
+
+- title t('.title')
+
+= render 'event/participations/step_wizard', step: 1
+
+= crud_form(entry,
+ { cancel_url: group_event_path(group, event),
+ buttons_bottom: true,
+ submit_label: t('.save'),
+ url: contact_data_group_event_participations_path(group, event,
+ {event_role: { type: params[:event_role][:type] }})}) do |f|
+
+ = field_set_tag do
+ - [:first_name, :last_name, :nickname, :company_name].each do |a|
+ = f.labeled_input_field(a) if entry.show_attr?(a)
+
+ = render 'address_fields', f: f
+
+ - if entry.show_attr?(:email)
+ = f.labeled_input_field :email, help_inline: t('people.email_field.used_as_login')
+
+ - Event::ParticipationContactData.contact_associations.each do |a|
+ = field_set_tag do
+ - unless entry.hidden_contact_attrs.include?(a)
+ = f.labeled_inline_fields_for a, "contactable/#{a.to_s.singularize}_fields"
+
+ = field_set_tag do
+ - if entry.show_attr?(:gender)
+ = f.labeled(:gender) do
+ - (Person::GENDERS + ['']).each do |key|
+ = f.inline_radio_button(:gender, key, entry.gender_label(key))
+
+ - if entry.show_attr?(:birthday)
+ = f.labeled_string_field(:birthday,
+ value: f.date_value(:birthday),
+ help_inline: t('people.fields.format_birthday'),
+ class: 'span2')
+
+ = render_extensions :fields, locals: { f: f }
diff --git a/app/views/event/participations/_actions_approval.html.haml b/app/views/event/participations/_actions_approval.html.haml
index 775042349c..7f06624bf3 100644
--- a/app/views/event/participations/_actions_approval.html.haml
+++ b/app/views/event/participations/_actions_approval.html.haml
@@ -1,10 +1,12 @@
-- unless @application.approved?
- = action_button("✓ #{t('.approve_button')}".html_safe,
- approve_group_event_application_path(@group, @event, @application),
- nil,
- method: :put)
-- unless @application.rejected?
- = action_button("× #{t('.reject_button')}".html_safe,
- reject_group_event_application_path(@group, @event, @application),
- nil,
- method: :delete)
+- if @event.requires_approval? && can?(:approve, @application)
+
+ - unless @application.approved?
+ = action_button("✓ #{t('.approve_button')}".html_safe,
+ approve_group_event_application_path(@group, @event, @application),
+ nil,
+ method: :put)
+ - unless @application.rejected?
+ = action_button("× #{t('.reject_button')}".html_safe,
+ reject_group_event_application_path(@group, @event, @application),
+ nil,
+ method: :delete)
diff --git a/app/views/event/participations/_actions_index.html.haml b/app/views/event/participations/_actions_index.html.haml
index d2a27f5dcf..f00129c51b 100644
--- a/app/views/event/participations/_actions_index.html.haml
+++ b/app/views/event/participations/_actions_index.html.haml
@@ -6,6 +6,8 @@
- if can?(:new, @event.new_role)
= Dropdown::Event::RoleAdd.new(self, @group, @event)
+= render 'invoices/button_new', group: @group, people: entries.collect(&:person)
+
= dropdown_people_export(can?(:show_details, entries.first))
= action_button(t('global.button.print'), 'javascript:window.print();', :print)
diff --git a/app/views/event/participations/_actions_show.html.haml b/app/views/event/participations/_actions_show.html.haml
index c4d117b0fa..8697683bae 100644
--- a/app/views/event/participations/_actions_show.html.haml
+++ b/app/views/event/participations/_actions_show.html.haml
@@ -21,7 +21,6 @@
- if can?(:destroy, entry)
= button_action_destroy
- - if @event.requires_approval? && can?(:approve, @application)
- = render 'actions_approval'
+ = render 'actions_approval'
= render_extensions :actions_show
diff --git a/app/views/event/participations/_answers.html.haml b/app/views/event/participations/_answers.html.haml
index ccfa9d4540..e2d0eb2b8d 100644
--- a/app/views/event/participations/_answers.html.haml
+++ b/app/views/event/participations/_answers.html.haml
@@ -1,10 +1,11 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-= section t('event.participations.specific_information') do
- %dl
- - @answers.each do |answer|
- %dt= answer.question.question
- %dd= answer.answer || t('event.participations.no_answer_given')
+- if answers.present?
+ = section title do
+ %dl
+ - answers.each do |answer|
+ %dt= answer.question.question
+ %dd= answer.answer || t('event.participations.no_answer_given')
diff --git a/app/views/event/participations/_apply_to.html.haml b/app/views/event/participations/_apply_to.html.haml
index d74ca6174b..04d7a62e2c 100644
--- a/app/views/event/participations/_apply_to.html.haml
+++ b/app/views/event/participations/_apply_to.html.haml
@@ -1,11 +1,11 @@
-# Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
--# hitobito_pbs and licensed under the Affero General Public License version 3
+-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_pbs.
+-# https://github.com/hitobito/hitobito.
- if entry.person == current_user && @application.present? && @application.contact.present?
= section t('event.applied_to') do
- contact_type = @application.contact.class.base_class.name.underscore
= render "#{contact_type.pluralize}/contact_data",
contact_type.to_sym => @application.contact,
- only_public: true
\ No newline at end of file
+ only_public: true
diff --git a/app/views/event/participations/_attrs.html.haml b/app/views/event/participations/_attrs.html.haml
index 660c97e28c..c68583c98b 100644
--- a/app/views/event/participations/_attrs.html.haml
+++ b/app/views/event/participations/_attrs.html.haml
@@ -7,7 +7,7 @@
%article.span6
= render 'people/contact_data', person: entry.person, only_public: cannot?(:show_details, entry)
- = render_attrs(entry.person, :birthday, :gender)
+ = render_attrs(entry.person, :layer_group, :birthday, :gender)
- if can?(:show_details, entry)
= render 'application_details'
@@ -16,7 +16,14 @@
folder: :people,
locals: { entry: entry.person, show_full: false }
- = render 'answers' if @answers.present?
+ = render 'answers',
+ answers: @answers.reject(&:admin?),
+ title: t('event.participations.application_answers')
+
+ - if can?(:show_full, entry)
+ = render 'answers',
+ answers: @answers.select(&:admin?),
+ title: t('event.participations.admin_answers')
= section t('activerecord.attributes.event/participation.additional_information') do
= simple_format(entry.additional_information || '-')
diff --git a/app/views/event/participations/_form.html.haml b/app/views/event/participations/_form.html.haml
index 22c874d0f8..695f4a5c83 100644
--- a/app/views/event/participations/_form.html.haml
+++ b/app/views/event/participations/_form.html.haml
@@ -5,6 +5,8 @@
- if entry.new_record?
- title t('.title_new', role: entry.roles.first.class.model_name.human)
+ - unless params[:for_someone_else]
+ = render 'event/participations/step_wizard', step: 2
- else
- title t('.title_edit', person: entry.person)
@@ -21,9 +23,13 @@
- if params[:for_someone_else]
= f.labeled_person_field(:person)
- = f.fields_for(:answers) do |fans|
+ = f.fields_for(:answers, @answers.reject(&:admin?)) do |fans|
= render 'event/answers/fields', f: fans
+ - if params[:for_someone_else] || entry.persisted?
+ = f.fields_for(:answers, @answers.select(&:admin?)) do |fans|
+ = render 'event/answers/fields', f: fans
+
= f.labeled_text_area(:additional_information)
- if entry.application && entry.new_record?
diff --git a/app/views/event/participations/_list.html.haml b/app/views/event/participations/_list.html.haml
index a5439114ca..d5da7f437b 100644
--- a/app/views/event/participations/_list.html.haml
+++ b/app/views/event/participations/_list.html.haml
@@ -1,4 +1,4 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
@@ -14,7 +14,7 @@
- t.col('') do |p|
.profil
= image_tag(p.person.picture.thumb.url, size: '32x32')
- - sortable_grouped_person_attr(t, %w(last_name first_name nickname)) do |p|
+ - sortable_grouped_person_attr(t, last_name: true, first_name: true, nickname: true) do |p|
%strong
-# Any person listed can be shown
= link_to(p.to_s(:list), group_event_participation_path(@group, @event, p))
@@ -22,6 +22,9 @@
- t.col(event_participations_roles_header(t)) { |p| event_participations_roles_content(p) }
- t.col(Person.human_attribute_name(:emails)) { |p| p.all_emails(cannot?(:show_details, p)) }
- t.col(PhoneNumber.model_name.human(count: 2)) { |p| p.all_phone_numbers(cannot?(:show_details, p)) }
- - sortable_grouped_person_attr(t, %w(zip_code town), :address) { |p| p.complete_address }
+ - sortable_grouped_person_attr(t, address: false, zip_code: true, town: true) { |p| p.complete_address }
+ - t.col(t.sort_header(:birthday, Person.human_attribute_name(:birthday))) do |p|
+ = format_attr(p.person, :birthday)
+ - t.col(Person.human_attribute_name(:layer_group)) { |p| p.layer_group_label }
= render_extensions :list, locals: { table: t }
diff --git a/app/views/event/participations/_precondition_warnings.html.haml b/app/views/event/participations/_precondition_warnings.html.haml
index dd386a95ec..629716ff5a 100644
--- a/app/views/event/participations/_precondition_warnings.html.haml
+++ b/app/views/event/participations/_precondition_warnings.html.haml
@@ -1,8 +1,8 @@
-# Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
--# hitobito_pbs and licensed under the Affero General Public License version 3
+-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_pbs.
+-# https://github.com/hitobito/hitobito.
- if @precondition_warnings.present?
.alert.alert-warning
- %p= simple_format(@precondition_warnings.flatten.join("\n"))
\ No newline at end of file
+ %p= simple_format(@precondition_warnings.flatten.join("\n"))
diff --git a/app/views/event/participations/_step_wizard.html.haml b/app/views/event/participations/_step_wizard.html.haml
new file mode 100644
index 0000000000..6dec873267
--- /dev/null
+++ b/app/views/event/participations/_step_wizard.html.haml
@@ -0,0 +1,18 @@
+-# Copyright (c) 2012-2017, Pfadibewegung Schweiz. This file is part of
+-# hitobito_pbs and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito_pbs.
+
+.stepwizard
+ .stepwizard-step{class: (step == 1 ? 'is-current' : 'is-disabled')}
+ %a.stepwizard-link{href: '#'}
+ .stepwizard-number
+ 1
+ .stepwizard-title
+ = t('event.participation_contact_datas.edit.title')
+ .stepwizard-step{class: (step == 2 ? 'is-current' : 'is-disabled')}
+ %a.stepwizard-link{href: '#'}
+ .stepwizard-number
+ 2
+ .stepwizard-title
+ = t('event.participations.edit.title')
diff --git a/app/views/event/qualifications/_action_link_columns.html.haml b/app/views/event/qualifications/_action_link_columns.html.haml
deleted file mode 100644
index 643fcfb8c4..0000000000
--- a/app/views/event/qualifications/_action_link_columns.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%td.issue
- = p.issue_action(group)
-%td.revoke
- = p.revoke_action(group)
diff --git a/app/views/event/qualifications/_checkbox.html.haml b/app/views/event/qualifications/_checkbox.html.haml
new file mode 100644
index 0000000000..85c3f80dca
--- /dev/null
+++ b/app/views/event/qualifications/_checkbox.html.haml
@@ -0,0 +1,6 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= check_box_tag('participation_ids[]', participation.id, participation.qualified?)
diff --git a/app/views/event/qualifications/_list.html.haml b/app/views/event/qualifications/_list.html.haml
index 24cf346cd2..415fa3dd52 100644
--- a/app/views/event/qualifications/_list.html.haml
+++ b/app/views/event/qualifications/_list.html.haml
@@ -1,4 +1,4 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
@@ -7,7 +7,7 @@
%tbody
- entries.each do |p|
%tr{id: dom_id(p)}
- = render 'action_link_columns', p: p, group: @group
+ %td.center= render 'checkbox', participation: p
%td
%strong= link_to(p.to_s(:list), group_event_participation_path(@group, @event, p))
%td= p.roles_short
diff --git a/app/views/event/qualifications/_people.html.haml b/app/views/event/qualifications/_people.html.haml
new file mode 100644
index 0000000000..95592c3959
--- /dev/null
+++ b/app/views/event/qualifications/_people.html.haml
@@ -0,0 +1,9 @@
+- if @event.kind.qualification_kinds(%w(qualification prolongation), 'leader').to_a.present?
+ %p= event.issued_qualifications_info_for_leaders
+
+ = render 'list', entries: @leaders
+
+- if @event.kind.qualification_kinds(%w(qualification prolongation), 'participant').to_a.present?
+ %p= event.issued_qualifications_info_for_participants
+
+ = render 'list', entries: @participants
diff --git a/app/views/event/qualifications/index.html.haml b/app/views/event/qualifications/index.html.haml
index 7ae4c540cf..6b0e26394e 100644
--- a/app/views/event/qualifications/index.html.haml
+++ b/app/views/event/qualifications/index.html.haml
@@ -1,18 +1,15 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
--title event.to_s
+- title event.to_s
-= render_extensions(:index)
+= form_tag(nil, method: :put) do
-- if @event.kind.qualification_kinds(%w(qualification prolongation), 'leader').to_a.present?
- %p= event.issued_qualifications_info_for_leaders
+ = render 'people'
- = render 'list', entries: @leaders
-
-- if @event.kind.qualification_kinds(%w(qualification prolongation), 'participant').to_a.present?
- %p= event.issued_qualifications_info_for_participants
-
- = render 'list', entries: @participants
+ .btn-group
+ = button_tag(t('event.qualifications.index.save'),
+ class: 'btn btn-primary',
+ data: { disable_with: t('event.qualifications.index.save') })
diff --git a/app/views/event/qualifications/qualification.js.haml b/app/views/event/qualifications/qualification.js.haml
deleted file mode 100644
index 82eccc2c8c..0000000000
--- a/app/views/event/qualifications/qualification.js.haml
+++ /dev/null
@@ -1,10 +0,0 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
--# hitobito and licensed under the Affero General Public License version 3
--# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito.
-
-- if @nothing_changed
- alert("#{j(t('event.no_qualifications_could_be_prolonged', person: participation.person))}");
-
-$('##{dom_id(participation)} td.issue').html('#{escape_javascript(participation.issue_action(@group))}')
-$('##{dom_id(participation)} td.revoke').html('#{escape_javascript(participation.revoke_action(@group))}')
diff --git a/app/views/event/questions/_fields.html.haml b/app/views/event/questions/_fields.html.haml
index 01eb6608f2..22bc5369a1 100644
--- a/app/views/event/questions/_fields.html.haml
+++ b/app/views/event/questions/_fields.html.haml
@@ -1,4 +1,4 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
@@ -6,6 +6,7 @@
= f.labeled_input_field(:question, help_inline: f.link_to_remove(ta(:remove)))
= f.labeled_input_field(:choices)
= f.labeled_input_field(:multiple_choices)
-= f.labeled_input_field(:required)
+= f.labeled_input_field(:required) unless admin
+
.controls.fields-separation
%hr
diff --git a/app/views/events/_actions_index.html.haml b/app/views/events/_actions_index.html.haml
index 072665b4cd..d403a5e331 100644
--- a/app/views/events/_actions_index.html.haml
+++ b/app/views/events/_actions_index.html.haml
@@ -5,3 +5,4 @@
= new_event_button
= export_events_button
+= export_events_ical_button
diff --git a/app/views/events/_actions_show.html.haml b/app/views/events/_actions_show.html.haml
index 3ba9b7ff0b..4c09fea1fb 100644
--- a/app/views/events/_actions_show.html.haml
+++ b/app/views/events/_actions_show.html.haml
@@ -1,10 +1,16 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-= button_action_event_apply(entry, @group)
+- if !@user_participation && event_user_application_possible?(entry)
+ = Dropdown::Event::ParticipantAdd.for_user(self, @group, entry, current_user)
= button_action_edit if can?(:update, entry)
= button_action_destroy if can?(:destroy, entry)
+- if can?(:new, entry)
+ = action_button(t('events.global.link.duplicate'),
+ new_group_event_path(@group, source_id: entry.id),
+ :book)
+= export_events_ical_button
= render_extensions :actions_show
diff --git a/app/views/events/_additional_fields.html.haml b/app/views/events/_additional_fields.html.haml
deleted file mode 100644
index 30ff208c1d..0000000000
--- a/app/views/events/_additional_fields.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
--# Copyright (c) 2015, insieme Schweiz. This file is part of
--# hitobito_insieme and licensed under the Affero General Public License version 3
--# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_insieme.
-
-- if entry.role_types.present?
- = field_set_tag t('events.form.additional_information') do
- %p= t('events.form.explain_application_questions')
-
- = f.nested_fields_for :questions, 'event/questions/fields'
diff --git a/app/views/events/_admin_questions_fields.haml b/app/views/events/_admin_questions_fields.haml
new file mode 100644
index 0000000000..7dec3e1325
--- /dev/null
+++ b/app/views/events/_admin_questions_fields.haml
@@ -0,0 +1,11 @@
+-# Copyright (c) 2015 - 2017, Pfadibewegung Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+
+%p= t('events.form.explain_admin_questions')
+
+= field_set_tag nil do
+ = f.nested_fields_for :admin_questions do |fields|
+ = render 'event/questions/fields', f: fields, admin: true
diff --git a/app/views/events/_application_fields.html.haml b/app/views/events/_application_fields.html.haml
index cc477ab6bb..17c8a9ac61 100644
--- a/app/views/events/_application_fields.html.haml
+++ b/app/views/events/_application_fields.html.haml
@@ -1,26 +1,34 @@
-# Copyright (c) 2015, insieme Schweiz. This file is part of
--# hitobito_insieme and licensed under the Affero General Public License version 3
+-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_insieme.
+-# https://github.com/hitobito/hitobito.
-- if entry.participant_types.present?
- = field_set_tag do
- = f.labeled_input_fields(*entry.used_attributes(:application_opening_at, :application_closing_at, :application_conditions, :maximum_participants))
- - entry.used?(:external_applications) do
- = f.labeled_boolean_field(:external_applications, caption: t('events.form.caption_external_applications'))
+= field_set_tag do
+ = f.labeled_input_fields(*entry.used_attributes(:application_opening_at, :application_closing_at, :application_conditions, :maximum_participants))
- - entry.used?(:priorization) do
- = f.labeled_boolean_field(:priorization, caption: t('events.form.caption_prioritization'))
+ - entry.used?(:external_applications) do
+ = f.labeled_boolean_field(:external_applications, caption: t('events.form.caption_external_applications'))
- - application_approve_role_exists? && entry.used?(:requires_approval) do
- = f.labeled_boolean_field(:requires_approval, caption: t('events.form.caption_requires_approval'))
+ - entry.used?(:priorization) do
+ = f.labeled_boolean_field(:priorization, caption: t('events.form.caption_prioritization'))
- - entry.used?(:signature) do
- = f.labeled_boolean_field(:signature, caption: t('.caption_signature'))
- - entry.used?(:signature_confirmation) do
- = f.labeled_boolean_field(:signature_confirmation, caption: t('.caption_signature_confirmation'))
- - entry.used?(:signature_confirmation_text) do
- = f.labeled_input_field(:signature_confirmation_text, value: entry.signature_confirmation_text || t('.signature_confirmation_text_default'))
+ - application_approve_role_exists? && entry.used?(:requires_approval) do
+ = f.labeled_boolean_field(:requires_approval, caption: t('events.form.caption_requires_approval'))
- = render_extensions 'application_fields', locals: { f: f }
+ - entry.used?(:signature) do
+ = f.labeled_boolean_field(:signature, caption: t('.caption_signature'))
+ - entry.used?(:signature_confirmation) do
+ = f.labeled_boolean_field(:signature_confirmation, caption: t('.caption_signature_confirmation'))
+ - entry.used?(:signature_confirmation_text) do
+ = f.labeled_input_field(:signature_confirmation_text, value: entry.signature_confirmation_text || t('.signature_confirmation_text_default'))
+
+ - entry.used?(:applications_cancelable) do
+ = f.labeled_boolean_field(:applications_cancelable,
+ caption: t('events.form.caption_applications_cancelable'))
+
+ - entry.used?(:display_booking_info) do
+ = f.labeled_boolean_field(:display_booking_info,
+ caption: t('events.form.caption_display_booking_info'))
+
+ = render_extensions 'application_fields', locals: { f: f }
diff --git a/app/views/events/_application_questions_fields.haml b/app/views/events/_application_questions_fields.haml
new file mode 100644
index 0000000000..1fd2191ad6
--- /dev/null
+++ b/app/views/events/_application_questions_fields.haml
@@ -0,0 +1,11 @@
+-# Copyright (c) 2015, insieme Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+
+%p= t('events.form.explain_application_questions')
+
+= field_set_tag nil do
+ = f.nested_fields_for :application_questions do |fields|
+ = render 'event/questions/fields', f: fields, admin: false
diff --git a/app/views/events/_attrs.html.haml b/app/views/events/_attrs.html.haml
index dbd36bbe6f..c752a9cd20 100644
--- a/app/views/events/_attrs.html.haml
+++ b/app/views/events/_attrs.html.haml
@@ -3,6 +3,9 @@
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
+- if @user_participation
+ = render 'banner_participating'
+
#main.row-fluid
%article.span7
= render 'attrs_primary'
diff --git a/app/views/events/_attrs_application.html.haml b/app/views/events/_attrs_application.html.haml
index fca97f2c0c..0f3ace941f 100644
--- a/app/views/events/_attrs_application.html.haml
+++ b/app/views/events/_attrs_application.html.haml
@@ -14,15 +14,15 @@
- if entry.course_kind?
= labeled(Event::Kind.human_attribute_name(:minimum_age),
- entry.kind.minimum_age? ? t('events.minimum_age_with_years', minimum_age: entry.kind.minimum_age) : '')
- = labeled(t('events.preconditions'), entry.kind.qualification_kinds('precondition', 'participant').join(', '))
+ entry.kind.minimum_age? ? t('events.minimum_age_with_years', minimum_age: entry.kind.minimum_age) : '')
+ = labeled(t('events.preconditions'), grouped_qualification_kinds_string(entry.kind, 'precondition', 'participant'))
- if entry.course_kind?
%dl.dl-horizontal
= labeled(Event::Kind.human_attribute_name(:qualification_kinds),
- entry.kind.qualification_kinds('qualification', 'participant').join(', '))
+ entry.kind.qualification_kinds('qualification', 'participant').join(', '))
= labeled(Event::Kind.human_attribute_name(:prolongations),
- entry.kind.qualification_kinds('prolongation', 'participant').join(', '))
+ entry.kind.qualification_kinds('prolongation', 'participant').join(', '))
= render_attrs(entry, *entry.used_attributes(:signature, :signature_confirmation))
diff --git a/app/views/events/_banner_participating.html.haml b/app/views/events/_banner_participating.html.haml
new file mode 100644
index 0000000000..d13eade80a
--- /dev/null
+++ b/app/views/events/_banner_participating.html.haml
@@ -0,0 +1,10 @@
+-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+.alert.alert-success
+ = t("event.participations.cancel_application.explanation")
+ - if can?(:destroy, @user_participation)
+
+ = action_button_cancel_participation
diff --git a/app/views/events/_contact_attr_fields.html.haml b/app/views/events/_contact_attr_fields.html.haml
new file mode 100644
index 0000000000..1b5eac807d
--- /dev/null
+++ b/app/views/events/_contact_attr_fields.html.haml
@@ -0,0 +1,9 @@
+-# Copyright (c) 2017, Pfadibewegung Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+%p= t('events.form.explain_contact_attrs')
+
+= field_set_tag nil do
+ = ContactAttrs::ControlBuilder.new(f, entry).render
diff --git a/app/views/events/_default_description_link.html.haml b/app/views/events/_default_description_link.html.haml
index bb399e7ebd..17440359d2 100644
--- a/app/views/events/_default_description_link.html.haml
+++ b/app/views/events/_default_description_link.html.haml
@@ -1,8 +1,5 @@
-.controls{style: 'display: none' }
- %span.help-inline
- %a.standard-description-link
- = t('activerecord.attributes.event/kind.general_information')
+%a.standard-description-link{ href: '#' }= t('.insert_general_information')
- @kinds.each do |event_kind|
- %p.hide.default-description{data: {kind: event_kind.id} }
+ %span.hide.default-description{ data: { kind: event_kind.id } }
= event_kind.general_information
diff --git a/app/views/events/_form.html.haml b/app/views/events/_form.html.haml
index e1bcec6307..3239667fb0 100644
--- a/app/views/events/_form.html.haml
+++ b/app/views/events/_form.html.haml
@@ -1,4 +1,4 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
@@ -18,10 +18,21 @@
#dates.tab-pane
= render 'date_fields', f: f
- #application.tab-pane
- = render 'application_fields', f: f
- = render 'additional_fields', f: f
+ - if entry.participant_types.present?
+ #application.tab-pane
+ = render 'application_fields', f: f
= render_extensions 'form_tab_pane', locals: { f: f }
+ - if entry.role_types.present?
+ #application_questions.tab-pane
+ = render 'application_questions_fields', f: f
+
+ #admin_questions.tab-pane
+ = render 'admin_questions_fields', f: f
+
+ - if entry.participant_types.present?
+ #contact_attrs.tab-pane
+ = render 'contact_attr_fields', f: f
+
= render_extensions 'form_actions_caption', locals: { f: f }
diff --git a/app/views/events/_form_tabs.html.haml b/app/views/events/_form_tabs.html.haml
index de82c15605..77afcf81d2 100644
--- a/app/views/events/_form_tabs.html.haml
+++ b/app/views/events/_form_tabs.html.haml
@@ -1,6 +1,20 @@
+-# Copyright (c) 2015 - 2017, insieme Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
%ul.nav.nav-tabs
%li.active= link_to(t('events.form_tabs.general'), '#general', data: { toggle: 'tab' })
%li= link_to(Event.human_attribute_name(:dates), '#dates', data: { toggle: 'tab' })
- %li= link_to(Event::Application.model_name.human, '#application', data: { toggle: 'tab' })
+ - if entry.participant_types.present?
+ %li= link_to(Event::Application.model_name.human, '#application', data: { toggle: 'tab' })
= render_extensions :form_tabs
+
+ - if entry.role_types.present?
+ %li= link_to(t('events.form_tabs.application_questions'), '#application_questions', data: { toggle: 'tab' })
+ %li= link_to(t('events.form_tabs.admin_questions'), '#admin_questions', data: { toggle: 'tab' })
+
+ - if entry.participant_types.present?
+ %li= link_to(t('events.form_tabs.contact_attrs'), '#contact_attrs', data: { toggle: 'tab' })
+
diff --git a/app/views/events/_general_fields.html.haml b/app/views/events/_general_fields.html.haml
index 6cc46f5590..601c94601b 100644
--- a/app/views/events/_general_fields.html.haml
+++ b/app/views/events/_general_fields.html.haml
@@ -1,7 +1,7 @@
-# Copyright (c) 2015, insieme Schweiz. This file is part of
--# hitobito_insieme and licensed under the Affero General Public License version 3
+-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_insieme.
+-# https://github.com/hitobito/hitobito.
= field_set_tag do
= f.labeled_input_field(:name)
@@ -21,8 +21,8 @@
= f.labeled_input_field(:number)
- entry.used?(:description) do
- = render partial: 'default_description_link' if entry.kind_class == Event::Kind
- = f.labeled_input_field(:description)
+ - link = entry.kind_class == Event::Kind ? render('default_description_link') : nil
+ = f.labeled_input_field(:description, help: link)
= f.labeled_input_fields(*entry.used_attributes(:motto, :cost))
diff --git a/app/views/events/_group_fields.html.haml b/app/views/events/_group_fields.html.haml
index d8f58dda3b..c49d9b8940 100644
--- a/app/views/events/_group_fields.html.haml
+++ b/app/views/events/_group_fields.html.haml
@@ -1,7 +1,7 @@
-# Copyright (c) 2015, insieme Schweiz. This file is part of
--# hitobito_insieme and licensed under the Affero General Public License version 3
+-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_insieme.
+-# https://github.com/hitobito/hitobito.
- entry.used?(:group_ids) do
- if @groups.present?
diff --git a/app/views/events/_list.html.haml b/app/views/events/_list.html.haml
index 32e9f075f6..c098df00fd 100644
--- a/app/views/events/_list.html.haml
+++ b/app/views/events/_list.html.haml
@@ -8,7 +8,10 @@
= render 'shared/page_per_year'
= crud_table do |t|
- -t.col(Event.human_attribute_name(:name)) do |e|
+ - t.col(t.attr_header(:name)) do |e|
%strong= link_to e.name, group_event_path(@group, e)
- -t.attrs(:dates_full, :booking_info)
+ - t.attr(:dates_full)
+ - t.attr(:description_short, t.attr_header(:description))
+ - t.attr(:booking_info)
+ - t.col(nil, class: 'center') { |e| button_action_event_apply(e, @group) }
diff --git a/app/views/full_text/_events.html.haml b/app/views/full_text/_events.html.haml
new file mode 100644
index 0000000000..24630fd581
--- /dev/null
+++ b/app/views/full_text/_events.html.haml
@@ -0,0 +1,5 @@
+= table(@events, class: 'table table-striped table-hover') do |t|
+ - t.col(Event.human_attribute_name(:name)) do |e|
+ - content_tag(:strong, link_to(e, e))
+ - t.col(Event.human_attribute_name(:dates)) { |e| format_attr(e, :dates) }
+ - t.col(Group.model_name.human) { |e| format_attr(e, :groups) }
diff --git a/app/views/full_text/_groups.html.haml b/app/views/full_text/_groups.html.haml
new file mode 100644
index 0000000000..94f9eab9ec
--- /dev/null
+++ b/app/views/full_text/_groups.html.haml
@@ -0,0 +1,5 @@
+= table(@groups, class: 'table table-striped table-hover') do |t|
+ - t.col(Group.human_attribute_name(:name)) do |g|
+ - content_tag(:strong, link_to(g, g))
+ - t.col(Group.human_attribute_name(:layer_group)) do |g|
+ - link_to(format_attr(g, :layer_group), g.layer_group)
diff --git a/app/views/full_text/_people.html.haml b/app/views/full_text/_people.html.haml
new file mode 100644
index 0000000000..95a89990c4
--- /dev/null
+++ b/app/views/full_text/_people.html.haml
@@ -0,0 +1,11 @@
+= crud_table do |t|
+ - t.col('') do |p|
+ .profil= image_tag(p.picture.thumb.url, size: '32x32')
+ - t.col(Person.human_attribute_name(:name)) do |p|
+ %strong
+ -# Any person listed can be shown
+ = link_to(p.to_s(:list), group_person_path(p.default_group_id, p))
+ - t.col(Role.model_name.human(count: 2)) { |p| p.decorate.roles_short(nil) }
+ - t.col(Person.human_attribute_name(:emails)) { |p| p.decorate.all_emails(true) }
+ - t.col(PhoneNumber.model_name.human(count: 2)) { |p| p.decorate.all_phone_numbers(true) }
+ - t.col(Person.human_attribute_name(:address)) { |p| p.decorate.complete_address }
diff --git a/app/views/full_text/index.html.haml b/app/views/full_text/index.html.haml
index c8551178af..172c762f00 100644
--- a/app/views/full_text/index.html.haml
+++ b/app/views/full_text/index.html.haml
@@ -5,22 +5,17 @@
- title t('.title')
-= paginate(entries)
-
#main
- = crud_table do |t|
- - t.col('') do |p|
- .profil= image_tag(p.picture.thumb.url, size: '32x32')
- - t.col(Person.human_attribute_name(:name)) do |p|
- %strong
- -# Any person listed can be shown
- = link_to(p.to_s(:list), group_person_path(p.default_group_id, p))
- - t.col(Role.model_name.human(count: 2)) { |p| p.roles_short(nil) }
- - t.col(Person.human_attribute_name(:emails)) { |p| p.all_emails(true) }
- - t.col(PhoneNumber.model_name.human(count: 2)) { |p| p.all_phone_numbers(true) }
- - t.col(Person.human_attribute_name(:address)) { |p| p.complete_address }
+ - if params[:q].to_s.size < 2
+ %p= t('.incomplete_search_request')
-- if params[:q].to_s.size < 2
- %p= t('.incomplete_search_request')
+ - else
+ %ul.nav.nav-tabs
+ %li.active= link_to(Person.model_name.human(count: 2), '#people', data: { toggle: 'tab' })
+ %li= link_to(Group.model_name.human(count: 2), '#groups', data: { toggle: 'tab' })
+ %li= link_to(Event.model_name.human(count: 2), '#events', data: { toggle: 'tab' })
-= paginate(entries)
+ .tab-content
+ #people.tab-pane.active= render 'people'
+ #groups.tab-pane= render 'groups'
+ #events.tab-pane= render 'events'
diff --git a/app/views/group/deleted_people/_list.html.haml b/app/views/group/deleted_people/_list.html.haml
new file mode 100644
index 0000000000..c4652a3cdf
--- /dev/null
+++ b/app/views/group/deleted_people/_list.html.haml
@@ -0,0 +1,36 @@
+-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+- title @group.to_s
+
+.pagination-bar
+ = paginate @people
+
+ .pagination-info
+ - if @people.total_count > 0
+ = t('people.list.number_of_people_shown', count: @people.total_count)
+ - else
+ = ti(:no_list_entries)
+
+- if @people.total_count > 0
+ = crud_table do |t|
+ - t.col('') do |p|
+ .profil= image_tag(p.picture.thumb.url, size: '32x32')
+ - sortable_grouped_person_attr(t, last_name: false, first_name: false, nickname: false) do |p|
+ %strong
+ = p.to_s(:list)
+ %br/
+ = muted p.additional_name
+ - t.col(Role.model_name.human(count: 2)) do |p|
+ - if can?(:create, Role.new(group_id: p.restored_group(@group).id))
+ - p.last_role_new_link(@group)
+ - t.col(Person.human_attribute_name(:emails)) do |p|
+ - p.all_emails(true)
+ - t.col(PhoneNumber.model_name.human(count: 2)) do |p|
+ - p.all_phone_numbers(true)
+ - sortable_grouped_person_attr(t, address: false, zip_code: false, town: false) do |p|
+ - p.complete_address
+
+= paginate @people
diff --git a/app/views/group/deleted_people/index.html.haml b/app/views/group/deleted_people/index.html.haml
new file mode 100644
index 0000000000..02411fc1d6
--- /dev/null
+++ b/app/views/group/deleted_people/index.html.haml
@@ -0,0 +1,8 @@
+-# Copyright (c) 2012-2015, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+- @sheet = Sheet::Group::DeletedPeople.new(self)
+
+= render 'list'
diff --git a/app/views/groups/_attrs.html.haml b/app/views/groups/_attrs.html.haml
index de1f2264f0..b659f39cae 100644
--- a/app/views/groups/_attrs.html.haml
+++ b/app/views/groups/_attrs.html.haml
@@ -8,11 +8,13 @@
%h2= t('.contact_details')
= render 'contact_data', group: entry, only_public: cannot?(:show_details, @group)
-
%h2= t('.additional_information')
= render_attrs(entry, :type_name)
= render_extensions :attrs
= render_present_attrs(entry, :created_info, :updated_info, :deleted_info) if can?(:show_details, @group)
+ - if can?(:index_notes, entry)
+ = render 'notes/section', create_path: group_notes_path(@group)
+
%aside.span5.offset1
= render 'child_groups'
diff --git a/app/views/groups/deleted_subgroups.html.haml b/app/views/groups/deleted_subgroups.html.haml
index df7a041f9a..1e89658aaf 100644
--- a/app/views/groups/deleted_subgroups.html.haml
+++ b/app/views/groups/deleted_subgroups.html.haml
@@ -1,7 +1,9 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-= render 'child_groups'
-
+- if @sub_groups.present?
+ = render 'child_groups'
+- else
+ = t('.no_deleted_sub_groups')
diff --git a/app/views/invoice_articles/_form.html.haml b/app/views/invoice_articles/_form.html.haml
new file mode 100644
index 0000000000..3ea61e2b56
--- /dev/null
+++ b/app/views/invoice_articles/_form.html.haml
@@ -0,0 +1,12 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= entry_form do |f|
+ = f.labeled_input_fields :number, :name, :description
+ = f.labeled_input_field(:category, data: { provide: :typeahead, source: InvoiceArticle.categories })
+
+ = f.labeled_input_fields :unit_cost, :vat_rate
+ = f.labeled_input_field(:cost_center, data: { provide: :typeahead, source: InvoiceArticle.cost_centers })
+ = f.labeled_input_field(:account, data: { provide: :typeahead, source: InvoiceArticle.accounts })
diff --git a/app/views/invoice_articles/_list.html.haml b/app/views/invoice_articles/_list.html.haml
new file mode 100644
index 0000000000..f2546bb3b4
--- /dev/null
+++ b/app/views/invoice_articles/_list.html.haml
@@ -0,0 +1,11 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= crud_table do |t|
+ - t.col(t.sort_header(:name)) do |article|
+ %strong= link_to article.name, group_invoice_article_path(parent, article)
+ - t.sortable_attrs(:number, :description, :category)
+ - t.col(t.sort_header(:unit_cost)) { |a| number_to_currency(a.unit_cost) }
+ - t.sortable_attrs(:vat_rate, :cost_center, :account)
diff --git a/app/views/invoice_configs/_actions_show.html.haml b/app/views/invoice_configs/_actions_show.html.haml
new file mode 100644
index 0000000000..721eb1c77b
--- /dev/null
+++ b/app/views/invoice_configs/_actions_show.html.haml
@@ -0,0 +1,6 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= button_action_edit if can?(:edit, entry)
diff --git a/app/views/invoice_configs/_form.html.haml b/app/views/invoice_configs/_form.html.haml
new file mode 100644
index 0000000000..ac40bd0416
--- /dev/null
+++ b/app/views/invoice_configs/_form.html.haml
@@ -0,0 +1,9 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= standard_form(entry, url: [parent, :invoice_config]) do |f|
+ = f.error_messages
+ = f.labeled_input_field :payment_information
+ = save_form_buttons(f, nil, group_invoice_config_path(parent))
diff --git a/app/views/invoice_lists/_calculated.html.haml b/app/views/invoice_lists/_calculated.html.haml
new file mode 100644
index 0000000000..0a1b02a81c
--- /dev/null
+++ b/app/views/invoice_lists/_calculated.html.haml
@@ -0,0 +1,14 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+#calculated.control-group
+ .controls
+ %dl.dl-horizontal
+ %dt.muted= Invoice.human_attribute_name(:cost)
+ %dd= invoice.cost
+ %dt.muted= Invoice.human_attribute_name(:vat)
+ %dd= invoice.vat
+ %dt.muted= Invoice.human_attribute_name(:total)
+ %dd= invoice.total
diff --git a/app/views/invoice_lists/_form.html.haml b/app/views/invoice_lists/_form.html.haml
new file mode 100644
index 0000000000..c2bf3092cc
--- /dev/null
+++ b/app/views/invoice_lists/_form.html.haml
@@ -0,0 +1,25 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= entry_form(url: group_invoice_list_path(parent),
+ cancel_url: cancel_url,
+ data: { group: group_path(parent) }) do |f|
+
+ = f.hidden_field :recipient_ids
+ = f.labeled_input_field :title, help: t('.recipient_info', count: entry.recipients.size)
+
+ = f.labeled(:invoice_item_article) do
+ = select("temp", "invoice_article_id",
+ InvoiceArticle.all.pluck(:number, :id),
+ { include_blank: true },
+ { id: "invoice_item_article" })
+
+ = field_set_tag do
+ = f.labeled_inline_fields_for :invoice_items, 'invoice_items'
+
+
+ = f.labeled_input_field :description
+
+ = render "calculated", invoice: entry.decorate
diff --git a/app/views/invoice_lists/_invoice_items.html.haml b/app/views/invoice_lists/_invoice_items.html.haml
new file mode 100644
index 0000000000..781c0bf56e
--- /dev/null
+++ b/app/views/invoice_lists/_invoice_items.html.haml
@@ -0,0 +1,10 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= f.input_field(:name, class: 'span2', placeholder: InvoiceItem.human_attribute_name(:name))
+= f.input_field(:description, class: 'span2', rows: 1, placeholder: InvoiceItem.human_attribute_name(:description))
+= f.input_field(:vat_rate, class: 'span1', data: { recalculate: true }, placeholder: InvoiceItem.human_attribute_name(:vat_rate))
+= f.input_field(:unit_cost, class: 'span1', data: { recalculate: true }, placeholder: InvoiceItem.human_attribute_name(:unit_cost))
+= f.input_field(:count, class: 'span1', data: { recalculate: true }, placeholder: InvoiceItem.human_attribute_name(:count))
diff --git a/app/views/invoice_lists/new.js.haml b/app/views/invoice_lists/new.js.haml
new file mode 100644
index 0000000000..03757b99a2
--- /dev/null
+++ b/app/views/invoice_lists/new.js.haml
@@ -0,0 +1,6 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+$('#calculated').html('#{escape_javascript(render('calculated', invoice: entry.decorate))}')
diff --git a/app/views/invoices/_actions_index.html.haml b/app/views/invoices/_actions_index.html.haml
new file mode 100644
index 0000000000..9ff2f1cfb5
--- /dev/null
+++ b/app/views/invoices/_actions_index.html.haml
@@ -0,0 +1,14 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+- if parent.invoices.draft.exists?
+ = invoice_sending_dropdown(:group_invoice_list_path)
+
+- if entries.present?
+ = action_button ti('link.delete'), group_invoice_list_path, :trash, method: :delete, data: { checkable: true }
+ = invoices_export_dropdown
+ = invoices_print_dropdown
+
+= action_button(t('invoices.add'), new_group_invoice_path(parent), :plus)
diff --git a/app/views/invoices/_actions_show.html.haml b/app/views/invoices/_actions_show.html.haml
new file mode 100644
index 0000000000..a5fe13c4be
--- /dev/null
+++ b/app/views/invoices/_actions_show.html.haml
@@ -0,0 +1,16 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= button_action_edit if can?(:edit, entry)
+= invoices_export_dropdown
+= invoices_print_dropdown
+= button_action_destroy if can?(:destroy, entry)
+
+- if entry.remindable? && can?(:update, entry)
+ = action_button(t('crud.new.title', model: PaymentReminder.model_name.human),
+ '#', :plus, { data: { turbolinks: false, toggle: 'collapse', target: '#payment_reminder' } })
+
+ = action_button(t('crud.new.title', model: Payment.model_name.human),
+ '#', :plus, { data: { turbolinks: false, toggle: 'collapse', target: '#payment' } })
diff --git a/app/views/invoices/_attrs.html.haml b/app/views/invoices/_attrs.html.haml
new file mode 100644
index 0000000000..e99199610f
--- /dev/null
+++ b/app/views/invoices/_attrs.html.haml
@@ -0,0 +1,10 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= render 'payment_reminder_form' if @reminder
+= render 'payment_form' if @payment
+
+= render 'summary'
+= render 'details'
diff --git a/app/views/invoices/_button_new.html.haml b/app/views/invoices/_button_new.html.haml
new file mode 100644
index 0000000000..5356b6d1ff
--- /dev/null
+++ b/app/views/invoices/_button_new.html.haml
@@ -0,0 +1,5 @@
+- if can?(:create, group.layer_group.invoices.new)
+ = action_button(t('crud.new.title', model: Invoice.model_name.human),
+ new_group_invoice_list_path(group.layer_group, invoice: { recipient_ids: people.collect(&:id).join(',') }),
+ :plus)
+
diff --git a/app/views/invoices/_details.html.haml b/app/views/invoices/_details.html.haml
new file mode 100644
index 0000000000..88144a55c1
--- /dev/null
+++ b/app/views/invoices/_details.html.haml
@@ -0,0 +1,48 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+.invoice
+ .invoice-recipient-address
+ .contactable
+ .address
+ = invoice_receiver_address(entry)
+
+ .invoice-table
+ %h2= entry.title
+
+ %table.header
+ %tr
+ %td.left
+ = "#{t('activerecord.models.invoice.one')}: ##{entry.sequence_number}"
+ %td.right
+ = l(entry.sent_at, format: :long) if entry.sent_at?
+
+ = table(entry.invoice_items.list, class: 'table table-striped') do |t|
+ - t.attr(:name)
+ - t.attr(:description)
+ - t.attr(:count)
+ - t.attr(:vat_rate) { |i| i.decorate.vat_rate }
+ - t.attr(:unit_cost) { |i| i.decorate.unit_cost }
+ - t.attr(:total) { |i| i.decorate.cost }
+
+ .invoice-items-total
+ %table
+ %tr
+ %td.left
+ = captionize(:cost, Invoice)
+ %td.right
+ = entry.cost
+ %tr
+ %td.left
+ = captionize(:vat_rate, InvoiceItem)
+ %td.right
+ = entry.vat
+ %tr
+ %td.left
+ %b= captionize(:total_inkl_vat, Invoice)
+ %td.right
+ %b= entry.total
+
+ %p= entry.description
diff --git a/app/views/invoices/_filter.html.haml b/app/views/invoices/_filter.html.haml
new file mode 100644
index 0000000000..ed036009fe
--- /dev/null
+++ b/app/views/invoices/_filter.html.haml
@@ -0,0 +1,15 @@
+= content_for(:filter) do
+ = form_tag(nil, { method: :get, class: 'form-inline-search', role: 'search', remote: true, data: { spin: true } }) do
+ = hidden_field_tag :returning, true
+ = hidden_field_tag :page, 1
+
+ .control-group.has-feedback.has-clear
+ = label_tag(:q, t('global.button.search'), class: 'control-label')
+ = search_field_tag :q, params[:q], class: 'form-control', placeholder: t('global.button.search'), data: { submit: true }
+ %span.form-control-feedback{data: { clear: true }}
+ = icon(:remove)
+
+ = direct_filter_select(:state, Invoice.state_labels.to_a, nil)
+
+ - if params[:state].blank? || %w(overdue reminded).include?(params[:state])
+ = direct_filter_select(:due_since, invoice_due_since_options, t('.due_since'))
diff --git a/app/views/invoices/_form.html.haml b/app/views/invoices/_form.html.haml
new file mode 100644
index 0000000000..71ef7a7815
--- /dev/null
+++ b/app/views/invoices/_form.html.haml
@@ -0,0 +1,25 @@
+= entry_form(data: { group: group_path(parent) }) do |f|
+ = field_set_tag do
+ = f.labeled_input_fields :title, :description
+ = f.labeled(:state) do
+ = f.collection_select(:state, Invoice.state_labels.to_a, :first, :second)
+ = f.labeled_input_field :issued_at
+ = f.labeled_input_field :due_at
+
+ = field_set_tag do
+ = f.labeled_input_field :recipient_email
+ = f.labeled_input_field :recipient_address
+
+
+ = field_set_tag do
+ - if parent.invoice_articles.exists?
+ = f.labeled(:invoice_item_article) do
+ = select("temp", "invoice_article_id",
+ InvoiceArticle.all.pluck(:number, :id),
+ { include_blank: true },
+ { id: "invoice_item_article" })
+
+ = f.labeled_inline_fields_for :invoice_items, 'invoice_lists/invoice_items'
+
+
+ = render "invoice_lists/calculated", invoice: entry
diff --git a/app/views/invoices/_list.html.haml b/app/views/invoices/_list.html.haml
new file mode 100644
index 0000000000..1884f7b773
--- /dev/null
+++ b/app/views/invoices/_list.html.haml
@@ -0,0 +1,7 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= render "filter"
+= render "table"
diff --git a/app/views/invoices/_nav_left.html.haml b/app/views/invoices/_nav_left.html.haml
new file mode 100644
index 0000000000..438f505c0e
--- /dev/null
+++ b/app/views/invoices/_nav_left.html.haml
@@ -0,0 +1,15 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+%nav.nav-left-list
+ - current_user.finance_groups.each do |group|
+ - if group == parent
+ %h3.nav-left-title= link_to parent, parent
+ %ul.nav-left-list
+ = nav Invoice.model_name.human(count: 2), group_invoices_path(parent), %w(invoices)
+ = nav InvoiceArticle.model_name.human(count: 2), group_invoice_articles_path(parent), %w(invoice_articles)
+ = nav t('navigation.admin'), group_invoice_config_path(parent), %w(invoice_config)
+ - else
+ = nav(group, group_invoices_path(group))
diff --git a/app/views/invoices/_payment_form.html.haml b/app/views/invoices/_payment_form.html.haml
new file mode 100644
index 0000000000..c2b249969c
--- /dev/null
+++ b/app/views/invoices/_payment_form.html.haml
@@ -0,0 +1,7 @@
+- state = 'in' unless @payment_valid
+#payment{class: %W(collapse #{state}).compact.join(' ')}
+ = standard_form(@payment, url: group_invoice_payments_path(parent, entry)) do |f|
+ = f.error_messages
+ = f.labeled_input_fields :amount, :received_at
+ = save_form_buttons(f, nil, cancel_url: group_invoice_path(parent, entry))
+
diff --git a/app/views/invoices/_payment_reminder_form.html.haml b/app/views/invoices/_payment_reminder_form.html.haml
new file mode 100644
index 0000000000..4f1332b6ff
--- /dev/null
+++ b/app/views/invoices/_payment_reminder_form.html.haml
@@ -0,0 +1,7 @@
+- state = 'in' unless @reminder_valid
+
+#payment_reminder{class: %W(collapse #{state}).compact.join(' ')}
+ = standard_form(@reminder, url: group_invoice_payment_reminders_path(parent, entry)) do |f|
+ = f.error_messages
+ = f.labeled_input_fields :due_at, :message
+ = save_form_buttons(f, nil, cancel_url: group_invoice_path(parent, entry))
diff --git a/app/views/invoices/_summary.html.haml b/app/views/invoices/_summary.html.haml
new file mode 100644
index 0000000000..b1f97de967
--- /dev/null
+++ b/app/views/invoices/_summary.html.haml
@@ -0,0 +1,23 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+.invoice-state.clearfix
+ %table.invoice-state-table
+ %tr
+ %th.left
+ = captionize(:state, Invoice)
+ %th.left
+ = captionize(:total, Invoice)
+ %th.left
+ = captionize(:due_at, Invoice) if entry.due_at
+ %tr
+ %td
+ = format_attr(entry, :state)
+ %td
+ = entry.cost
+ %td
+ = format_attr(entry, :due_at)
+
+ .invoice-history
+ = invoice_history(entry)
diff --git a/app/views/invoices/_table.html.haml b/app/views/invoices/_table.html.haml
new file mode 100644
index 0000000000..bebd7000b4
--- /dev/null
+++ b/app/views/invoices/_table.html.haml
@@ -0,0 +1,12 @@
+.pagination-bar
+ = paginate @invoices
+
+= crud_table(data: { checkable: true }) do |t|
+ - t.col(check_box_tag(:all, 0, false, { data: :multiselect })) do |i|
+ - check_box_tag('ids[]', i.id, false, data: { multiselect: true })
+ - t.col(t.sort_header(:title)) do |invoice|
+ %strong= link_to invoice.title, group_invoice_path(parent, invoice)
+ - t.sortable_attrs(:sequence_number, :state, :recipient, :issued_at, :sent_at, :due_at)
+ - t.col(t.sort_header(:total)) { |i| i.decorate.total }
+
+= paginate @invoices
diff --git a/app/views/label_format/settings/update.js.haml b/app/views/label_format/settings/update.js.haml
new file mode 100644
index 0000000000..702601d507
--- /dev/null
+++ b/app/views/label_format/settings/update.js.haml
@@ -0,0 +1,5 @@
+- if current_user.show_global_label_formats
+ $('.global-formats').slideDown();
+- else
+ $('.global-formats').slideUp();
+
diff --git a/app/views/label_formats/_form.html.haml b/app/views/label_formats/_form.html.haml
index 7736246fb0..e1b932eb63 100644
--- a/app/views/label_formats/_form.html.haml
+++ b/app/views/label_formats/_form.html.haml
@@ -4,6 +4,7 @@
-# https://github.com/hitobito/hitobito.
= entry_form(buttons_bottom: false) do |f|
+ = hidden_field_tag :global, params[:global]
#main.row-fluid
%article.span6
@@ -17,5 +18,13 @@
= f.labeled_input_field :height, help_inline: 'mm'
= f.labeled_input_field :padding_top, help_inline: 'mm'
= f.labeled_input_field :padding_left, help_inline: 'mm'
+ = f.labeled_input_field :nickname
+ = f.labeled(:pp_post) do
+ .input-prepend.input-append
+ %span.add-on
+ = 'P.P.'
+ = f.input_field :pp_post, :placeholder => 'CH-3030 Bern'
+ %span.add-on
+ = 'Post CH AG'
%aside.span6
= image_tag("label_formats.png", size: '426x323')
diff --git a/app/views/label_formats/_list.html.haml b/app/views/label_formats/_list.html.haml
index aaf1e66532..8248b3b970 100644
--- a/app/views/label_formats/_list.html.haml
+++ b/app/views/label_formats/_list.html.haml
@@ -3,4 +3,14 @@
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-= crud_table :name, :page_size, :landscape, :dimensions, :font_size, :width, :height
+- global ||= false
+
+-if !global || can?(:manage_global, LabelFormat)
+ .btn-toolbar
+ = button_action_add(new_label_format_path(global: global))
+
+= table(entries, class: 'table table-striped table-hover') do |t|
+ - t.sortable_attr(:name) do |f|
+ - link_to_if(!global || can?(:manage_global, LabelFormat), f.name, label_format_path(f.id))
+ - t.sortable_attrs(:page_size, :landscape, :dimensions, :font_size, :width, :height)
+ - add_table_actions(t)
diff --git a/app/views/label_formats/index.html.haml b/app/views/label_formats/index.html.haml
new file mode 100644
index 0000000000..e9319ef45c
--- /dev/null
+++ b/app/views/label_formats/index.html.haml
@@ -0,0 +1,28 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+- title ti(:title, :models => models_label)
+
+#main
+ %h2= I18n.t('label_formats.own_labels')
+ = render 'list'
+
+ %h2
+ = I18n.t('label_formats.global_labels')
+ - if can?(:update_settings, current_user)
+
+ = check_box_tag(:show_global_label_formats,
+ true,
+ current_user.show_global_label_formats,
+ id: 'show_global_label_formats',
+ class: 'switcher',
+ data: { remote: true,
+ url: label_format_settings_path,
+ method: :put })
+ %label{for: 'show_global_label_formats'}
+
+ .global-formats{ style: element_visible(current_user.show_global_label_formats) }
+ = render 'list', entries: @global_entries, global: true
+
diff --git a/app/views/layouts/_quicksearch.html.haml b/app/views/layouts/_quicksearch.html.haml
index a7c8ccb82e..9f03a5a20f 100644
--- a/app/views/layouts/_quicksearch.html.haml
+++ b/app/views/layouts/_quicksearch.html.haml
@@ -11,5 +11,6 @@
placeholder: t('global.search.placeholder'),
data: { url: query_path,
person_url: person_path(1).gsub(/\/1$/, ''),
- group_url: groups_path }
- = button_tag icon(:search), class: 'btn'
\ No newline at end of file
+ group_url: groups_path,
+ event_url: event_path(1).gsub(/\/1$/, '') }
+ = button_tag icon(:search), class: 'btn'
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 6d9ca67c9f..819f02326e 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -10,6 +10,7 @@
%meta{charset: 'utf-8'}
%title= "#{Settings.application.name} - #{title}"
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1.0'}
+ %meta{name: 'turbolinks-cache-control', content: 'no-cache'}
= csrf_meta_tag
= favicon_link_tag 'favicon.ico'
@@ -77,10 +78,8 @@
= link_to label, url, target: '_blank'
%br/
%p
- - if Wagons.app_version.to_s > '0.0'
- = link_to "Version #{Wagons.app_version}", changelog_path
-
- %br/
+ = app_version_changelog_link
+ %br/
= link_to t('.source_code'), 'https://github.com/hitobito', target: '_blank'
= t('.available_under_license')
diff --git a/app/views/notes/_list.html.haml b/app/views/notes/_list.html.haml
new file mode 100644
index 0000000000..5d72b3a377
--- /dev/null
+++ b/app/views/notes/_list.html.haml
@@ -0,0 +1,17 @@
+-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+.pagination-bar
+ = render 'notes/paginator', notes: notes
+
+#notes-list
+ - if notes.total_count == 0
+ .pagination-info
+ = ti(:no_list_entries)
+
+ - notes.each do |note|
+ = render note, show_subject: show_subject
+
+= render 'notes/paginator', notes: notes
diff --git a/app/views/notes/_note.html.haml b/app/views/notes/_note.html.haml
new file mode 100644
index 0000000000..c324c0e440
--- /dev/null
+++ b/app/views/notes/_note.html.haml
@@ -0,0 +1,30 @@
+-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+.row-fluid.note{id: dom_id(note), class: ('is-current-subject' unless show_subject)}
+ - if show_subject
+ .note-image
+ - case note.subject
+ - when Group
+ = image_tag('group.svg')
+ - when Person
+ %img{src: note.subject.picture}
+
+ .note-body
+ - if show_subject
+ = assoc_link(note.subject)
+
+ %small.muted.note-author
+ = person_link(note.author)
+ .note-date
+ = t('.created', time_ago: time_ago_in_words(note.created_at))
+ - if can?(:destroy, note)
+ = link_to icon(:trash),
+ note_path(@group, note),
+ method: :delete,
+ remote: true,
+ data: { confirm: ti(:confirm_delete) }
+
+ = auto_link(simple_format(note.text))
diff --git a/app/views/notes/_paginator.html.haml b/app/views/notes/_paginator.html.haml
new file mode 100644
index 0000000000..6e85ed9926
--- /dev/null
+++ b/app/views/notes/_paginator.html.haml
@@ -0,0 +1,22 @@
+-# Copyright (c) 2012-2017, Dachverband Schweizer Jugendparlamente. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+- if notes.total_pages > 1
+ .pagination.notes-pagination
+ %ul
+ %li{class: notes.first_page? && :disabled}
+ = link_to_previous_page notes,
+ t('views.pagination.previous'),
+ param_name: :notes_page,
+ params: { anchor: 'notes' }
+ - if notes.first_page?
+ %a= t('views.pagination.previous')
+ %li{class: notes.last_page? && :disabled}
+ = link_to_next_page notes,
+ t('views.pagination.next'),
+ param_name: :notes_page,
+ params: { anchor: 'notes' }
+ - if notes.last_page?
+ %a= t('views.pagination.next')
diff --git a/app/views/notes/_section.html.haml b/app/views/notes/_section.html.haml
new file mode 100644
index 0000000000..85407cd545
--- /dev/null
+++ b/app/views/notes/_section.html.haml
@@ -0,0 +1,28 @@
+-# Copyright (c) 2012-2017, Dachverband Schweizer Jugendparlamente. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+
+%section
+ %h2
+ = Note.model_name.human(count: 2)
+ - if can?(:create, entry.notes.new)
+ %span.pull-right
+ = action_button(t('notes.new_note'),
+ '#',
+ 'plus',
+ id: 'notes-new-button',
+ class: 'btn-small notes-swap',
+ data: { swap: 'notes-swap' })
+
+ #notes-form.notes-swap{ style: 'display: none;', data: { swap: 'notes-swap' } }
+ #notes-error.alert.alert-error{ style: 'display: none;' }
+
+ = form_for(Note.new, url: create_path, remote: true) do |f|
+ = f.text_area(:text, rows: 5, class: 'input-block-level')
+ = save_form_buttons(f, nil, '')
+
+ = render 'notes/list',
+ notes: entry.notes.includes(:author, :subject).list.page(params[:notes_page]).per(10),
+ show_subject: false
diff --git a/app/views/notes/create.js.haml b/app/views/notes/create.js.haml
new file mode 100644
index 0000000000..5bfd72394c
--- /dev/null
+++ b/app/views/notes/create.js.haml
@@ -0,0 +1,9 @@
+-# Copyright (c) 2012-2017, Dachverband Schweizer Jugendparlamente. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+- if @note.errors.blank?
+ App.Notes.addNote('#{escape_javascript(render @note, show_subject: false)}');
+- else
+ App.Notes.showError('#{@note.errors.full_messages.join("\n")}');
diff --git a/app/views/notes/destroy.js.haml b/app/views/notes/destroy.js.haml
new file mode 100644
index 0000000000..68a5a29f87
--- /dev/null
+++ b/app/views/notes/destroy.js.haml
@@ -0,0 +1,9 @@
+-# Copyright (c) 2012-2016, hitobito AG. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+- if @note.destroyed?
+ $('#notes-list ##{dom_id(@note)}').remove()
+- else
+ alert('#{t('.failure', errors: @note.errors.full_messages.join(', '))}');
diff --git a/app/views/person/notes/index.html.haml b/app/views/notes/index.html.haml
similarity index 50%
rename from app/views/person/notes/index.html.haml
rename to app/views/notes/index.html.haml
index beae54f426..1d0c8139ee 100644
--- a/app/views/person/notes/index.html.haml
+++ b/app/views/notes/index.html.haml
@@ -1,8 +1,9 @@
-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
--# hitobito_dsj and licensed under the Affero General Public License version 3
+-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_dsj.
+-# https://github.com/hitobito/hitobito.
- @sheet = Sheet::Group.new(self)
-= render 'person/notes/list', notes: @notes, show_person: true
+#notes-index
+ = render 'list', notes: @notes, show_subject: true
diff --git a/app/views/people/_actions_index.html.haml b/app/views/people/_actions_index.html.haml
index fab696ca50..8207ed45de 100644
--- a/app/views/people/_actions_index.html.haml
+++ b/app/views/people/_actions_index.html.haml
@@ -5,6 +5,8 @@
- if can?(:new, @group.roles.new)
= action_button(t('.add_person'), new_group_role_path(@group), :plus)
+= render 'invoices/button_new', group: @group, people: @people
+
- if can?(:new, @group.roles.new)
= action_button(t('.import_list'), new_group_csv_imports_path, :upload)
diff --git a/app/views/people/_actions_show.html.haml b/app/views/people/_actions_show.html.haml
index 003b736916..231fe80ebb 100644
--- a/app/views/people/_actions_show.html.haml
+++ b/app/views/people/_actions_show.html.haml
@@ -6,6 +6,11 @@
- if can?(:edit, entry)
= button_action_edit
+- if can?(:destroy, entry)
+ = button_action_destroy(nil, { class: "btn-danger", data: { confirm: t('person.confirm_delete',
+ person: entry.person) } })
+
+= render 'invoices/button_new', group: parent, people: [entry]
= dropdown_people_export(can?(:show_full, entry), false)
- if entry.email? && can?(:send_password_instructions, entry)
diff --git a/app/views/people/_attrs.html.haml b/app/views/people/_attrs.html.haml
index 3efc323449..02f18336e0 100644
--- a/app/views/people/_attrs.html.haml
+++ b/app/views/people/_attrs.html.haml
@@ -20,12 +20,12 @@
- if entry.additional_information?
%h2= Person.human_attribute_name(:additional_information)
- = simple_format(entry.additional_information)
+ = auto_link(simple_format(entry.additional_information))
= render_extensions :show_left
- if can?(:index_notes, entry)
- = render 'notes'
+ = render 'notes/section', create_path: group_person_notes_path(@group, entry)
- if can?(:show_full, entry)
%aside.span6.offset1
@@ -34,6 +34,9 @@
= render 'add_requests'
= render 'event_aside', title: Event::Application.model_name.human(count: 2), collection: entry.pending_applications
= render 'event_aside', title: t('.events'), collection: entry.upcoming_events
+
+ = render_extensions :show_event
+
= render 'qualifications', show_buttons: true
= render_extensions :show_right
diff --git a/app/views/people/_fields.html.haml b/app/views/people/_fields.html.haml
index fe04ee0ef6..46aea64ed0 100644
--- a/app/views/people/_fields.html.haml
+++ b/app/views/people/_fields.html.haml
@@ -11,7 +11,12 @@
- without_relations ||= false
= field_set_tag do
- = f.labeled_input_fields :first_name, :last_name, :nickname, :company_name, :company
+ = f.labeled_input_fields :first_name, :last_name, :nickname
+ = f.labeled_string_field(:company_name,
+ placeholder: I18n.t('global.search.placeholder_company_name'),
+ data: { provide: 'entity',
+ url: query_company_name_path })
+ = f.labeled_input_fields :company
= render_extensions :name_fields, locals: { f: f }
diff --git a/app/views/people/_list.html.haml b/app/views/people/_list.html.haml
index 4402f89b47..af453f8a6f 100644
--- a/app/views/people/_list.html.haml
+++ b/app/views/people/_list.html.haml
@@ -1,20 +1,20 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
- title @group.to_s
-- content_for(:filter, FilterNavigation::People.new(self, @group, params).to_s)
+- content_for(:filter, FilterNavigation::People.new(self, @group, @person_filter).to_s)
.pagination-bar
= paginate @people
.pagination-info
- - if @all_count > 0
+ - if @person_filter.all_count > 0
= t('.number_of_people_shown', count: @people.total_count)
- - unless @all_count == @people.total_count
- = muted(t('.number_of_people_hidden', count: @all_count - @people.total_count))
+ - unless @person_filter.all_count == @people.total_count
+ = muted(t('.number_of_people_hidden', count: @person_filter.all_count - @people.total_count))
- else
= ti(:no_list_entries)
@@ -25,15 +25,20 @@
= crud_table do |t|
- t.col('') do |p|
.profil= image_tag(p.picture.thumb.url, size: '32x32')
- - sortable_grouped_person_attr(t, %w(last_name first_name nickname)) do |p|
+ - sortable_grouped_person_attr(t, last_name: true, first_name: true, nickname: true) do |p|
%strong
-# Any person listed can be shown
- = link_to(p.to_s(:list), @multiple_groups ? group_person_path(p.default_group_id, p) : group_person_path(@group, p))
+ = link_to(p.to_s(:list),
+ @person_filter.multiple_groups ? group_person_path(p.default_group_id, p) : group_person_path(@group, p))
%br/
= muted p.additional_name
- - t.col(t.sort_header(:roles, Role.model_name.human(count: 2))) { |p| p.roles_short(@multiple_groups ? nil : @group) }
- - t.col(Person.human_attribute_name(:emails)) { |p| p.all_emails(!index_full_ability?) }
- - t.col(PhoneNumber.model_name.human(count: 2)) { |p| p.all_phone_numbers(!index_full_ability?) }
- - sortable_grouped_person_attr(t, %w(zip_code town), :address) { |p| p.complete_address }
+ - t.col(t.sort_header(:roles, Role.model_name.human(count: 2))) do |p|
+ = p.roles_short(@person_filter.multiple_groups ? nil : @group)
+ - t.col(Person.human_attribute_name(:emails)) do |p|
+ = p.all_emails(!index_full_ability?)
+ - t.col(PhoneNumber.model_name.human(count: 2)) do |p|
+ = p.all_phone_numbers(!index_full_ability?)
+ - sortable_grouped_person_attr(t, address: false, zip_code: true, town: true) do |p|
+ = p.complete_address
= paginate @people
diff --git a/app/views/people/_notes.html.haml b/app/views/people/_notes.html.haml
deleted file mode 100644
index 6acccb4982..0000000000
--- a/app/views/people/_notes.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
--# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
--# hitobito_dsj and licensed under the Affero General Public License version 3
--# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_dsj.
-
-%h2#person-notes= Person::Note.model_name.human(count: 2)
-
-- if can?(:create, Person::Note)
- %button#person-notes-new-button.btn.person-notes-swap{ data: { swap: 'person-notes-swap' } }= t('.new_note')
-
- #person-notes-form.person-notes-swap{ style: 'display: none;', data: { swap: 'person-notes-swap' } }
- %h3= t('.new_note')
-
- #person-notes-error.alert.alert-error{ style: 'display: none;' }
-
- = form_for(Person::Note.new, url: group_person_notes_path(@group, entry), remote: true) do |f|
- = f.text_area(:text, rows: 7, width: '100%')
- = save_form_buttons(f, nil, '')
-
-= render 'person/notes/list',
- notes: entry.notes.includes(:author).page(params[:notes_page]).per(10),
- show_person: false
diff --git a/app/views/people/_tags.html.haml b/app/views/people/_tags.html.haml
index 26ca354390..70ec686410 100644
--- a/app/views/people/_tags.html.haml
+++ b/app/views/people/_tags.html.haml
@@ -1,7 +1,7 @@
-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
--# hitobito_dsj and licensed under the Affero General Public License version 3
+-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_dsj.
+-# https://github.com/hitobito/hitobito.
- if can?(:index_tags, entry) || can?(:manage_tags, entry)
%section.tags
@@ -9,7 +9,7 @@
= render 'person/tags/list', tags: @tags
- if can?(:manage_tags, entry)
- %span.label.person-tag.person-tag-add
+ %button.chip.chip-add.person-tag-add
= t('.add_tag')
= icon(:plus)
diff --git a/app/views/people_filters/_filter.html.haml b/app/views/people_filters/_filter.html.haml
new file mode 100644
index 0000000000..f0a2311770
--- /dev/null
+++ b/app/views/people_filters/_filter.html.haml
@@ -0,0 +1,8 @@
+.accordion-group
+ .accordion-heading
+ %a.accordion-toggle.collapsed.header{ href: "##{id}", data: { toggle: :collapse } }
+ = caption
+
+ .accordion-body.collapse{ id: id }
+ .accordion-inner
+ = yield
diff --git a/app/views/people_filters/_form.html.haml b/app/views/people_filters/_form.html.haml
index 0daaf3c3a2..537c6777af 100644
--- a/app/views/people_filters/_form.html.haml
+++ b/app/views/people_filters/_form.html.haml
@@ -1,31 +1,27 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito.
-= standard_form(path_args(entry), noindent: true) do |f|
+= standard_form(path_args(entry), noindent: true, stacked: true) do |f|
= render 'search_or_save_buttons', f: f
= render_extensions :form, locals: { f: f }
= f.error_messages
- .label-columns
- = field_set_tag(t('.prompt_role_selection')) do
- - @role_types.each do |layer, groups|
- %h4.filter-toggle= layer
- - groups.each do |group, role_types|
- .control-group
- = label_tag(nil, group, class: 'filter-toggle control-label')
- .controls
- - role_types.each do |role_type|
- = f.inline_check_box(:role_type_ids,
- role_type.id,
- role_type.label,
- checked: entry.role_types.include?(role_type.sti_name))
+ = render 'range', f: f
+
+ .accordion
+ = render(layout: 'filter', locals: { caption: t('people_filters.role.title'), id: 'roles' }) do
+ = render 'role', f: f
+
+ - if @qualification_kinds.present?
+ = render(layout: 'filter', locals: { caption: t('people_filters.qualification.title'), id: 'qualifications' }) do
+ = render 'qualification', f: f
+
- if can?(:create, entry)
- = field_set_tag(t('.save_filter')) do
- = f.labeled_input_field :name, placeholder: t('.save_filter_placeholder')
+ = f.labeled_input_field :name, placeholder: t('.save_filter_placeholder')
= render 'search_or_save_buttons', f: f
diff --git a/app/views/people_filters/_qualification.html.haml b/app/views/people_filters/_qualification.html.haml
new file mode 100644
index 0000000000..835cd5625c
--- /dev/null
+++ b/app/views/people_filters/_qualification.html.haml
@@ -0,0 +1,35 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+- filter = entry.filter_chain[:qualification]
+
+.label-columns
+ = field_set_tag(t('.prompt_qualification_selection')) do
+ - unless can?(:index_full_people, @group)
+ .alert.alert-warning= t('.not_enough_permissions')
+
+ .controls
+ - @qualification_kinds.each do |kind|
+ - dom_id = "qualification_kind_id_#{kind.id}"
+ = label_tag(dom_id, class: 'checkbox inline') do
+ = check_box_tag("filters[qualification][qualification_kind_ids][]",
+ kind.id,
+ filter && filter.args[:qualification_kind_ids].include?(kind.id),
+ id: dom_id)
+ = kind.to_s
+
+ = field_set_tag(t('.prompt_validity')) do
+ = render 'simple_radio',
+ attr: "filters[qualification][validity]",
+ value: 'active',
+ checked: true # first item is checked per default
+ = render 'simple_radio',
+ attr: "filters[qualification][validity]",
+ value: 'reactivateable',
+ checked: filter && filter.args[:validity] == 'reactivateable'
+ = render 'simple_radio',
+ attr: "filters[qualification][validity]",
+ value: 'all',
+ checked: filter && filter.args[:validity] == 'all'
diff --git a/app/views/people_filters/_range.html.haml b/app/views/people_filters/_range.html.haml
new file mode 100644
index 0000000000..b06cf9de5d
--- /dev/null
+++ b/app/views/people_filters/_range.html.haml
@@ -0,0 +1,15 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+= render 'simple_radio',
+ attr: 'range',
+ value: 'deep',
+ checked: true,
+ caption: "people_filters.form.range.#{@group.layer? ? 'deep' : 'group_deep'}"
+- if @group.layer?
+ = render 'simple_radio', attr: 'range', value: 'layer', checked: entry.range == 'layer'
+= render 'simple_radio', attr: 'range', value: 'group', checked: entry.range == 'group'
+
+%br/
diff --git a/app/views/people_filters/_role.html.haml b/app/views/people_filters/_role.html.haml
new file mode 100644
index 0000000000..fac2fec618
--- /dev/null
+++ b/app/views/people_filters/_role.html.haml
@@ -0,0 +1,44 @@
+-# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+- filter = entry.filter_chain[:role]
+
+.label-columns
+ = field_set_tag(t('.prompt_role_selection')) do
+ - @role_types.each do |layer, groups|
+ .layer{ class: [@group.klass.label, @group.layer_group.class.label].include?(layer) && 'same-layer' }
+ %h4.filter-toggle= layer
+ - groups.each do |group, role_types|
+ .group.control-group{ class: group == @group.klass.label && 'same-group' }
+ %h5.filter-toggle= group
+ .controls
+ - role_types.each do |role_type|
+ - id = "filters_role_role_type_ids_#{role_type.id}"
+ = label_tag(nil, id, class: 'checkbox inline') do
+ = check_box_tag("filters[role][role_type_ids][]",
+ role_type.id,
+ filter && filter.to_hash[:role_types].include?(role_type.to_s),
+ id: id)
+ = role_type.label
+
+ = field_set_tag do
+ = label_tag(:duration) do
+ %label=t('.prompt_role_duration')
+ .input-prepend
+ %span{class: 'add-on'}
+ = icon(:calendar)
+ = text_field_tag("filters[role][start_at]", filter && filter.args[:start_at], class: 'date span2')
+ %div{style: 'vertical-align: middle; margin: 0px 5px 15px 5px; display: inline-block;'}
+
+ =t('.prompt_role_duration_until')
+ .input-prepend
+ %span{class: 'add-on'}
+ = icon(:calendar)
+ = text_field_tag("filters[role][finish_at]", filter && filter.args[:finish_at], class: 'date span2')
+ - %w(active created deleted).each do |key|
+ = render 'simple_radio',
+ attr: "filters[role][kind]",
+ value: key,
+ checked: filter && filter.args[:kind] == key
diff --git a/app/views/people_filters/_search_or_save_buttons.html.haml b/app/views/people_filters/_search_or_save_buttons.html.haml
index 161bfc96d0..6e7a994226 100644
--- a/app/views/people_filters/_search_or_save_buttons.html.haml
+++ b/app/views/people_filters/_search_or_save_buttons.html.haml
@@ -1,7 +1,13 @@
+-# Copyright (c) 2015-2017, Hitobito AG. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
.btn-toolbar
- .btn-group
- = f.button(t('global.button.search'), class: 'btn btn-primary', value: 'search')
+ - unless entry.persisted?
+ .btn-group
+ = f.button(t('global.button.search'), class: 'btn btn-primary', value: 'search')
- if can?(:create, entry)
.btn-group
= f.button(t('people_filters.form.save_search'), class: 'btn btn-primary', value: 'save')
- = link_to(t('global.button.cancel'), people_list_path, class: 'link')
\ No newline at end of file
+ = link_to(t('global.button.cancel'), people_list_path, class: 'link')
diff --git a/app/views/people_filters/_simple_radio.html.haml b/app/views/people_filters/_simple_radio.html.haml
index c780cfc15e..c35cc95a50 100644
--- a/app/views/people_filters/_simple_radio.html.haml
+++ b/app/views/people_filters/_simple_radio.html.haml
@@ -1,7 +1,6 @@
-- caption ||= ".#{attr}.#{value}"
+- key = attr.gsub(/\W+/, '_').gsub(/_+$/, '')
+- caption ||= "people_filters.form.#{key}.#{value}"
-= label_tag("#{attr}_#{value}", class: 'radio') do
- = radio_button_tag(attr,
- value,
- params[attr] == value || defined?(first))
- = t(caption)
\ No newline at end of file
+= label_tag("#{key}_#{value}", class: 'radio') do
+ = radio_button_tag(attr, value, checked)
+ = t(caption)
diff --git a/app/views/people_filters/qualification.html.haml b/app/views/people_filters/qualification.html.haml
deleted file mode 100644
index 4d76c34b90..0000000000
--- a/app/views/people_filters/qualification.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
--# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
--# hitobito and licensed under the Affero General Public License version 3
--# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito.
-
-- title(t('.title'))
-
-= form_tag(people_list_path, method: :get, class: 'form-noindent') do |f|
- = hidden_field_tag 'filter', 'qualification'
-
- = render 'search_button'
-
- .label-columns
- = field_set_tag(t('.prompt_qualification_selection')) do
- .controls
- - @qualification_kinds.each do |kind|
- - dom_id = "qualification_kind_id_#{kind.id}"
- = label_tag(dom_id, class: 'checkbox inline') do
- = check_box_tag('qualification_kind_id[]',
- kind.id,
- Array(params[:qualification_kind_id]).include?(kind.id.to_s),
- id: dom_id)
- = kind.to_s
-
- = field_set_tag(t('.prompt_validity')) do
- = render 'simple_radio', attr: 'validity', value: 'active', first: true
- = render 'simple_radio', attr: 'validity', value: 'reactivateable'
- = render 'simple_radio', attr: 'validity', value: 'all'
-
- = field_set_tag(t('.prompt_kind')) do
- = render 'simple_radio', attr: 'kind', value: 'deep', first: true,
- caption: ".kind.#{@group.layer? ? 'deep' : 'group_deep'}"
- = render 'simple_radio', attr: 'kind', value: 'layer'
- = render 'simple_radio', attr: 'kind', value: 'group'
-
- = render 'search_button'
diff --git a/app/views/person/colleagues/index.html.haml b/app/views/person/colleagues/index.html.haml
new file mode 100644
index 0000000000..1e8bd90db1
--- /dev/null
+++ b/app/views/person/colleagues/index.html.haml
@@ -0,0 +1,33 @@
+-# Copyright (c) 2017, Dachverband Schweizer Jugendparlamente. This file is part of
+-# hitobito and licensed under the Affero General Public License version 3
+-# or later. See the COPYING file at the top-level directory or at
+-# https://github.com/hitobito/hitobito.
+
+
+= paginate @colleagues
+
+= table(@colleagues, class: 'table table-striped table-hover') do |t|
+ - t.col('') do |p|
+ .profil= image_tag(p.picture.thumb.url, size: '32x32')
+ - sortable_grouped_person_attr(t, last_name: true, first_name: true, nickname: true) do |p|
+ - @showable = can?(:show, p)
+ %strong
+ = link_to_if(@showable,
+ p.to_s(:list),
+ group_person_path(p.default_group_id, p))
+ %br/
+ = muted p.additional_name
+ - t.col(t.sort_header(:roles, Role.model_name.human(count: 2))) do |p|
+ - if @showable
+ = p.roles_short
+ - t.col(Person.human_attribute_name(:emails)) do |p|
+ - if @showable
+ = p.all_emails(true)
+ - t.col(PhoneNumber.model_name.human(count: 2)) do |p|
+ - if @showable
+ = p.all_phone_numbers(true)
+ - sortable_grouped_person_attr(t, address: false, zip_code: true, town: true) do |p|
+ - if @showable
+ = p.complete_address
+
+= paginate @colleagues
diff --git a/app/views/person/history/index.html.haml b/app/views/person/history/index.html.haml
index edfd58437a..1f56767224 100644
--- a/app/views/person/history/index.html.haml
+++ b/app/views/person/history/index.html.haml
@@ -6,7 +6,8 @@
%h2= Role.model_name.human(count: 2)
= table(@roles, class: 'table table-striped') do |t|
- - t.attr(:group_id, Group.model_name.human)
+ - t.col(Group.model_name.human) do |r|
+ = GroupDecorator.new(r.group).link_with_layer
- t.col(Role.model_name.human) do |r|
= r.to_s
- t.attr(:created_at, t('global.from'))
diff --git a/app/views/person/invoices/_actions_index.html.haml b/app/views/person/invoices/_actions_index.html.haml
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/views/person/invoices/_table.html.haml b/app/views/person/invoices/_table.html.haml
new file mode 100644
index 0000000000..f321d49f66
--- /dev/null
+++ b/app/views/person/invoices/_table.html.haml
@@ -0,0 +1,12 @@
+- title @person.to_s
+
+.pagination-bar
+ = paginate @invoices
+
+= crud_table(data: { checkable: true }) do |t|
+ - t.col(t.sort_header(:title)) do |invoice|
+ %strong= link_to invoice.title, group_invoice_path(invoice.group, invoice)
+ - t.sortable_attrs(:sequence_number, :state, :issued_at, :sent_at, :due_at)
+ - t.col(t.sort_header(:total)) { |i| i.decorate.total }
+
+= paginate @invoices
diff --git a/app/views/person/notes/_list.html.haml b/app/views/person/notes/_list.html.haml
deleted file mode 100644
index eabda32be7..0000000000
--- a/app/views/person/notes/_list.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
--# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
--# hitobito_dsj and licensed under the Affero General Public License version 3
--# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_dsj.
-
-#person-notes-pagination.pagination-bar
- = render 'person/notes/paginator', notes: notes
-
- .pagination-info
- - if notes.total_count == 0
- = ti(:no_list_entries)
-
-#person-notes-list
- - notes.each do |note|
- = render note, show_person: show_person
-
-= render 'person/notes/paginator', notes: notes
diff --git a/app/views/person/notes/_note.html.haml b/app/views/person/notes/_note.html.haml
deleted file mode 100644
index e97c19be1f..0000000000
--- a/app/views/person/notes/_note.html.haml
+++ /dev/null
@@ -1,27 +0,0 @@
--# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
--# hitobito_dsj and licensed under the Affero General Public License version 3
--# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_dsj.
-
-
-- if show_person
- .row-fluid.note
- %img.note-image{:src => note.person.picture}
- .note-body
- %small.muted.note-author
- = !show_person ? person_link(note.author) : note.author
- = t('.created', time_ago: time_ago_in_words(note.created_at))
- .note-person
- = person_link(note.person)
- = auto_link(simple_format(note.text))
-- else
- .row-fluid.note
- .span12
- %small.muted
- = !show_person ? person_link(note.author) : note.author
- - if show_person
- = t('.wrote_about')
- = person_link(note.person)
- .pull-right
- = t('.created', time_ago: time_ago_in_words(note.created_at))
- = auto_link(simple_format(note.text))
diff --git a/app/views/person/notes/_paginator.html.haml b/app/views/person/notes/_paginator.html.haml
deleted file mode 100644
index 4982c704c7..0000000000
--- a/app/views/person/notes/_paginator.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
--# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
--# hitobito_dsj and licensed under the Affero General Public License version 3
--# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_dsj.
-
-- if notes.total_count > 0
- .pagination
- %ul
- %li{class: notes.first_page? && :disabled}
- = link_to_previous_page notes, "« #{t('.newer_notes')}",
- param_name: :notes_page,
- params: { anchor: 'person-notes' }
- - if notes.first_page?
- %a= "« #{t('.newer_notes')}"
- %li{class: notes.last_page? && :disabled}
- = link_to_next_page notes, "#{t('.older_notes')} »",
- param_name: :notes_page,
- params: { anchor: 'person-notes' }
- - if notes.last_page?
- %a= "#{t('.older_notes')} »"
diff --git a/app/views/person/notes/create.js.haml b/app/views/person/notes/create.js.haml
deleted file mode 100644
index cd4f10ffc6..0000000000
--- a/app/views/person/notes/create.js.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- if @note.errors.blank?
- App.PersonNotes.addNote('#{escape_javascript(render @note, show_person: false)}');
-- else
- App.PersonNotes.showError('#{@note.errors.full_messages.join("\n")}');
diff --git a/app/views/person/tags/_tag.html.haml b/app/views/person/tags/_tag.html.haml
index 2edeae7525..079eee7347 100644
--- a/app/views/person/tags/_tag.html.haml
+++ b/app/views/person/tags/_tag.html.haml
@@ -1,8 +1,8 @@
-%span.label.label-inverse.person-tag{data: {tag_id: tag.id}}
+%span.chip.person-tag{data: {tag_id: tag.id}}
= tag.name_without_category
- if can?(:manage_tags, person)
= link_to icon(:remove),
- group_person_tag_path(person_id: person.id, name: tag.name),
+ group_person_tags_path(person_id: person.id, name: tag.name),
method: :delete,
data: { tag_id: tag.id },
remote: true,
diff --git a/app/views/roles/_popover.html.haml b/app/views/roles/_popover.html.haml
index a4275c6e7d..690458e693 100644
--- a/app/views/roles/_popover.html.haml
+++ b/app/views/roles/_popover.html.haml
@@ -1,4 +1,5 @@
-= standard_form entry, url: group_role_path(group_id: entry.group.id, id: entry.id), remote: true do |f|
+- action = entry.persisted? ? :update : :create
+= standard_form [group, entry], controller: :roles, action: action, remote: true do |f|
= f.error_messages
- if @group_selection.present? && @group_selection.size > 1
= f.labeled(:group_id) do
@@ -32,5 +33,8 @@
class: 'span4',
help: t('roles.fields.help_optional_label'),
data: { provide: :typeahead, source: entry.klass.available_labels })
+
+ - if @person_id
+ = f.hidden_field :person_id, value: @person_id
= save_form_buttons(f, ti(:"button.save"), '#')
diff --git a/app/views/roles/_popover.js.haml b/app/views/roles/_popover.js.haml
new file mode 100644
index 0000000000..a679b9c044
--- /dev/null
+++ b/app/views/roles/_popover.js.haml
@@ -0,0 +1,12 @@
+- id ||= nil
+:plain
+ var el = #{id ? "'##{id}'" : "$('body').data('popover')"};
+ $($('body').data('popover')).popover('destroy');
+ $(el).popover({content: '#{j(render('popover.html', group: entry.group))}',
+ title: "#{t(title_key, person: entry.person)}",
+ container: 'body',
+ placement: 'bottom',
+ html: true }).popover('show');
+
+ $('body').data('popover', el);
+ $('body .chosen-select').each(App.activateChosen);
diff --git a/app/views/roles/create.js.haml b/app/views/roles/create.js.haml
new file mode 100644
index 0000000000..c727ce2732
--- /dev/null
+++ b/app/views/roles/create.js.haml
@@ -0,0 +1,7 @@
+- if entry.persisted?
+ $($('body').data('popover')).parent().hide()
+ $($('body').data('popover')).popover('destroy').replaceWith('#{j(PersonDecorator.new(entry.person).roles_short)}')
+ $('##{dom_id(entry)}').parent().toggle('highlight', {duration: 1000})
+- else
+ - @person_id = entry.person_id
+ = render('popover', title_key: 'roles.form.new_role_for_person' )
diff --git a/app/views/roles/edit.js.haml b/app/views/roles/edit.js.haml
index 02539a8854..61082d510c 100644
--- a/app/views/roles/edit.js.haml
+++ b/app/views/roles/edit.js.haml
@@ -1,10 +1 @@
-:plain
- $($('body').data('popover')).popover('destroy')
- $('##{dom_id(entry)}').popover({content: '#{j(render('popover'))}',
- title: "#{t('roles.form.edit_role_for_person', person: entry.person)}",
- container: 'body',
- placement: 'bottom',
- html: true }).popover('show')
-
- $('body').data('popover', '##{dom_id(entry)}')
- $('body .chosen-select').each(App.activateChosen)
+= render('popover', title_key: 'roles.form.edit_role_for_person', id: dom_id(entry) )
diff --git a/app/views/roles/new.js.haml b/app/views/roles/new.js.haml
new file mode 100644
index 0000000000..75a8d96132
--- /dev/null
+++ b/app/views/roles/new.js.haml
@@ -0,0 +1 @@
+= render('popover', title_key: 'roles.form.new_role_for_person', id: "role_#{params[:role_id]}" )
diff --git a/app/views/roles/update.js.haml b/app/views/roles/update.js.haml
index b7390f00d8..36dabcf54f 100644
--- a/app/views/roles/update.js.haml
+++ b/app/views/roles/update.js.haml
@@ -1,3 +1,4 @@
+- group ||= defined?(@group) ? group : nil
$('##{dom_id(@old_role || @role)}').parent().hide()
-$('##{dom_id(@old_role || @role)}').popover('destroy').replaceWith('#{j(PersonDecorator.new(entry.person).roles_short)}')
+$('##{dom_id(@old_role || @role)}').popover('destroy').replaceWith('#{j(PersonDecorator.new(entry.person).roles_short(group))}')
$('##{dom_id(entry)}').parent().toggle('highlight', {duration: 1000})
diff --git a/app/views/subscriber/group/_tags.html.haml b/app/views/subscriber/group/_tags.html.haml
index bc348bf0b4..7175e37cc1 100644
--- a/app/views/subscriber/group/_tags.html.haml
+++ b/app/views/subscriber/group/_tags.html.haml
@@ -1,7 +1,7 @@
-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
--# hitobito_dsj and licensed under the Affero General Public License version 3
+-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_dsj.
+-# https://github.com/hitobito/hitobito.
%p= t('.only_add_persons_with_tags')
diff --git a/app/views/subscriptions/_subscription.html.haml b/app/views/subscriptions/_subscription.html.haml
index 53dd15aee6..0c26d7b59b 100644
--- a/app/views/subscriptions/_subscription.html.haml
+++ b/app/views/subscriptions/_subscription.html.haml
@@ -1,7 +1,7 @@
-# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
--# hitobito_dsj and licensed under the Affero General Public License version 3
+-# hitobito and licensed under the Affero General Public License version 3
-# or later. See the COPYING file at the top-level directory or at
--# https://github.com/hitobito/hitobito_dsj.
+-# https://github.com/hitobito/hitobito.
- if subscription.subscriber.is_a?(Group) && subscription.related_role_types.present?
%h4= subscription.subscriber.with_layer.join(' / ')
diff --git a/bin/ci/wagon_setup.sh b/bin/ci/wagon_setup.sh
index cd72ec27a0..41f745fbde 100755
--- a/bin/ci/wagon_setup.sh
+++ b/bin/ci/wagon_setup.sh
@@ -14,6 +14,11 @@ bundle install --path vendor/bundle
for d in ../hitobito_*; do
cp Gemfile.lock $d
+ mkdir -p $d/.bundle
+ bundle_config=$d/.bundle/config
+ echo "---" > $bundle_config
+ echo "BUNDLE_PATH: ../hitobito/vendor/bundle" >> $bundle_config
+ echo "BUNDLE_DISABLE_SHARED_GEMS: '1'" >> $bundle_config
done
rm -rf tmp/tarantula
diff --git a/bin/rake b/bin/rake
new file mode 100755
index 0000000000..615dea9089
--- /dev/null
+++ b/bin/rake
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
+require 'bundler/setup'
+load Gem.bin_path('rake', 'rake')
diff --git a/bin/rspec b/bin/rspec
new file mode 100755
index 0000000000..6e6709219a
--- /dev/null
+++ b/bin/rspec
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
+require 'bundler/setup'
+load Gem.bin_path('rspec-core', 'rspec')
diff --git a/bin/spring b/bin/spring
new file mode 100755
index 0000000000..fb2ec2ebb4
--- /dev/null
+++ b/bin/spring
@@ -0,0 +1,17 @@
+#!/usr/bin/env ruby
+
+# This file loads spring without using Bundler, in order to be fast.
+# It gets overwritten when you run the `spring binstub` command.
+
+unless defined?(Spring)
+ require 'rubygems'
+ require 'bundler'
+
+ lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
+ spring = lockfile.specs.detect { |spec| spec.name == "spring" }
+ if spring
+ Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
+ gem 'spring', spring.version
+ require 'spring/binstub'
+ end
+end
diff --git a/config/application.rb b/config/application.rb
index a292e6d8fd..62e6cc0918 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -26,6 +26,7 @@ def with_benchmark(tag, &block)
module Hitobito
class Application < Rails::Application
+
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
@@ -89,7 +90,7 @@ class Application < Rails::Application
config.assets.version = '1.0'
config.assets.precompile += %w(print.css ie.css ie7.css wysiwyg.css wysiwyg.js
- *.png *.gif *.jpg)
+ *.png *.gif *.jpg favicon.ico)
config.generators do |g|
g.test_framework :rspec, fixture: true
@@ -101,7 +102,7 @@ class Application < Rails::Application
# Assert the mail relay job is scheduled on every restart.
if Delayed::Job.table_exists?
MailRelayJob.new.schedule if Settings.email.retriever.config.present?
- SphinxIndexJob.new.schedule
+ SphinxIndexJob.new.schedule if Application.sphinx_present? && Application.sphinx_local?
end
end
@@ -111,6 +112,26 @@ class Application < Rails::Application
ThinkingSphinx::Index.define_partial_indizes!
end
end
+
+ def self.sphinx_version
+ @sphinx_version ||= ThinkingSphinx::Configuration.instance.controller.sphinx_version.presence ||
+ ENV['RAILS_SPHINX_VERSION']
+ end
+
+ def self.sphinx_present?
+ port = ENV['RAILS_SPHINX_PORT']
+ port.present? || ThinkingSphinx::Configuration.instance.controller.running?
+ end
+
+ def self.sphinx_local?
+ host = ENV['RAILS_SPHINX_HOST']
+ host.blank? || host == '127.0.0.1' || host == 'localhost'
+ end
+
+ def self.build_info
+ @build_info ||= File.read("#{Rails.root}/BUILD_INFO").strip rescue ''
+ end
+
end
end
diff --git a/config/initializers/acts_as_taggable_on.rb b/config/initializers/acts_as_taggable_on.rb
index bb3aa99d45..ad183a0089 100644
--- a/config/initializers/acts_as_taggable_on.rb
+++ b/config/initializers/acts_as_taggable_on.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
+# https://github.com/hitobito/hitobito.
ActsAsTaggableOn.remove_unused_tags = true
ActsAsTaggableOn.default_parser = TagCategoryParser
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index fd086a5010..a386be7ef8 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -8,6 +8,7 @@
# Configure the class responsible to send e-mails.
# config.mailer = "Devise::Mailer"
+ config.parent_mailer = 'ApplicationMailer'
# ==> ORM configuration
# Load and configure the ORM. Supports :active_record (default) and
diff --git a/config/initializers/paper_trail.rb b/config/initializers/paper_trail.rb
new file mode 100644
index 0000000000..39a6679176
--- /dev/null
+++ b/config/initializers/paper_trail.rb
@@ -0,0 +1 @@
+PaperTrail.config.track_associations = false
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 45baac6dd7..1854071fc1 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -17,8 +17,8 @@ def initialize_secret
hash = Digest::SHA512.hexdigest(seed)
hash[0, 128]
else
- ENV['RAILS_SECRET_TOKEN'] ||
- '026a97227d5e4cdf52470310b0f2511b259f4743606a45be8cbbd42ee48a004ee0d71de819138ba36b6526c58cd7811f5ca58f2f1e006835f57c551d6192f974'
+ ENV['SECRET_KEY_BASE'] || ENV['RAILS_SECRET_TOKEN'] ||
+ '026a97227d5e4cdf52470310b0f2511b259f4743606a45be8cbbd42ee48a004ee0d71de819138ba36b6526c58cd7811f5ca58f2f1e006835f57c551d6192f974'
end
end
diff --git a/config/initializers/sphinx_20.rb b/config/initializers/sphinx_20.rb
index bdac65b06f..d80f387624 100644
--- a/config/initializers/sphinx_20.rb
+++ b/config/initializers/sphinx_20.rb
@@ -1,6 +1,5 @@
# Config for Sphinx < 2.1
-version = ThinkingSphinx::Configuration.instance.controller.sphinx_version.presence ||
- ENV['RAILS_SPHINX_VERSION']
+version = Rails.application.class.sphinx_version
if version.nil? || version < '2.1'
ThinkingSphinx::SphinxQL.variables!
diff --git a/config/locales/models.de.yml b/config/locales/models.de.yml
index f23f326903..3ee15b267d 100644
--- a/config/locales/models.de.yml
+++ b/config/locales/models.de.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2012-2015, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -47,6 +47,12 @@ de:
attributes:
body:
placeholder_missing: 'muss den Platzhalter %{placeholder} enthalten'
+ event:
+ attributes:
+ base:
+ contact_attr_mandatory: "'%{attribute}' ist ein Pflichtfeld und kann nicht als optional oder 'nicht anzeigen' gesetzt werden"
+ contact_attr_invalid: "'%{attribute}' ist kein gültiges Personen-Attribut"
+ contact_attr_hidden_required: "'%{attribute}' kann nicht als obligatorisch und 'nicht anzeigen' gesetzt werden"
event/date:
attributes:
finish_at:
@@ -102,6 +108,9 @@ de:
name:
must_be_unique: 'existiert bereits'
+ invoice:
+ recipient_address_or_email_required: 'Empfänger Addresse oder E-Mail muss ausgefüllt werden'
+
models:
acts_as_taggable_on/tag:
one: Tag
@@ -139,6 +148,9 @@ de:
event/role/cook:
one: Küche
other: Küche
+ event/role/helper:
+ one: Helfer/-in
+ other: Helfer/-innen
event/role/participant:
one: Teilnehmer/-in
other: Teilnehmer/-innen
@@ -154,21 +166,36 @@ de:
group:
one: Gruppe
other: Gruppen
+ invoice:
+ one: Rechnung
+ other: Rechnungen
+ invoice_article:
+ one: Rechnungsartikel
+ other: Rechnungsartikel
+ invoice_item:
+ one: Rechnungsposten
+ other: Rechnungsposten
label_format:
one: Etikettenformat
other: Etikettenformate
mailing_list:
one: Abo
other: Abos
+ note:
+ one: Notiz
+ other: Notizen
+ payment:
+ one: Zahlung
+ other: Zahlungen
+ payment_reminder:
+ one: Mahnung
+ other: Mahnungen
person:
one: Person
other: Personen
person/add_request:
one: Zugriffsanfrage
other: Anfragen
- person/note:
- one: Notiz
- other: Notizen
people_filter:
one: Filter
other: Filter
@@ -235,6 +262,7 @@ de:
nickname: Übername
email: Haupt-E-Mail
emails: E-Mails
+ layer_group: Hauptebene
password: Passwort
password_confirmation: Passwort Bestätigung
current_password: Altes Passwort
@@ -269,13 +297,11 @@ de:
person/add_request/event:
label: "%{body} in %{group}"
+ deleted_event: Gelöschter Anlass
person/add_request/mailing_list:
label: "%{body} in %{group}"
- person/note:
- text: Text
-
group:
name: Name
short_name: Kurzname
@@ -289,6 +315,7 @@ de:
phone_numbers: Telefonnummern
social_accounts: Social Media
additional_emails: Weitere E-Mails
+ layer_group: Ebene
parent_id: Elterngruppe
layer_group_id: Ebene
type: Gruppentyp
@@ -320,6 +347,13 @@ de:
teamer_count: Anzahl Leitungsteam
participant_count: Anzahl Teilnehmende
applicant_count: Anzahl Anmeldungen
+ applications_cancelable: Abmeldung möglich
+ display_booking_info: Anzeige Anmeldestand
+
+ event/contact_attrs:
+ required: Obligatorisch
+ optional: Optional
+ hidden: Nicht anzeigen
event/answer:
answer: Antwort
@@ -382,7 +416,13 @@ de:
multiple_choices: Mehrfachauswahl
required: Antwort obligatorisch
- questions:
+ admin_questions:
+ question: Frage
+ choices: Mögliche Antworten
+ multiple_choices: Mehrfachauswahl
+ required: Antwort obligatorisch
+
+ application_questions:
question: Frage
choices: Mögliche Antworten
multiple_choices: Mehrfachauswahl
@@ -394,6 +434,9 @@ de:
type: Rolle
participation: Person
+ note:
+ text: Text
+
qualification:
qualification_kind: Qualifikation
qualification_kind_id: Qualifikation
@@ -455,6 +498,7 @@ de:
contact_data: Lesen der Kontaktdaten aller anderen Personen mit Kontaktdatenberechtigung.
qualify: Erstellen von Qualifikationen für Personen auf dieser Ebene und allen darunter liegenden Ebenen.
approve_applications: Bestätigen der Kursanmeldungen für Personen dieser Ebene.
+ finance: Erstellen und Verwalten von Rechnungen.
kind:
member:
one: Mitglied
@@ -537,6 +581,64 @@ de:
padding_top: Rand oben
padding_left: Rand links
dimensions: Anzahl
+ nickname: Übername auf Etikett
+ pp_post: PP-Zeile
+
+ invoice:
+ title: Titel
+ description: Beschreibung
+ invoice_items: Rechnungsposten
+ invoice_item_article: Rechnungsartikel
+ state: Status
+ sequence_number: Nummer
+ esr_number: Referenz Nummer
+ amount_paid: Total bezahlt
+ states:
+ draft: Entwurf
+ sent: Gestellt
+ payed: Bezahlt
+ overdue: Überfällig
+ reminded: Gemahnt
+ cancelled: Storniert
+ recipient: Empfänger
+ recipient_email: Empfänger E-Mail
+ recipient_address: Empfänger Adresse
+ due_at: Fällig am
+ issued_at: Gestellt am
+ sent_at: Verschickt am
+ cost: Betrag
+ total: Total
+ total_inkl_vat: Total inkl. MWSt.
+ total: Total inkl. MWSt.
+ vat: MWSt.
+
+ invoice_article:
+ number: Artikelnummer
+ name: Bezeichnung
+ description: Beschreibung
+ category: Kategorie
+ unit_cost: Preis
+ vat_rate: MWSt.
+ cost_center: Kostenstelle
+ account: Konto
+
+ invoice_item:
+ name: Name
+ description: Beschreibung
+ vat_rate: MWSt.
+ unit_cost: Preis
+ count: Anzahl
+ cost: Betrag
+
+ payment:
+ invoice: Rechnung
+ amount: Betrag
+ received_at: Empfangen am
+
+ payment_reminder:
+ invoice: Rechnung
+ message: Nachricht
+ due_at: Fällig am
errors:
messages:
diff --git a/config/locales/models.en.yml b/config/locales/models.en.yml
index e3e0bc9aab..f02221754c 100644
--- a/config/locales/models.en.yml
+++ b/config/locales/models.en.yml
@@ -37,6 +37,12 @@ en:
attributes:
body:
placeholder_missing: 'must contain the placeholder %{placeholder} '
+ event:
+ attributes:
+ base:
+ contact_attr_mandatory: "'%{attribute}' is a mandatory field and cannot be set to optional or 'do not display'."
+ contact_attr_invalid: "'%{attribute}' is no valid person attribute."
+ contact_attr_hidden_required: "'%{attribute}' cannot be set as mandatory or as 'do-not-display'"
event/date:
attributes:
finish_at:
@@ -46,6 +52,7 @@ en:
choices:
requires_more_than_one_choice: 'needs at least two picks'
event/participation:
+ emoji_suspected: 'Please don''t use any special characters (especially emojis).'
attributes:
person_id:
taken: is already registered
@@ -55,6 +62,7 @@ en:
not_allowed: "'%{mail_name}' is not allowed"
person:
name_missing: 'Please enter a name'
+ emoji_suspected: 'Please don''t use any special characters (especially emojis).'
attributes:
email:
taken: >
@@ -124,6 +132,9 @@ en:
event/role/cook:
one: Kitchen
other: Kitchen
+ event/role/helper:
+ one: Helper
+ other: Helpers
event/role/participant:
one: Participant
other: Participants
@@ -145,15 +156,15 @@ en:
mailing_list:
one: Subscription
other: Subscriptions
+ note:
+ one: Note
+ other: Notes
person:
one: Person
other: Persons
person/add_request:
one: request
other: requests
- person/note:
- one: note
- other: notes
people_filter:
one: Filter
other: Filters
@@ -217,6 +228,7 @@ en:
nickname: Nickname
email: Main e-mail
emails: e-mails
+ layer_group: Main layer
password: Password
password_confirmation: Password confirmation
current_password: Old password
@@ -239,6 +251,7 @@ en:
picture: Upload new picture
remove_picture: Remove current image
roles: Roles
+ tags: Tags
created_at: Created
updated_at: Changed
person/add_request:
@@ -248,10 +261,9 @@ en:
created_at: date
person/add_request/event:
label: "%{body} in %{group}"
+ deleted_event: Deleted event
person/add_request/mailing_list:
label: "%{body} in %{group}"
- person/note:
- text: text
group:
name: Name
short_name: Nickname
@@ -295,6 +307,12 @@ en:
teamer_count: Leaders count
participant_count: Participants count
applicant_count: Registrations count
+ applications_cancelable: Deregistration possible
+ display_booking_info: Display number of registrations
+ event/contact_attrs:
+ required: Mandatory
+ optional: Optional
+ hidden: Do not display
event/answer:
answer: Reply
answers:
@@ -329,7 +347,7 @@ en:
preconditions: Preconditions
prolongations: Extended
qualification_kinds: Qualifies for
- general_information: General information
+ general_information: Standard description
application_conditions: Application conditions
created_at: Created
updated_at: Changed
@@ -346,7 +364,12 @@ en:
choices: Possible answers
multiple_choices: Multiple choice
required: Reply mandatory
- questions:
+ admin_questions:
+ question: Question
+ choices: Possible answers
+ multiple_choices: Multiple choice
+ required: Reply mandatory
+ application_questions:
question: Question
choices: Possible answers
multiple_choices: Multiple choice
@@ -356,6 +379,8 @@ en:
person: Person
type: Role
participation: Person
+ note:
+ text: Text
qualification:
qualification_kind: Qualification
qualification_kind_id: Qualification
@@ -479,6 +504,8 @@ en:
padding_top: Margin top
padding_left: Margin left
dimensions: Number
+ nickname: Nickname on label
+ pp_post: PP-Line
errors:
messages:
invalid_date: "is not a valid date"
diff --git a/config/locales/models.fr.yml b/config/locales/models.fr.yml
index 745bd45ab2..0f817cf4d6 100644
--- a/config/locales/models.fr.yml
+++ b/config/locales/models.fr.yml
@@ -37,6 +37,12 @@ fr:
attributes:
body:
placeholder_missing: 'doit contenir la variable %{placeholder}'
+ event:
+ attributes:
+ base:
+ contact_attr_mandatory: "'%{attribute}' est un champ obligatoire et ne peut pas être défini comme optionnel ou invisible"
+ contact_attr_invalid: "'%{attribute}' n'est pas un attribut personnel valable"
+ contact_attr_hidden_required: "'%{attribute}' ne peut pas être défini comme optionnel ou invisible"
event/date:
attributes:
finish_at:
@@ -46,6 +52,7 @@ fr:
choices:
requires_more_than_one_choice: 'doit au moins contenir deux réponses'
event/participation:
+ emoji_suspected: 'Prière de ne pas utiliser de caractères spéciaux (en particulier, des emoji)'
attributes:
person_id:
taken: est déjà annoncé
@@ -55,6 +62,7 @@ fr:
not_allowed: "'%{mail_name}' ne peut pas être utilisé"
person:
name_missing: 'Veuillez entrer votre nom'
+ emoji_suspected: 'Prière de ne pas utiliser de caractères spéciaux (en particulier, des emoji)'
attributes:
email:
taken: >
@@ -126,6 +134,9 @@ fr:
event/role/cook:
one: Cuisine
other: Cuisine
+ event/role/helper:
+ one: Assistant/e
+ other: Assistant(e)s
event/role/participant:
one: Participant/-e
other: Participant/-es
@@ -147,15 +158,15 @@ fr:
mailing_list:
one: abonnement
other: Abonnements
+ note:
+ one: Note
+ other: Notes
person:
one: Personne
other: Personnes
person/add_request:
one: Question
other: Questions
- person/note:
- one: Note
- other: Notes
people_filter:
one: Filtre
other: Filtres
@@ -219,6 +230,7 @@ fr:
nickname: Surnom
email: Adresse e-mail principale
emails: E-mails
+ layer_group: Niveau
password: Mot de passe
password_confirmation: Confirmation du mot de passe
current_password: Ancien mot de passe
@@ -251,10 +263,9 @@ fr:
created_at: Date
person/add_request/event:
label: "%{body} dans %{group}"
+ deleted_event: Évènement supprimé
person/add_request/mailing_list:
label: "%{body} dans %{group}"
- person/note:
- text: Texte
group:
name: Nom
short_name: Abréviation
@@ -298,6 +309,12 @@ fr:
teamer_count: Nombre d'équipe de direction
participant_count: Nombre de participant-e-s
applicant_count: Nombre d'inscriptions
+ applications_cancelable: Désinscription possible
+ display_booking_info: Voir l'état des inscriptions
+ event/contact_attrs:
+ required: Obligatoire
+ optional: Optionnel
+ hidden: Ne pas afficher
event/answer:
answer: Réponse
answers:
@@ -349,7 +366,12 @@ fr:
choices: Réponses possibles
multiple_choices: Sélection multiple
required: réponse obligatoire
- questions:
+ admin_questions:
+ question: Question
+ choices: Réponses possibles
+ multiple_choices: Sélection multiple
+ required: réponse obligatoire
+ application_questions:
question: Question
choices: Réponses possibles
multiple_choices: Sélection multiple
@@ -359,6 +381,8 @@ fr:
person: Personne
type: Rôle
participation: Personne
+ note:
+ text: Texte
qualification:
qualification_kind: Qualification
qualification_kind_id: Qualification
@@ -482,6 +506,8 @@ fr:
padding_top: Marge (en haut)
padding_left: Marge (à gauche)
dimensions: Nombre
+ nickname: Surnom sur l'étiquette
+ pp_post: Espace PP
errors:
messages:
invalid_date: "n'est pas une date valable"
diff --git a/config/locales/models.it.yml b/config/locales/models.it.yml
index bd3e9edaf5..e94eb9ff0a 100644
--- a/config/locales/models.it.yml
+++ b/config/locales/models.it.yml
@@ -37,6 +37,12 @@ it:
attributes:
body:
placeholder_missing: 'deve contenere il carattere %{placeholder}'
+ event:
+ attributes:
+ base:
+ contact_attr_mandatory: "'%{attribute}' è un campo obbligatorio e non può essere impostato come facoltativo o 'non visualizzare'"
+ contact_attr_invalid: "'%{attribute}' non è un attributo valido in merito alla persona"
+ contact_attr_hidden_required: "'%{attribute}' non può essere impostato come obbligatorio e 'non visualizzare'"
event/date:
attributes:
finish_at:
@@ -46,6 +52,7 @@ it:
choices:
requires_more_than_one_choice: 'deve contenere almeno due risposte'
event/participation:
+ emoji_suspected: 'Non utilizzare simboli speciali (in particolare Emoji)'
attributes:
person_id:
taken: è già iscritto
@@ -55,6 +62,7 @@ it:
not_allowed: "'%{mail_name}' non può essere utilizzato"
person:
name_missing: 'Inserire un nome'
+ emoji_suspected: 'Non utilizzare simboli speciali (in particolare Emoji)'
attributes:
email:
taken: >
@@ -89,6 +97,9 @@ it:
name:
must_be_unique: 'già existe'
models:
+ acts_as_taggable_on/tag:
+ one: Tag
+ other: Tags
additional_email:
one: altro email
other: Altre email
@@ -122,6 +133,9 @@ it:
event/role/cook:
one: Cucina
other: Cucina
+ event/role/helper:
+ one: Aiutante
+ other: Aiutanti
event/role/participant:
one: Partecipante
other: Partecipante
@@ -143,15 +157,15 @@ it:
mailing_list:
one: abbonamento
other: Abbonamenti
+ note:
+ one: Nota
+ other: Note
person:
one: persona
other: Persone
person/add_request:
one: Richiesta di accesso
other: Richieste
- person/note:
- one: Nota
- other: Note
people_filter:
one: filtro
other: Filtri
@@ -209,12 +223,13 @@ it:
person:
first_name: Nome
last_name: Cognome
- name: Cognome
+ name: Nome
company_name: Nome della ditta
company: Ditta
nickname: Soprannome
email: Email principale
emails: Email
+ layer_group: Livello principale
password: Password
password_confirmation: Confermare la password
current_password: Vecchia password
@@ -237,6 +252,7 @@ it:
picture: Carica una nuova foto
remove_picture: Elimina la foto attuale
roles: Ruoli
+ tags: Tags
created_at: Salvato
updated_at: Modificato
person/add_request:
@@ -246,12 +262,11 @@ it:
created_at: Data
person/add_request/event:
label: "%{body} in %{group}"
+ deleted_event: Evento cancellato
person/add_request/mailing_list:
label: "%{body} in %{group}"
- person/note:
- text: Testo
group:
- name: Cognome
+ name: Nome
short_name: Abbreviazione
email: Email principale
address: Indirizzo
@@ -269,7 +284,7 @@ it:
type_name: Tipo di gruppi
event:
group_ids: Gruppi
- name: Cognome
+ name: Nome
number: Numero
motto: Motto
cost: Costi
@@ -293,6 +308,12 @@ it:
teamer_count: Numero di organizzatori
participant_count: Numero di partecipanti
applicant_count: Numero di iscrizioni
+ applications_cancelable: Disiscrizione possibile
+ display_booking_info: Visualizza stato iscrizioni
+ event/contact_attrs:
+ required: Obbligatorio
+ optional: Facoltativo
+ hidden: Non visualizzare
event/answer:
answer: Risposta
answers:
@@ -344,7 +365,12 @@ it:
choices: Possibili risposte
multiple_choices: Scelta multipla
required: Risposta obbligatoria
- questions:
+ admin_questions:
+ question: Domanda
+ choices: Possibili risposte
+ multiple_choices: Scelta multipla
+ required: Risposta obbligatoria
+ application_questions:
question: Domanda
choices: Possibili risposte
multiple_choices: Scelta multipla
@@ -354,6 +380,8 @@ it:
person: Persona
type: Ruolo
participation: Persona
+ note:
+ text: Testo
qualification:
qualification_kind: Qualifiche
qualification_kind_id: Qualifica
@@ -460,7 +488,7 @@ it:
subscription:
related_role_types: Ruoli
people_filter:
- name: Cognome
+ name: Nome
custom_content:
label: Testo
subject: Oggetto
@@ -477,6 +505,8 @@ it:
padding_top: Margine superiore
padding_left: Margine a sinistra
dimensions: Numero
+ nickname: Soprannome sull'etichetta
+ pp_post: Riga PP
errors:
messages:
invalid_date: "non è una data valida"
diff --git a/config/locales/views.de.yml b/config/locales/views.de.yml
old mode 100644
new mode 100755
index 3629bc646c..2663dbbf7f
--- a/config/locales/views.de.yml
+++ b/config/locales/views.de.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2012-2015, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -12,6 +12,7 @@ de:
"no": "nein"
unknown: unbekannt
nobody: niemand
+ all: Alle
from: Von
until: Bis
@@ -64,6 +65,7 @@ de:
placeholder_person: 'Person suchen...'
placeholder_group: 'Gruppe suchen...'
placeholder_event: 'Anlass suchen...'
+ placeholder_company_name: 'Firmennamen suchen...'
list:
index:
@@ -113,6 +115,11 @@ de:
dropdown/event/group_filter:
all_groups: 'Alle Gruppen'
+ dropdown/event/events_export:
+ button: Export
+ csv: CSV
+ xlsx: Excel
+
dropdown/event/role_add:
add: 'Person hinzufügen'
@@ -126,6 +133,8 @@ de:
dropdown/people_export:
button: 'Export'
csv: 'CSV'
+ xlsx: 'Excel'
+ vcard: 'vCard'
labels: 'Etiketten'
emails: 'E-Mail Adressen'
addresses: 'Adressliste'
@@ -133,6 +142,21 @@ de:
condense_labels: 'Mehrfachsendungen vermeiden'
condense_labels_hint: 'Nur einen Eintrag ausgeben, wenn die gesamte Adresse und der Nachname übereinstimmen.'
+ dropdown/invoices:
+ download: 'Export'
+ print: 'Drucken'
+ labels: 'Etiketten'
+ full: 'Rechnung inkl. Einzahlungsschein'
+ esr_only: 'Einzahlungsschein separat'
+ articles_only: 'Rechnung separat'
+ csv: 'CSV'
+ pdf: 'PDF'
+
+ dropdown/invoice_sending:
+ button: 'Rechnung stellen'
+ state: 'Status setzen'
+ mail: 'Status setzen und per E-Mail verschicken'
+
devise:
failure:
already_authenticated: 'Du bist bereits angemeldet.'
@@ -212,7 +236,7 @@ de:
explanation: 'Wir arbeiten gerade an dieser Seite, deshalb ist sie nicht verfügbar. Das sollte nicht all zu lange dauern.'
instruction: 'Bitte versuchen Sie es später nochmals.'
- export/csv/events/list:
+ export/tabular/events/list:
group_names: 'Organisatoren'
duration: Zeitraum
date: "Datum %{index}"
@@ -222,20 +246,21 @@ de:
applied_to: Anmeldung an
alternative_dates: Ausweichdaten
priorities: 'Anmeldeprioritäten'
- no_qualifications_could_be_prolonged: "%{person} hat keine Qualifikationen, die verlängert werden können."
lists:
courses:
title: 'Verfügbare Kurse'
explanation: 'Hier werden Kurse deiner Gruppe, sowie deren Übergruppen angezeigt. Andere Kurse findest du bei der organisierenden Gruppe.'
csv_export_button: 'CSV Export'
+ ical_export_button: 'Kalender Export'
events:
title: 'Demnächst stattfindende Anlässe'
explanation: 'Hier werden Anlässe von Gruppen, bei denen du Mitglied bist, sowie deren Übergruppen angezeigt. Andere Anlässe findest du bei der organisierenden Gruppe.'
-
+ apply_until: 'bis %{date}'
application_market:
- already_assigned: 'Diese Person ist bereits anderweitig zugeteilt.'
+ already_assigned: 'Diese Person ist bereits anderweitig zugeteilt oder bei diesem Anlass angemeldet.'
participation:
registered_at: 'Anmeldung am %{date}'
+ town: Wohnort
prio_buttons:
national_waitinglist: 'nationale Warteliste'
index:
@@ -261,11 +286,25 @@ de:
qualifications:
for_participants: 'Qualifikationen für Teilnehmende'
for_leaders: 'Qualifikationen für Leitende'
-
+ precondition_fields:
+ add_precondition_grouping: '+ Vorbedingungen hinzufügen'
+ add_precondition: 'Hinzufügen'
+ qualifications:
+ or: oder
+ and: und
+
+ participation_contact_datas:
+ edit:
+ title: Kontaktangaben
+ save: Weiter
participations:
+ edit:
+ title: Anmeldung
approvals:
title: Freigabe
specific_information: 'Spezifische Angaben'
+ application_answers: Anmeldeangaben
+ admin_answers: Administrationsangaben
no_answer_given: '(nicht beantwortet)'
actions_show:
change_contact_data_button: 'Kontaktdaten ändern'
@@ -294,6 +333,17 @@ de:
on_waiting_list: Auf Warteliste
list:
incomplete: Pflichtangaben fehlen
+ cancel_application:
+ explanation: Du bist für diesen Anlass angemeldet.
+ caption: Abmelden
+ confirmation: "Bist Du sicher, dass Du dich von diesem Anlass abmelden möchtest?"
+
+ qualifications:
+ index:
+ save: Qualifikationen aktualisieren
+ update:
+ flash:
+ success: Die Qualifikationen wurden erfolgreich aktualisiert.
register:
register:
@@ -313,21 +363,32 @@ de:
signature_confirmation_text_default: Erziehungsberechtigte Person (bei Minderjährigen)
attachments:
add: "hinzufügen"
+ default_description_link:
+ insert_general_information: Standardbeschreibung einsetzen
form:
caption_external_applications: 'Externe können sich für diesen Anlass anmelden'
caption_prioritization: 'Teilnehmende können bei der Anmeldung zwei weitere Kurse als Alternativen angeben'
caption_requires_approval: 'Der/Die Gruppenleiter/-in wird informiert und muss die Anmeldung bestätigen'
- additional_information: 'Angaben für Anmeldung'
times_are_optional: Uhrzeiten sind optional
explain_application_questions: Hier kannst du weitere Angaben für die Anmeldung verlangen.
Gib mögliche Antworten mit Komma getrennt ein oder lass das Feld leer,
um beliebige Antworten zu ermöglichen.
+ explain_admin_questions: Hier kannst du weitere Angaben pro Teilnehmer/-in definieren,
+ welche nur für die Kursadministration verwendet werden.
+ explain_contact_attrs: Hier kannst du wählen, welche Kontaktangaben bei der Anmeldung
+ abgefragt werden sollen.
+ caption_applications_cancelable: Teilnehmende können sich selbst abmelden
+ caption_display_booking_info: Die Anzahl Anmeldungen/Plätze ist auf der Kursliste für alle sichtbar
form_tabs:
general: Allgemein
+ application_questions: Anmeldeangaben
+ admin_questions: Administrationsangaben
+ contact_attrs: Kontaktangaben
global:
link:
add_event: Anlass erstellen
add_event/course: Kurs erstellen
+ duplicate: Duplizieren
minimum_age_with_years: '%{minimum_age} Jahre (Jahrgang)'
new:
title_event: Anlass erstellen
@@ -335,6 +396,7 @@ de:
preconditions: 'Erforderliche Qualifikationen'
tabs:
participants: 'Teilnehmende'
+ export_enqueued: 'Export wird im Hintergrund gestartet und nach Fertigstellung an %{email} versendet.'
event/applications:
approved: 'Die Anmeldung wurde freigegeben.'
@@ -356,7 +418,8 @@ de:
event/participations:
full_entry_label: '%{model_label} von %{person} in %{event} '
success: '%{full_entry_label} wurde erfolgreich erstellt. Bitte überprüfe die Kontaktdaten und passe diese gegebenenfalls an.'
- instructions: 'Für die definitive Anmeldung musst du diese Seite über Drucken ausdrucken, unterzeichnen und per Post an die entsprechende Adresse schicken.'
+ instructions: 'Für die definitive Anmeldung musst du die Teilnahmebestätigung über Drucken ausdrucken, unterzeichnen und per Post an die entsprechende Adresse schicken.'
+ export_enqueued: 'Export wird im Hintergrund gestartet und nach Fertigstellung an %{email} versendet.'
show:
link:
@@ -369,8 +432,9 @@ de:
event/precondition_checker:
preconditions_not_fulfilled: 'Vorbedingungen für Anmeldung sind nicht erfüllt. '
- below_minimum_age: "Altersgrenze von %{course_minimum_age} unterschritten."
+ below_minimum_age: "Altersgrenze von %{course_minimum_age} Jahren ist unterschritten."
qualifications_missing: "Folgende Qualifikationen fehlen: %{missing}"
+ some_qualifications_missing: "Erforderliche Qualifikationen fehlen."
event/register:
not_logged_in: "Du musst dich einloggen um dich für den Anlass '%{event}' anzumelden."
@@ -397,11 +461,11 @@ de:
active_participants_info:
one: '%{count} Anmeldung zugeteilt'
other: '%{count} Anmeldungen zugeteilt'
- issue_only: 'Vergibt die %{model} %{issued} auf den %{until} (letztes Kursdatum).'
+ issue_only: 'Vergibt die %{model} %{issued} unmittelbar per %{until} (letztes Kursdatum).'
prolong_only:
- one: 'Verlängert existierende Qualifikation %{prolonged} auf den %{until} (letztes Kursdatum).'
- other: 'Verlängert existierende Qualifikationen %{prolonged} auf den %{until} (letztes Kursdatum).'
- issue_and_prolong: 'Vergibt die %{model} %{issued} und verlängert existierende Qualifikationen %{prolonged} auf den %{until} (letztes Kursdatum).'
+ one: 'Verlängert existierende Qualifikation %{prolonged} unmittelbar per %{until} (letztes Kursdatum).'
+ other: 'Verlängert existierende Qualifikationen %{prolonged} unmittelbar per %{until} (letztes Kursdatum).'
+ issue_and_prolong: 'Vergibt die %{model} %{issued} und verlängert existierende Qualifikationen %{prolonged} unmittelbar per %{until} (letztes Kursdatum).'
filter_navigation/dropdown:
@@ -411,8 +475,7 @@ de:
custom_filter: 'Eigener Filter'
entire_layer: 'Gesamte Ebene'
entire_group: 'Gesamte Gruppe'
- new_role_filter: 'Neuer Rollen Filter...'
- new_qualification_filter: 'Neuer Qualifikationen Filter...'
+ new_filter: 'Neuer Filter...'
filter_navigation/event/participations:
predefined_filters:
@@ -422,7 +485,7 @@ de:
full_text:
index:
- title: 'Gefundene Personen'
+ title: 'Ergebnisse'
incomplete_search_request: 'Bitte geben Sie mindestens zwei Zeichen ein.'
group:
@@ -471,11 +534,14 @@ de:
attrs:
contact_details: 'Kontaktangaben'
additional_information: 'Weitere Angaben'
+ deleted_subgroups:
+ no_deleted_sub_groups: 'Keine gelöschten Gruppen vorhanden.'
form:
help_contact: 'Adresse und öffentliche Telefonnummern dieser Person verwenden.'
global:
link:
add: Gruppe erstellen
+ deleted_person: Ohne Rollen
new:
title: '%{model} erstellen'
reactivated: "Gruppe %{group} wurde erfolgreich reaktiviert."
@@ -515,7 +581,61 @@ de:
import/person_doublette_finder:
duplicates: "%{count} Treffer in Duplikatserkennung."
+ invoices:
+ filter:
+ due_since: Fällig seit
+ due_since_list:
+ one_day: Gestern
+ one_week: Einer Woche
+ one_month: Einem Monat
+ add: Externe Rechnung erstellen
+ issued: Rechnung gestellt
+ sent: Rechnung versendet
+ reminder_sent: Mahnung versendet
+ payd: bezahlt
+ destroy:
+ flash:
+ success: Rechnung wurde storniert.
+ pdf:
+ total: Gesamtbetrag
+ total_vat: MWSt. gesamt
+ invoice_number: Rechnungsnummer
+ invoice_date: Rechnungsdataum
+ due_at: Fällig bis
+
+ invoice_lists:
+ form:
+ recipient_info:
+ one: "Rechnung wird für eine Person erstellt."
+ other: "Rechnung wird für %{count} Personen erstellt."
+ create:
+ one: "Rechnung %{title} wurde erstellt."
+ other: "Rechnung %{title} wurde für %{count} Empfänger erstellt."
+ update:
+ zero: 'Es muss mindestens eine Rechnung im Status "Entwurf" ausgewählt werden.'
+ one: "Rechnung wurde gestellt."
+ other: "%{count} Rechnungen wurden gestellt."
+ background_send:
+ zero: "Es werden keine Rechnungen per E-Mail verschickt."
+ one: "Rechnung wird im Hintergrund per E-Mail verschickt."
+ other: "%{count} Rechnungen werden im Hintergrund per E-Mail verschickt."
+ error:
+ no_mail: "Rechnung %{number} an %{name} kann nicht verschickt werden, da keine E-Mail hinterlegt ist."
+ not_draft: "Rechnung %{number} wurde bereits verschickt."
+ destroy:
+ zero: "Zuerst muss eine Rechnung ausgewählt werden."
+ one: "Rechnung wurde storniert."
+ other: "%{count} Rechnungen wurden storniert."
+
+ payments:
+ create:
+ flash:
+ success: Zahlung über %{amount} wurde erfasst.
+
label_formats:
+ global_labels: "Globale Etikettenformate"
+ own_labels: Meine Etikettenformate
+ see_global_labels: "Globale Etikettenformate anzeigen"
form:
portrait: 'Hochformat'
landscape: 'Querformat'
@@ -561,7 +681,16 @@ de:
groups: 'Gruppen'
events: 'Anlässe'
courses: 'Kurse'
- admin: 'Admin'
+ admin: 'Einstellungen'
+ invoices: 'Rechnungen'
+
+ notes:
+ new_note: Neue Notiz
+ note:
+ created: vor %{time_ago}
+ paginator:
+ newer_notes: Neuere Beiträge
+ older_notes: Altere Beiträge
qualifications:
in_years: '%{years} Jahre'
@@ -583,6 +712,8 @@ de:
person:
+ confirm_delete: "Sämtliche Daten zu %{person} werden endgültig gelöscht, inkl. Teilnahmen und Qualifikationen. Lösche diese Person nur, wenn niemand mehr diese Daten benötigt."
+
add_requests:
body_list:
@@ -685,13 +816,6 @@ de:
ask_responsibles:
request_link: Anfrage beantworten
- notes:
- note:
- created: vor %{time_ago}
- paginator:
- newer_notes: Neuere Beiträge
- older_notes: Altere Beiträge
-
tags:
list:
category_other: Andere
@@ -738,6 +862,8 @@ de:
tabs:
history: Verlauf
log: Log
+ colleagues: Mitarbeiter/-innen
+ invoices: Rechnungen
actions_show:
send_login: Login schicken
@@ -792,9 +918,8 @@ de:
tags:
add_tag: Tag hinzufügen…
no_entry: (keine)
-
- notes:
- new_note: Neue Notiz
+ export_enqueued: 'Export wird im Hintergrund gestartet und nach Fertigstellung an %{email} versendet.'
+ export_email_needed: 'Für den export wird eine Email Adresse benötigt.'
participations:
destroy:
@@ -806,18 +931,51 @@ de:
save_search: Suche speichern
save_filter: Filter speichern
save_filter_placeholder: Geben Sie einen Namen an, um diesen Filter zu speichern
- prompt_role_selection: Welche Rollen sollen angezeigt werden?
+ range:
+ deep: In der aktuellen Ebene und allen darunter liegenden Ebenen und Gruppen
+ layer: In der aktuellen Ebene und allen ihren Gruppen
+ group: Nur in der aktuellen Gruppe
+ group_deep: In der aktuellen Gruppe und allen darunter liegenden Gruppen
+ filters_qualification_validity:
+ active: Nur aktuell gültige Qualifikationen
+ reactivateable: Gültige und reaktivierbare Qualifikationen
+ all: Alle jemals erteilten Qualifikationen
+ filters_role_kind:
+ active: war die Role aktiv.
+ created: wurde die Role erstellt.
+ deleted: wurde die Role gelöscht.
new:
- title: Personen nach Rollen filtern
+ title: Personen filtern
+
+ range:
+ prompt_range: In welchem Bereich soll gesucht werden?
+
+ role:
+ title: Rollen
+ prompt_role_selection: Welche Rollen sollen angezeigt werden?
+ prompt_role_duration: Im Zeitraum (optional)
+ prompt_role_duration_selection: Welche Zeitraum sollen berücksichtigt werden?
+ prompt_role_duration_until: bis
qualification:
- title: Personen nach Qualifikationen filtern
+ title: Qualifikationen
prompt_qualification_selection: Welche Qualifikationen sollen angezeigt werden?
prompt_validity: Welche Gültigkeit sollen die angezeigten Qualifikationen haben?
+ prompt_year: Qualifikationsjahr einschränken
prompt_kind: In welchem Bereich soll gesucht werden?
+ start_at_year_label: Erhalten zwischen
+ start_at_year_infix: und
+ start_at_year_suffix: ''
+ finish_at_year_label: Abgelaufen zwischen
+ finish_at_year_infix: und
+ finish_at_year_suffix: ''
+ not_enough_permissions: Du verfügst nicht über ausreichende Berechtigungen, um Personen mit diesem Filter zu suchen.
simple_radio:
+ match:
+ one: Person hat mindestens EINE dieser Qualifikationen
+ all: Person hat ALLE diese Qualifikationen
validity:
active: Nur aktuell gültige Qualifikationen
reactivateable: Gültige und reaktivierbare Qualifikationen
@@ -838,6 +996,7 @@ de:
add_person: Person hinzufügen
add_role_for_person: Rolle für %{person} erstellen
edit_role_for_person: Rolle für %{person} bearbeiten
+ new_role_for_person: Neue Rolle erstellen
fields:
help_optional_label: Optionale Bezeichnung der Rolle dieser Person
@@ -900,6 +1059,7 @@ de:
export_not_allowed: 'Du darfst die Abonnenten dieses Abos nicht exportieren, da diese auch Rollen und/oder Anlässe ausserhalb deines Berechtigungsbereiches enthalten.'
subscription:
only_persons_with: 'Nur Personen mit:'
+ export_enqueued: 'Export wird im Hintergrund gestartet und nach Fertigstellung an %{email} versendet.'
version:
attribute_change:
diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml
index 0bbabcabcf..1965453fd3 100644
--- a/config/locales/views.en.yml
+++ b/config/locales/views.en.yml
@@ -83,6 +83,10 @@ en:
available_placeholders_empty: 'No placeholders available'
dropdown/event/group_filter:
all_groups: 'All groups'
+ dropdown/event/events_export:
+ button: Export
+ csv: CSV
+ xlsx: Excel
dropdown/event/role_add:
add: 'Add person'
dropdown/event/participant_add:
@@ -93,10 +97,14 @@ en:
dropdown/people_export:
button: 'Export'
csv: 'CSV'
+ xlsx: 'Excel'
+ vcard: 'vCard'
labels: 'Labels'
emails: 'E-Mail addresses'
addresses: 'Address list'
everything: 'All information'
+ condense_labels: 'Avoid multiple entries for household'
+ condense_labels_hint: 'Only give one entry for multiple people with the same adress and family name. '
devise:
failure:
already_authenticated: 'You are already registered.'
@@ -122,7 +130,7 @@ en:
updated: 'Your password has been changed. You''re now logged in.'
updated_not_active: 'Your password has been changed.'
send_paranoid_instructions: "If your e-mail address exist in our database, you will receive in a few minutes an e-mail with instructions on how to reset your password."
- no_token: 'You can only access this page by clicking the link provided in a password reset email. If you have copied the link from a password reset email, please make sure you copied the entire URL provided.'
+ no_token: 'You can only access this page by clicking the link provided in a password reset e-mail. If you have copied the link from a password reset email, please make sure you copied the entire URL provided.'
new:
reset_password_button: 'Reset password'
confirmations:
@@ -164,7 +172,7 @@ en:
title: 'Sorry, page is currently not available (503)'
explanation: 'We are working on this page, so it is not available. This should not take too long.'
instruction: 'Please try again later.'
- export/csv/events/list:
+ export/tabular/events/list:
group_names: 'Organizers'
duration: Duration
date: "Date %{index}"
@@ -173,7 +181,6 @@ en:
applied_to: Registration to
alternative_dates: Alternative dates
priorities: 'Application priorities'
- no_qualifications_could_be_prolonged: "%{person} has no qualifications that can be extended."
lists:
courses:
title: 'Available courses'
@@ -183,7 +190,7 @@ en:
title: 'Next events'
explanation: 'Events of your groups and their top groups are shown here. Other events you''ll find in the organizing group.'
application_market:
- already_assigned: 'This person is already allocated elsewhere.'
+ already_assigned: 'This person was already assigned or registered for a different event.'
participation:
registered_at: 'Registration on %{date}'
prio_buttons:
@@ -197,6 +204,11 @@ en:
label: 'Waiting list'
popover_waiting_list:
waiting_list_info: Put this person on the waiting list for courses of of %{event_kind}.
+ attachments:
+ create:
+ failure: "Upload failed: %{errors}."
+ destroy:
+ failure: "Failed to delete: %{errors}."
kinds:
form:
help_minimum_age: 'Years (vintage on the first course date is decisive)'
@@ -226,6 +238,7 @@ en:
signature: Signature
signature_confirmation: First name, last name, signature
heading_event/course: Course application %{year}
+ heading_event: Registration for event
read_and_agreed_for_event: I meet the requirements for the event, have read the event description and agree to them.
read_and_agreed_for_event_course: I meet the course prerequisites, have read the course description and course regulations and agree to them.
requirements_for_event: Requirements for the event
@@ -235,6 +248,10 @@ en:
on_waiting_list: On waiting list
list:
incomplete: Required data is missing
+ cancel_application:
+ explanation: You are registered for this event.
+ caption: Cancel registration
+ confirmation: "Are you sure, that you want to cancel your registration?"
register:
register:
title: Collect contact data
@@ -250,13 +267,17 @@ en:
caption_signature: Participants must sign the application
caption_signature_confirmation: Application must be confirmed with a second signature
signature_confirmation_text_default: Legal guardian (for minors)
+ attachments:
+ add: "add"
+ default_description_link:
+ insert_general_information: Insert standard description
form:
caption_external_applications: 'Externals can sign up for this event'
caption_prioritization: 'Participants may set two additional courses as alternatives'
caption_requires_approval: 'The group leader will be informed and has to confirm your registration'
- additional_information: 'Information for registration'
times_are_optional: Times are optional
explain_application_questions: Here you can request further information for the application. Give possible answers separated by a comma or leave the field blank to allow any answers.
+ caption_applications_cancelable: Participants can cancel their registration
form_tabs:
general: General
global:
@@ -287,7 +308,6 @@ en:
event/participations:
full_entry_label: '%{model_label} from %{person} in %{event} '
success: '%{full_entry_label} has successfully been created. Please check the contact data, and update them if necessary.'
- instructions: 'For the final registration you have to print the page via Print , sign and mail it to the appropriate address.'
show:
link:
delete: 'Delete application'
@@ -297,7 +317,6 @@ en:
remove: 'Markes course as not passed and removes qualifications.'
event/precondition_checker:
preconditions_not_fulfilled: 'Prerequisites for registration are not met. b>'
- below_minimum_age: "Age limit falls below %{course_minimum_age}"
qualifications_missing: "The following qualifications are missing: %{missing}"
event/register:
not_logged_in: "You must log in for the registration of the event '%{event}'. "
@@ -313,11 +332,15 @@ en:
apply: 'Register'
applied: 'Registered'
not_possible: 'not possible'
- issue_only: 'Assigns the %{model} %{issued} on the %{until} (last course date).'
- prolong_only:
- one: 'Extends existing qualification %{prolonged} until %{until} (last course date).'
- other: 'Extends existing qualifications %{prolonged} until %{until} (last course date).'
- issue_and_prolong: 'Assigns the %{model} %{issued} and extends existing qualifications %{prolonged} until %{until} (last course date).'
+ participants_info:
+ one: '%{count} registered'
+ other: '%{count} registered'
+ participants_info_with_limit:
+ one: '%{count} registered for %{limit} plesac'
+ other: '%{count} registered for %{limit} places'
+ active_participants_info:
+ one: '%{count} assigned registration'
+ other: '%{count} assigned registrations'
filter_navigation/dropdown:
additional_views: 'Further views'
filter_navigation/people:
@@ -328,13 +351,43 @@ en:
new_qualification_filter: 'New qualification filter...'
filter_navigation/event/participations:
predefined_filters:
- all: All Persons
+ all: All people
teamers: Leaders
participants: Participants
full_text:
index:
title: 'people found'
incomplete_search_request: 'Please enter at least two characters.'
+ group:
+ merge:
+ select:
+ title: 'Merge %{group} '
+ explanation: When merging two groups all the subgroups, events and people including their roles will be merged. Subscriptions will not be merged. All former groups will still be visible under 'Deleted'.
+ merge_is_irreversible: This process cannot be undone!
+ new_group_name: 'Name of the merged group'
+ merge_group_with: 'Merge %{group} with'
+ merge_button: 'Merge group'
+ move:
+ select:
+ title: "Move group"
+ select_group: "Please pick the target group that will contain the current group."
+ person_add_requests:
+ actions_index:
+ activate: Activate manual approval
+ deactivate: Deactivate
+ activate_title: Activate manual approval
+ deactivate_title: Deactivate manual approval
+ index:
+ activated: Manual approval is active
+ deactivated: Manual approval is not active
+ explanation: >
+ When active people from this layer will only be added to other groups,
+ events or subscriptions after an authorized person has given his/her approval.
+ list:
+ approve: Accept
+ reject: Deny
+ approvers:
+ title: 'Notifications to'
groups:
actions_show:
export_subgroups: 'CSV subgroups'
@@ -342,6 +395,8 @@ en:
attrs:
contact_details: 'Contact data'
additional_information: 'More information'
+ deleted_subgroups:
+ no_deleted_sub_groups: 'No deleted groups found.'
form:
help_contact: 'Use public address and telephone number of this person.'
global:
@@ -353,6 +408,11 @@ en:
subgroups: 'Subgroups'
tabs:
deleted: 'Deleted'
+ group/person_add_requests:
+ deactivated: Manual approval deactivated
+ activated: Manual approval activated
+ global:
+ no_list_entries: No pending approvals.
group/merge:
success: 'The selected groups were merged to form the new group %{new_group_name}.'
failure: 'The selected groups can''t be merged.'
@@ -375,6 +435,9 @@ en:
import/person_doublette_finder:
duplicates: "%{count} results in duplicate detection."
label_formats:
+ global_labels: "Global label formats"
+ own_labels: My label formats
+ see_global_labels: "Show global labels"
form:
portrait: 'Portrait'
landscape: 'Landscape'
@@ -414,7 +477,7 @@ en:
groups: 'Groups'
events: 'Events'
courses: 'Courses'
- admin: 'Admin'
+ admin: 'Settings'
qualifications:
in_years: '%{years} years'
valid_until: 'to %{date}'
@@ -430,6 +493,126 @@ en:
title: Create qualification kind
paper_trail/version_decorator:
by: 'from %{author}'
+ person:
+ confirm_delete: "All Data for %{person} will be completely deleted, including any participations and qualifications. Delete this person only, if no one needs this data anymore."
+ add_requests:
+ body_list:
+ open_requests:
+ one: 1 pending approval.
+ other: '%{count} pending approvals.'
+ request_to: Request to
+ cancel: Cancel request for access
+ creator:
+ group:
+ success: "We sent an approval request for %{person}. The role will be created as soon as the request was granted. Any additional details for the role have to entered again after the approval."
+ failure: "The role could not be created, because for %{person} you need be granted access by approval. %{errors}."
+ event:
+ success: "We sent an approval request for %{person}. The registration will be created as soon as the request was granted. Any additional details for the registration have to entered again after the approval."
+ failure: "The registration could not be created, because for %{person} you need be granted access by approval. %{errors}."
+ mailing_list:
+ success: "We sent an approval request for %{person}. The subscription will be created as soon as the request was granted."
+ failure: "%{person} could not be added, because you need be granted access using an approval request. %{errors}."
+ approve:
+ success_notice: "The request for access for %{person} was granted."
+ failure_notice: "The request for approval für %{person} could not be granted: %{errors}."
+ reject:
+ success_notice: "The request for access for %{person} has been denied."
+ cancel:
+ success_notice: "The request for access for %{person} has been retracted."
+ status:
+ group:
+ approved: "The request for access for %{person} has already been granted."
+ rejected: "The request for access for %{person} has already been denied."
+ event:
+ approved: "The request for access for %{person} has already been granted."
+ rejected: "The request for access for %{person} has already been denied."
+ mailing_list:
+ approved: "The request for access for %{person} has already been granted."
+ rejected: "The request for access for %{person} has already been denied."
+ csv_imports:
+ description:
+ header: CSV files
+ explain_csv_format: 'A CSV file contains the details of a person on each line. The first line contains the field names. The fields are separated with a comma. If a field value itself contains commas, it must be complemented with quotation marks ("). Example:'
+ example: |
+ First name,Last name,e-Mail,Place,gender
+ Hans,Muster,hans@beispiel.ch,Bern,m
+ Vreni,Bischofsberger,vreni@beispiel.net,"Zürich, New York",f
+ explain_field_names: The fields in the CSV file can be assigned to the fields in the application in the next step.
+ explain_role: All people in a file will be assigned to the same role, which can be selected in the next step.
+ special_fields: Special field values
+ explain_special_fields_html: The following fields must contain one of the italic em> values.
+ preview_table:
+ icon_tooltip_invalid: 'Invalid values'
+ icon_tooltip_updated: 'Updating'
+ icon_tooltip_created: 'Recording'
+ icon_tooltip_request: 'is being requested'
+ define_mapping:
+ choose_role: 'Select role'
+ choose_role_help: 'This role will be assigned to all imported people'
+ assign_columns_to_fields: 'Assign columns to fields'
+ column_from_csv: 'Column from CSV'
+ field_in_database: 'Field in database'
+ preview: 'Preview'
+ update_behaviour: Update behavior
+ keep_behaviour: >
+ Only update empty values in the database with the values from the CSV
+ file, existing values are preserved.
+ keep_behaviour_tags: Tags will be added.
+ override_behaviour: >
+ Overwrite existing values in the database with the values from the CSV
+ file. Empty values in the CSV file will delete existing values in the
+ database.
+ override_behaviour_tags: Tags will be removed or replaced.
+ update_behaviour_explanation: >
+ This behavior only applies to persons already existing in the database.
+ New persons are created with the data from the CSV file.
+ new:
+ upload_button: 'Upload'
+ csv_file: 'CSV file'
+ preview:
+ import_now_button: Import people now
+ import_details_html: >
+ The following persons will be importet with the role %{role}
+ into the group %{group} .
+ add_request_mailer:
+ ask_person_to_add:
+ request_link: Answer request
+ ask_responsibles:
+ request_link: Answer request
+ tags:
+ list:
+ category_other: Other
+ person/csv_imports:
+ invalid_file: Please select a valid CSV file.
+ duplicate_keys:
+ one: '%{list} was assigned more than once.'
+ other: '%{list} were assigned more than once.'
+ preview:
+ new:
+ one: '%{count} %{role} will be imported.'
+ other: '%{count} %{role} will be imported.'
+ updated:
+ one: '%{count} %{role} will be updated.'
+ other: '%{count} %{role} will be updated.'
+ failed:
+ one: '%{count} %{role} won''t be imported.'
+ other: '%{count} %{role} won''t be imported.'
+ requests:
+ one: '%{count} %{role} will receive a request for their data. Your data will not be imported.'
+ other: '%{count} %{role} will receive a request for their data. Your data will not be imported.'
+ create:
+ new:
+ one: '%{count} %{role} has been imported succesfully.'
+ other: '%{count} %{role} have been imported succesfully.'
+ updated:
+ one: '%{count} %{role} has been updated succesfully.'
+ other: '%{count} %{role} have been updated succesfully.'
+ failed:
+ one: '%{count} %{role} has not been imported.'
+ other: '%{count} %{role} have not been imported.'
+ requests:
+ one: '%{count} %{role} will receive a request for their data. Any changes from this import have to be repeated after having been granted access.'
+ other: '%{count} %{role} will receive a request for their data. Any changes from this import have to be repeated after having been granted access.'
people:
send_password_instructions: Login information has been sent.
years_old: (%{years} years old)
@@ -445,6 +628,9 @@ en:
actions_index:
add_person: Add Person
import_list: Import list
+ add_requests:
+ approve_title: 'Approve request'
+ reject_title: 'Deny request'
attrs:
additional_data: More information
events: My next events
@@ -458,10 +644,10 @@ en:
list:
number_of_people_shown:
one: "%{count} person displayed."
- other: "%{count} persons displayed."
+ other: "%{count} people displayed."
number_of_people_hidden:
one: "%{count} more person is not visible for you."
- other: "%{count} more persons are not visible for you."
+ other: "%{count} more people are not visible for you."
log:
no_changes: So far, no changes were recorded.
pdf:
@@ -469,7 +655,11 @@ en:
roles:
title: Active roles
roles_aside:
+ add_role: Add role
set_main_group: Set main group
+ tags:
+ add_tag: Add tag...
+ no_entry: (none)
participations:
destroy:
flash:
@@ -506,6 +696,7 @@ en:
add_person: Add person
add_role_for_person: Create role for %{person}
edit_role_for_person: Edit role for %{person}
+ new_role_for_person: Create role
fields:
help_optional_label: Optional name of the role of this person
person_fields:
@@ -516,8 +707,8 @@ en:
role_has_permissions: "The role %{role} in the group %{group} has the following permissions:"
role_only_public: >
The role %{role} in the group %{group} can only view public data (groups,
- events and subscriptions; no other persons).
- only_visible_from_above: This role is visible only to persons in this level, not by people from higher layers.
+ events and subscriptions; no other people).
+ only_visible_from_above: This role is visible only to people in this level, not by people from higher layers.
subscriber:
group:
form:
@@ -525,6 +716,8 @@ en:
subgroups_and_siblings_selectable: 'Only groups within the subscription group or within the adjacent groups from tye %{type} can be selected'
roles:
please_choose_group: Please select a group
+ tags:
+ only_add_persons_with_tags: 'Only add people with one of the following tags:'
exclude_person:
form:
exclude_subscriber: 'Exclude person'
@@ -538,6 +731,8 @@ en:
subscriber/exclude_person:
success: 'Subscriber %{subscriber} has been successfully excluded'
failure: '%{subscriber} is not a subscriber'
+ sheet/person/csv_import:
+ title: 'Import person via CSV'
sheet/group:
belongs_to: 'belongs to'
deleted: '(deleted)'
@@ -549,7 +744,10 @@ en:
add_event: Add event
exclude_person: 'Exclude person'
list:
- excluded_people: 'Excluded persons'
+ excluded_people: 'Excluded people'
+ export_not_allowed: 'You are not allowed to export the subscribers, because it contains people outside of your authorization.'
+ subscription:
+ only_persons_with: 'Only people with:'
version:
attribute_change:
from_to: "%{attr} has been changed from %{from} to %{to} ."
@@ -559,6 +757,10 @@ en:
create: "%{model} %{label} has been added."
update: "%{model} %{label} has been updated: %{changeset}"
destroy: "%{model} %{label} has been deleted."
+ person/add_request:
+ create: "Access for %{label} requested."
+ update: "Update of request for %{label} : %{changeset}"
+ destroy: "Request for %{label} has been answered."
views:
pagination:
next: '>>'
diff --git a/config/locales/views.fr.yml b/config/locales/views.fr.yml
index 8c525b653c..281b7ebdc3 100644
--- a/config/locales/views.fr.yml
+++ b/config/locales/views.fr.yml
@@ -45,6 +45,7 @@ fr:
placeholder_person: 'Chercher une personne …'
placeholder_group: 'Chercher un groupe ….'
placeholder_event: 'Chercher un événement ….'
+ placeholder_company_name: 'Chercher un nom d''entreprise…'
list:
index:
title: '%{models}'
@@ -83,6 +84,10 @@ fr:
available_placeholders_empty: 'Pas d''éléments de substitution disponibles'
dropdown/event/group_filter:
all_groups: 'Tous les groupes'
+ dropdown/event/events_export:
+ button: Exporter
+ csv: CSV
+ xlsx: Excel
dropdown/event/role_add:
add: 'Ajouter une personne'
dropdown/event/participant_add:
@@ -93,6 +98,8 @@ fr:
dropdown/people_export:
button: 'Exporter'
csv: 'CSV'
+ xlsx: 'Excel'
+ vcard: 'vCard'
labels: 'Etiquettes'
emails: 'Adresses électroniques'
addresses: 'Liste d''adresses'
@@ -166,7 +173,7 @@ fr:
title: 'Nous présentons nos excuses, cette page n''est pas disponible pour le moment (503)'
explanation: 'Nous travaillons sur cette page, c''est pourquoi elle n''est pas disponible. Ceci ne devrait pas durer très longtemps.'
instruction: 'Prière de réessayer plus tard.'
- export/csv/events/list:
+ export/tabular/events/list:
group_names: 'organisateurs'
duration: Délai
date: "Date %{index}"
@@ -175,7 +182,6 @@ fr:
applied_to: Inscription auprès de
alternative_dates: Dates alternatives
priorities: 'Priorité d''inscription'
- no_qualifications_could_be_prolonged: "%{person} n'a pas de qualifications susceptibles d'êtres prolongées."
lists:
courses:
title: 'Cours disponibles'
@@ -184,10 +190,12 @@ fr:
events:
title: 'Evénements prévus prochainement'
explanation: 'Les événements de ton groupe ainsi que des groupes hiérarchiquement supérieurs sont indiqués ici. Tu trouveras d''autres événements auprès des groupes qui les organisent.'
+ apply_until: 'jusqu''au %{date}'
application_market:
already_assigned: 'Cette personne est déjà attribuée à un autre endroit.'
participation:
registered_at: 'Inscription le %{date}'
+ town: Domicile
prio_buttons:
national_waitinglist: 'liste d''attente nationale'
index:
@@ -211,10 +219,24 @@ fr:
qualifications:
for_participants: 'Qualifications des participants'
for_leaders: 'Qualifications des responsables'
+ precondition_fields:
+ add_precondition_grouping: '+ Ajouter une condition préalable'
+ add_precondition: 'Ajouter'
+ qualifications:
+ or: ou
+ and: et
+ participation_contact_datas:
+ edit:
+ title: Coordonnées
+ save: Continuer
participations:
+ edit:
+ title: Inscription
approvals:
title: Autorisation
specific_information: 'Informations spécifiques'
+ application_answers: Données pour l'inscription
+ admin_answers: Données administratives
no_answer_given: '(pas de réponse)'
actions_show:
change_contact_data_button: 'Modifier les données de contact'
@@ -243,6 +265,16 @@ fr:
on_waiting_list: Sur la liste d'attente
list:
incomplete: des indications obligatoires manquent
+ cancel_application:
+ explanation: Tu es déjà inscrit(e) à cet évènement.
+ caption: Désinscrire
+ confirmation: "Es-tu sûr/sure de vouloir annuler ton inscription à cet évènement?"
+ qualifications:
+ index:
+ save: Mettre à jour les qualifications
+ update:
+ flash:
+ success: Les qualifications ont été mises à jour.
register:
register:
title: Saisir les données de contact
@@ -260,19 +292,28 @@ fr:
signature_confirmation_text_default: Autorité parentale (pour les mineurs)
attachments:
add: "ajouter"
+ default_description_link:
+ insert_general_information: Placer une description standard
form:
caption_external_applications: 'Les personnes externes peuvent s''inscrire à cet événement.'
caption_prioritization: 'Les participants peuvent indiquer deux autres cours alternatifs lors de l''inscription.'
caption_requires_approval: 'Le responsable de troupe est informé et doit confirmer l''inscription.'
- additional_information: 'Données pour l''inscription'
times_are_optional: L'heure est optionnelle.
explain_application_questions: Tu peux demander des données supplémentaires pour l'inscription. Saisis les réponses possibles en les séparant avec une virgule, ou laisse le champ vierge pour permettre des réponses libres.
+ explain_admin_questions: Tu peux définir des données supplémentaires pour chaque participant(e); elles ne seront utilisées que pour l'administration du cours.
+ explain_contact_attrs: Tu peux choisir les coordonnées requises pour l'inscription.
+ caption_applications_cancelable: Les participants peuvent se désinscrire eux-mêmes.
+ caption_display_booking_info: Le nombre d'inscrits et de places libres est visible pour tous sur la liste de cours.
form_tabs:
general: Général
+ application_questions: Données pour l'inscription
+ admin_questions: Données administratives
+ contact_attrs: Coordonnées
global:
link:
add_event: Créer un événement
add_event/course: Créer un cours
+ duplicate: Dupliquer
minimum_age_with_years: 'au moins %{minimum_age} ans (année de naissance)'
new:
title_event: Créer un événement
@@ -296,7 +337,7 @@ fr:
title: Créer une catégorie de cours
event/participations:
full_entry_label: '%{model_label} de %{person} dans %{event} '
- success: '%{full_entry_label} a été ajouté avec succès. Prière de contrôler les informations de contact et de les adapter le cas échéant.'
+ success: '%{full_entry_label} a été ajouté avec succès.'
instructions: 'Pour l''inscription définitive, tu dois imprimer cette page en utilisant l''option imprimer , puis la signer et l''envoyer par poste à l''adresse indiquée.'
show:
link:
@@ -309,6 +350,7 @@ fr:
preconditions_not_fulfilled: 'Des conditions d''inscription ne sont pas remplies. '
below_minimum_age: "L'âge limite de %{course_minimum_age} n'est pas atteint."
qualifications_missing: "Les qualifications suivantes manquent : %{missing}"
+ some_qualifications_missing: "Des qualifications requises manquent."
event/register:
not_logged_in: "Tu dois t'authentifier afin de pouvoir t'inscrire à l'événement '%{event}'."
person_found: 'Nous t''avons trouvé(e) dans notre base de données.'
@@ -393,11 +435,14 @@ fr:
attrs:
contact_details: 'Contacts'
additional_information: 'Informations supplémentaires'
+ deleted_subgroups:
+ no_deleted_sub_groups: 'Pas de groupes effacés.'
form:
help_contact: 'Utiliser l''adresse et le numéro de téléphone public de cette personne'
global:
link:
add: Créer un groupe
+ deleted_person: Personnes effacées
new:
title: 'Créer un %{model}'
reactivated: "Le groupe %{group} a été réactivé avec succès."
@@ -431,6 +476,9 @@ fr:
import/person_doublette_finder:
duplicates: "%{count} emplacements trouvés lors de la recherche de doublons."
label_formats:
+ global_labels: "Formats globaux d'étiquettes"
+ own_labels: Mes formats d'étiquettes
+ see_global_labels: "Afficher les formats globaux d'étiquettes"
form:
portrait: 'Format portrait'
landscape: 'Format paysage'
@@ -471,6 +519,13 @@ fr:
events: 'Activités'
courses: 'Cours'
admin: 'Administrateur'
+ notes:
+ new_note: Ajouter une note
+ note:
+ created: il y a %{time_ago}
+ paginator:
+ newer_notes: Nouvelles contributions
+ older_notes: Anciennes contributions
qualifications:
in_years: '%{years} ans'
valid_until: 'à %{date}'
@@ -487,6 +542,7 @@ fr:
paper_trail/version_decorator:
by: 'de %{author}'
person:
+ confirm_delete: "Toutes les données sur %{person} vont être définitivement effacées, y compris les participations et les qualifications. N'efface cette personne que si plus personne n'a besoin de ces données. "
add_requests:
body_list:
open_requests:
@@ -570,12 +626,6 @@ fr:
request_link: Répondre à la requête
ask_responsibles:
request_link: 'Répondre à la requête '
- notes:
- note:
- created: il y a %{time_ago}
- paginator:
- newer_notes: Plus récent
- older_notes: Plus ancien
tags:
list:
category_other: Autre
@@ -616,6 +666,7 @@ fr:
tabs:
history: Historique
log: Journal
+ colleagues: Collaborateurs/collaboratrices
actions_show:
send_login: Envoyer les données d'identification
send_login_tooltip:
@@ -658,8 +709,6 @@ fr:
tags:
add_tag: Ajouter un tag…
no_entry: (aucun)
- notes:
- new_note: Ajouter une note
participations:
destroy:
flash:
@@ -676,8 +725,16 @@ fr:
title: Filtrer les personnes par qualification
prompt_qualification_selection: Quelles qualifications doivent apparaître?
prompt_validity: Quelles validités doivent avoir les qualifications montrées?
+ prompt_year: Restreindre l'année de qualification
prompt_kind: Dans quel secteur faut-il chercher ?
+ start_at_year_label: 'Reçu entre '
+ start_at_year_infix: et
+ finish_at_year_label: Périmé entre
+ finish_at_year_infix: et
simple_radio:
+ match:
+ one: La personne a au moins UNE de ces qualifications
+ all: La personne a TOUTES ces qualifications
validity:
active: Uniquement les qualifications valides
reactivateable: Les qualifications valides et renouvelables
@@ -696,6 +753,7 @@ fr:
add_person: Ajouter une personne
add_role_for_person: Créer un rôle pour %{person}
edit_role_for_person: Modifier un rôle pour %{person}
+ new_role_for_person: Créer un nouveau rôle
fields:
help_optional_label: désignation facultative du rôle de cette personne
person_fields:
@@ -747,6 +805,7 @@ fr:
export_not_allowed: 'Tu n''as pas le droit d''exporté les abonnés de cet abonnement, car ils contiennent des rôles ou des activités qui ne font pas partie de ton domaine d''autorisation.'
subscription:
only_persons_with: 'Seulement personnes avec:'
+ export_enqueued: 'L''exportation a commencé. Il sera envoyé à %{email} une fois terminé.'
version:
attribute_change:
from_to: "%{attr} a été changé de %{from} à %{to} ."
diff --git a/config/locales/views.it.yml b/config/locales/views.it.yml
index fc45cdf20b..f75112fc1c 100644
--- a/config/locales/views.it.yml
+++ b/config/locales/views.it.yml
@@ -45,6 +45,7 @@ it:
placeholder_person: 'Ricerca della persona....'
placeholder_group: 'Ricerca del gruppo...'
placeholder_event: 'Ricerca dell''evento...'
+ placeholder_company_name: 'Ricerca nome della ditta'
list:
index:
title: '%{models}'
@@ -83,6 +84,10 @@ it:
available_placeholders_empty: 'nessun carattere a disposizione'
dropdown/event/group_filter:
all_groups: 'tutti i gruppi'
+ dropdown/event/events_export:
+ button: esportare
+ csv: CSV
+ xlsx: Excel
dropdown/event/role_add:
add: 'aggiungere una persona'
dropdown/event/participant_add:
@@ -93,6 +98,8 @@ it:
dropdown/people_export:
button: 'esportare'
csv: 'CSV'
+ xlsx: 'Excel'
+ vcard: 'vCard'
labels: 'etichette'
emails: 'indirizzi email'
addresses: 'lista di indirizzi'
@@ -166,7 +173,7 @@ it:
title: 'siamo spiacenti, questa pagina non è disponibile al momento (503)'
explanation: 'al momento stiamo lavorando a questa pagine e per questo motivo non è disponibile. Non dovrebbe durare a lungo.'
instruction: 'vi preghiamo di riprovare nuovamente più tardi.'
- export/csv/events/list:
+ export/tabular/events/list:
group_names: 'organizzatori'
duration: Periodo di tempo
date: "Data %{index}"
@@ -175,7 +182,6 @@ it:
applied_to: iscrizioni a
alternative_dates: dati alternativi
priorities: 'Priorità per l''iscrizione'
- no_qualifications_could_be_prolonged: "%{person} non ha nessuna qualifica che può essere prolungata."
lists:
courses:
title: 'corsi disponibili'
@@ -184,10 +190,12 @@ it:
events:
title: 'eventi che si terranno prossimamente'
explanation: 'Qui vengono mostrati gli eventi dei gruppi così come nei gruppi superiori nei quali sei membro. Puoi trovare altri eventi tra i gruppi che li organizzano.'
+ apply_until: 'fino a %{date}'
application_market:
already_assigned: 'Questa persona è già assegnata da un''altra parte.'
participation:
registered_at: 'iscrizione il %{date}'
+ town: Domicilio
prio_buttons:
national_waitinglist: 'lista d''attesa nazionale'
index:
@@ -211,10 +219,24 @@ it:
qualifications:
for_participants: 'Qualifiche per i partecipanti'
for_leaders: 'Qualifiche per gli animatori'
+ precondition_fields:
+ add_precondition_grouping: 'Aggiungere requisiti per la partecipazione'
+ add_precondition: 'aggiungere'
+ qualifications:
+ or: oppure
+ and: e
+ participation_contact_datas:
+ edit:
+ title: Dati di contatto
+ save: avanti
participations:
+ edit:
+ title: Iscrizione
approvals:
title: Attivazione
specific_information: 'dati specifici'
+ application_answers: Dati per l'iscrizione
+ admin_answers: Dati amministrativi
no_answer_given: '(non risponde)'
actions_show:
change_contact_data_button: 'modificare i dati di contatto'
@@ -243,6 +265,16 @@ it:
on_waiting_list: In lista d'attesa
list:
incomplete: mancano dei campi obbligatori
+ cancel_application:
+ explanation: Sei iscritto a questo evento.
+ caption: Disiscriversi
+ confirmation: "Sei sicuro di volerti disiscrivere da questo evento?"
+ qualifications:
+ index:
+ save: Aggiornare le qualifiche
+ update:
+ flash:
+ success: Le qualifiche sono state aggiornate con successo.
register:
register:
title: registrare i dati di contatto
@@ -260,19 +292,28 @@ it:
signature_confirmation_text_default: Rappresentante legale (per i minorenni)
attachments:
add: "aggiungere"
+ default_description_link:
+ insert_general_information: Inserire informazioni generali
form:
caption_external_applications: 'esterni possono iscriversi a questo evento'
caption_prioritization: 'i partecipanti possono dare durante l''iscrizione due ulteriori corsi come alternative.'
caption_requires_approval: 'il responsabile verrà informato e dovrà confermare l''iscrizione'
- additional_information: 'dati per l''iscrizione'
times_are_optional: Gli orari sono opzionali
explain_application_questions: qui puoi richiedere altri dati per l'iscrizione. Digita le risposte dividendole con una virgola oppure lascia il campo vuoto per rendere possibile qualunque risposta.
+ explain_admin_questions: Qui per ogni partecipante puoi definire altri dati che vengono utilizzati solo per l'amministratzione del corso.
+ explain_contact_attrs: Qui puoi scegliere quali dati di contatto devono essere chiesti al momento dell'iscrizione.
+ caption_applications_cancelable: I partecipanti si possono disiscrivere personalmente
+ caption_display_booking_info: Il numero di iscrizioni/posti è visibile a tutti sulla lista del corso
form_tabs:
general: In generale
+ application_questions: Dati per l'iscrizione
+ admin_questions: Dati amministrativi
+ contact_attrs: Dati di contatto
global:
link:
add_event: Creare un evento
add_event/course: Creare un corso
+ duplicate: Raddoppiare
minimum_age_with_years: '%{minimum_age} anni (anno di nascita)'
new:
title_event: Creare un evento
@@ -296,7 +337,7 @@ it:
title: Creare il tipo di corso
event/participations:
full_entry_label: '%{model_label} da %{person} a %{event} '
- success: '%{full_entry_label} è stato creato con successo. Verifica i dati di contatto e eventualmente aggiornali'
+ success: '%{full_entry_label} è stato creato con successo.'
instructions: 'per l''iscrizione definitiva devi stampare questa pagina tramita stampare, firmarla e spedirla per posta all''indirizzo corrispondente'
show:
link:
@@ -309,6 +350,7 @@ it:
preconditions_not_fulfilled: 'le condizioni per iscriversi non sono soddisfatte. '
below_minimum_age: "il limite di età di %{course_minimum_age} non è raggiunto"
qualifications_missing: "mancano le qualifiche seguenti: %{missing}"
+ some_qualifications_missing: "Le qualifiche necessarie mancano."
event/register:
not_logged_in: "devi effettuare l'accesso per poterti iscrivere all'evento '%{event}'."
person_found: 'ti abbiamo trovato all''interno della nostra banca dati.'
@@ -392,11 +434,14 @@ it:
attrs:
contact_details: 'Dati di contatto'
additional_information: 'Altri dati'
+ deleted_subgroups:
+ no_deleted_sub_groups: 'Non ci sono gruppi cancellati'
form:
help_contact: 'Utilizzare l''indirizzo e il numero di telefono pubblico di questa persona'
global:
link:
add: Creare un gruppo
+ deleted_person: Persone cancellate
new:
title: 'Creare %{model} '
reactivated: "Il gruppo %{group} è stato riattivato con successo."
@@ -430,6 +475,9 @@ it:
import/person_doublette_finder:
duplicates: "%{count} risultati nel riconoscimento di duplicati."
label_formats:
+ global_labels: "Formati globali delle etichette"
+ own_labels: I miei formati delle etichette
+ see_global_labels: "Indicare formati globali delle etichette"
form:
portrait: 'formato verticale'
landscape: 'formato orizzontale'
@@ -470,6 +518,13 @@ it:
events: 'eventi'
courses: 'corsi'
admin: 'admin'
+ notes:
+ new_note: Nuova nota
+ note:
+ created: fa %{time_ago}
+ paginator:
+ newer_notes: Contributi più recenti
+ older_notes: Contributi più vecchi
qualifications:
in_years: '%{years} anni'
valid_until: 'fino a %{date}'
@@ -486,6 +541,7 @@ it:
paper_trail/version_decorator:
by: 'da %{author}'
person:
+ confirm_delete: "Tutti i dati relativi a %{person} vengono cancellati definitivamente, incl. partecipazioni e qualifiche. Cancella questa persona solo se nessuno ha ancora bisogno di queste informazioni."
add_requests:
body_list:
open_requests:
@@ -549,10 +605,12 @@ it:
keep_behaviour: >
Aggiornare solo i campi vuoti nella banca dati con i dati del CSV; i valori
già esistenti vengono mantenuti.
+ keep_behaviour_tags: I tags vengono completati.
override_behaviour: >
Sovrascrivere i valori già esistenti nella banca dati con i dati del CSV.
I valori vuoti nel CSV cancellano in questo caso i valori esistenti nella
banca dati.
+ override_behaviour_tags: I tags vengono cancellati o completati.
update_behaviour_explanation: >
Questa funzione si applica unicamente alle persone già esistenti nella
banca dati. Nuove persone vengono sempre create in totale corrispondenza
@@ -570,12 +628,6 @@ it:
request_link: Rispondere alla richiesta
ask_responsibles:
request_link: Rispondere alla richiesta
- notes:
- note:
- created: fa %{time_ago}
- paginator:
- newer_notes: Più nuovo
- older_notes: Più vecchio
tags:
list:
category_other: Altro
@@ -616,6 +668,7 @@ it:
tabs:
history: Azioni
log: Log
+ colleagues: Collaboratore
actions_show:
send_login: Inviare login
send_login_tooltip:
@@ -657,8 +710,6 @@ it:
tags:
add_tag: Aggiungere un tag
no_entry: (nessuno)
- notes:
- new_note: Aggiungere una nota
participations:
destroy:
flash:
@@ -675,8 +726,16 @@ it:
title: Filtrare le persone secondo le qualifiche
prompt_qualification_selection: Quali qualifiche devono essere visualizzate?
prompt_validity: Che validità devono avere le qualifiche mostrate?
+ prompt_year: Limitare l'anno delle qualifiche
prompt_kind: In quale ambito deve essere ricercato?
+ start_at_year_label: Ricevuto tra
+ start_at_year_infix: e
+ finish_at_year_label: Scaduto tra
+ finish_at_year_infix: e
simple_radio:
+ match:
+ one: La persona ha almeno UNA di queste qualifiche
+ all: La persona ha TUTTE queste qualifiche
validity:
active: Solo qualifiche attuali e valide
reactivateable: Qualifiche valide e riattivabili
@@ -695,6 +754,7 @@ it:
add_person: Aggiungere una persona
add_role_for_person: 'Inserire un ruolo per %{person} '
edit_role_for_person: 'Modificare il ruolo per %{person} '
+ new_role_for_person: Creare un nuovo ruolo
fields:
help_optional_label: Designazione opzionale del ruolo di questa persona
person_fields:
@@ -714,6 +774,8 @@ it:
subgroups_and_siblings_selectable: 'Possono essere selezionati unicamente i gruppi all''interno del gruppo abbonato oppure i gruppi vicini di tipo %{type}'
roles:
please_choose_group: prego selezionare un gruppo
+ tags:
+ only_add_persons_with_tags: 'Aggiungere solo una persona con i tags sottostanti'
exclude_person:
form:
exclude_subscriber: 'Escludere abbonato/a'
@@ -742,6 +804,9 @@ it:
list:
excluded_people: 'persone escluse'
export_not_allowed: 'Non è possibiie esportare gli abbonati di questo abbonamento perché contengono anche ruoli e/o eventi del tuo ambito di autorizzazioni.'
+ subscription:
+ only_persons_with: 'Solo persone con'
+ export_enqueued: 'L''esportazione viene effettuata e verrà inviata a %{email}.'
version:
attribute_change:
from_to: "%{attr} è stato modificato da %{from} in %{to} ."
diff --git a/config/routes.rb b/config/routes.rb
index c41e8c1d71..2c711f102f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -18,10 +18,11 @@
get '/503', to: 'errors#503'
get '/people/query' => 'person/query#index', as: :query_people
+ get '/people/company_name' => 'person/company_name#index', as: :query_company_name
get '/people/:id' => 'person/top#show', as: :person
+ get '/events/:id' => 'event/top#show', as: :event
resources :groups do
-
member do
get :deleted_subgroups
get :export_subgroups
@@ -33,24 +34,37 @@
get 'move' => 'group/move#select'
post 'move' => 'group/move#perform'
- get 'person_notes' => 'person/notes#index'
end
+ resource :invoice_list, except: [:edit]
+ resource :invoice_config, only: [:edit, :show, :update]
+ resources :invoices do
+ resources :payment_reminders, only: :create
+ resources :payments, only: :create
+ end
+ resources :invoice_articles
+
+ resources :notes, only: [:index, :create, :destroy]
+
resources :people, except: [:new, :create] do
+
member do
post :send_password_instructions
put :primary_group
get 'history' => 'person/history#index'
get 'log' => 'person/log#index'
+ get 'colleagues' => 'person/colleagues#index'
+ get 'invoices' => 'person/invoices#index'
end
+ resources :notes, only: [:create, :destroy]
resources :qualifications, only: [:new, :create, :destroy]
get 'qualifications' => 'qualifications#new' # route required for language switch
scope module: 'person' do
- resources :notes, only: [:create]
- resources :tags, param: :name, only: [:create, :destroy]
+ post 'tags' => 'tags#create'
+ delete 'tags' => 'tags#destroy'
get 'tags/query' => 'tags#query'
end
end
@@ -64,14 +78,12 @@
get 'roles' => 'roles#new' # route required for language switch
get 'roles/:id' => 'roles#edit' # route required for language switch
- resources :people_filters, only: [:new, :create, :destroy] do
- collection do
- get 'qualification'
- end
- end
+ resources :people_filters, only: [:new, :create, :edit, :update, :destroy]
get 'people_filters' => 'people_filters#new' # route required for language switch
+ get 'deleted_people' => 'group/deleted_people#index'
+
get 'person_add_requests' => 'group/person_add_requests#index', as: :person_add_requests
post 'person_add_requests' => 'group/person_add_requests#activate'
delete 'person_add_requests' => 'group/person_add_requests#deactivate'
@@ -113,15 +125,21 @@
resources :attachments, only: [:create, :destroy]
resources :participations do
- get 'print', on: :member
+ collection do
+ get 'contact_data', controller: 'participation_contact_datas', action: 'edit'
+ post 'contact_data', controller: 'participation_contact_datas', action: 'update'
+ end
+ member do
+ get 'print'
+ end
end
resources :roles, except: [:index, :show]
get 'roles' => 'roles#new' # route required for language switch
get 'roles/:id' => 'roles#edit' # route required for language switch
- resources :qualifications, only: [:index, :update, :destroy]
-
+ get 'qualifications' => 'qualifications#index'
+ put 'qualifications' => 'qualifications#update'
end
end
@@ -176,7 +194,11 @@
resources :qualification_kinds
- resources :label_formats
+ resources :label_formats do
+ collection do
+ resource :settings, controller: 'label_format/settings', as: 'label_format_settings'
+ end
+ end
resources :custom_contents, only: [:index, :edit, :update]
get 'custom_contents/:id' => 'custom_contents#edit'
diff --git a/config/rpm/rails-app.spec b/config/rpm/rails-app.spec
index 716c7f2f20..3205cb5040 100644
--- a/config/rpm/rails-app.spec
+++ b/config/rpm/rails-app.spec
@@ -3,7 +3,7 @@
%define app_name RPM_NAME
-%define app_version 1.14
+%define app_version 1.15
%define ruby_version 1.9.3
### optional libs
diff --git a/config/thinking_sphinx.yml b/config/thinking_sphinx.yml
index 6cbee37b3e..9d34f5e93d 100644
--- a/config/thinking_sphinx.yml
+++ b/config/thinking_sphinx.yml
@@ -10,11 +10,13 @@ base: &generic
max_filter_values: 1048576
charset_type: utf-8
utf8: false
- # maps accented characters to their 'base' character (e.g. é => e) # furthermore, the following punctuation characters are indexed: _.:
- charset_table: "0..9, a..z, _, U+002E->., U+003A, A..Z->a..z, U+00C0->a, U+00C1->a, U+00C2->a, U+00C3->a, U+00C4->a, U+00C5->a, U+00C7->c, U+00C8->e, U+00C9->e, U+00CA->e, U+00CB->e, U+00CC->i, U+00CD->i, U+00CE->i, U+00CF->i, U+00D1->n, U+00D2->o, U+00D3->o, U+00D4->o, U+00D5->o, U+00D6->o, U+00D9->u, U+00DA->u, U+00DB->u, U+00DC->u, U+00DD->y, U+00E0->a, U+00E1->a, U+00E2->a, U+00E3->a, U+00E4->a, U+00E5->a, U+00E7->c, U+00E8->e, U+00E9->e, U+00EA->e, U+00EB->e, U+00EC->i, U+00ED->i, U+00EE->i, U+00EF->i, U+00F1->n, U+00F2->o, U+00F3->o, U+00F4->o, U+00F5->o, U+00F6->o, U+00F9->u, U+00FA->u, U+00FB->u, U+00FC->u, U+00FD->y, U+00FF->y, U+0100->a, U+0101->a, U+0102->a, U+0103->a, U+0104->a, U+0105->a, U+0106->c, U+0107->c, U+0108->c, U+0109->c, U+010A->c, U+010B->c, U+010C->c, U+010D->c, U+010E->d, U+010F->d, U+0112->e, U+0113->e, U+0114->e, U+0115->e, U+0116->e, U+0117->e, U+0118->e, U+0119->e, U+011A->e, U+011B->e, U+011C->g, U+011D->g, U+011E->g, U+011F->g, U+0120->g, U+0121->g, U+0122->g, U+0123->g, U+0124->h, U+0125->h, U+0128->i, U+0129->i, U+012A->i, U+012B->i, U+012C->i, U+012D->i, U+012E->i, U+012F->i, U+0130->i, U+0134->j, U+0135->j, U+0136->k, U+0137->k, U+0139->l, U+013A->l, U+013B->l, U+013C->l, U+013D->l, U+013E->l, U+0142->l, U+0143->n, U+0144->n, U+0145->n, U+0146->n, U+0147->n, U+0148->n, U+014C->o, U+014D->o, U+014E->o, U+014F->o, U+0150->o, U+0151->o, U+0154->r, U+0155->r, U+0156->r, U+0157->r, U+0158->r, U+0159->r, U+015A->s, U+015B->s, U+015C->s, U+015D->s, U+015E->s, U+015F->s, U+0160->s, U+0161->s, U+0162->t, U+0163->t, U+0164->t, U+0165->t, U+0168->u, U+0169->u, U+016A->u, U+016B->u, U+016C->u, U+016D->u, U+016E->u, U+016F->u, U+0170->u, U+0171->u, U+0172->u, U+0173->u, U+0174->w, U+0175->w, U+0176->y, U+0177->y, U+0178->y, U+0179->z, U+017A->z, U+017B->z, U+017C->z, U+017D->z, U+017E->z, U+01A0->o, U+01A1->o, U+01AF->u, U+01B0->u, U+01CD->a, U+01CE->a, U+01CF->i, U+01D0->i, U+01D1->o, U+01D2->o, U+01D3->u, U+01D4->u, U+01D5->u, U+01D6->u, U+01D7->u, U+01D8->u, U+01D9->u, U+01DA->u, U+01DB->u, U+01DC->u, U+01DE->a, U+01DF->a, U+01E0->a, U+01E1->a, U+01E6->g, U+01E7->g, U+01E8->k, U+01E9->k, U+01EA->o, U+01EB->o, U+01EC->o, U+01ED->o, U+01F0->j, U+01F4->g, U+01F5->g, U+01F8->n, U+01F9->n, U+01FA->a, U+01FB->a, U+0200->a, U+0201->a, U+0202->a, U+0203->a, U+0204->e, U+0205->e, U+0206->e, U+0207->e, U+0208->i, U+0209->i, U+020A->i, U+020B->i, U+020C->o, U+020D->o, U+020E->o, U+020F->o, U+0210->r, U+0211->r, U+0212->r, U+0213->r, U+0214->u, U+0215->u, U+0216->u, U+0217->u, U+0218->s, U+0219->s, U+021A->t, U+021B->t, U+021E->h, U+021F->h, U+0226->a, U+0227->a, U+0228->e, U+0229->e, U+022A->o, U+022B->o, U+022C->o, U+022D->o, U+022E->o, U+022F->o, U+0230->o, U+0231->o, U+0232->y, U+0233->y, U+1E00->a, U+1E01->a, U+1E02->b, U+1E03->b, U+1E04->b, U+1E05->b, U+1E06->b, U+1E07->b, U+1E08->c, U+1E09->c, U+1E0A->d, U+1E0B->d, U+1E0C->d, U+1E0D->d, U+1E0E->d, U+1E0F->d, U+1E10->d, U+1E11->d, U+1E12->d, U+1E13->d, U+1E14->e, U+1E15->e, U+1E16->e, U+1E17->e, U+1E18->e, U+1E19->e, U+1E1A->e, U+1E1B->e, U+1E1C->e, U+1E1D->e, U+1E1E->f, U+1E1F->f, U+1E20->g, U+1E21->g, U+1E22->h, U+1E23->h, U+1E24->h, U+1E25->h, U+1E26->h, U+1E27->h, U+1E28->h, U+1E29->h, U+1E2A->h, U+1E2B->h, U+1E2C->i, U+1E2D->i, U+1E2E->i, U+1E2F->i, U+1E30->k, U+1E31->k, U+1E32->k, U+1E33->k, U+1E34->k, U+1E35->k, U+1E36->l, U+1E37->l, U+1E38->l, U+1E39->l, U+1E3A->l, U+1E3B->l, U+1E3C->l, U+1E3D->l, U+1E3E->m, U+1E3F->m, U+1E40->m, U+1E41->m, U+1E42->m, U+1E43->m, U+1E44->n, U+1E45->n, U+1E46->n, U+1E47->n, U+1E48->n, U+1E49->n, U+1E4A->n, U+1E4B->n, U+1E4C->o, U+1E4D->o, U+1E4E->o, U+1E4F->o, U+1E50->o, U+1E51->o, U+1E52->o, U+1E53->o, U+1E54->p, U+1E55->p, U+1E56->p, U+1E57->p, U+1E58->r, U+1E59->r, U+1E5A->r, U+1E5B->r, U+1E5C->r, U+1E5D->r, U+1E5E->r, U+1E5F->r, U+1E60->s, U+1E61->s, U+1E62->s, U+1E63->s, U+1E64->s, U+1E65->s, U+1E66->s, U+1E67->s, U+1E68->s, U+1E69->s, U+1E6A->t, U+1E6B->t, U+1E6C->t, U+1E6D->t, U+1E6E->t, U+1E6F->t, U+1E70->t, U+1E71->t, U+1E72->u, U+1E73->u, U+1E74->u, U+1E75->u, U+1E76->u, U+1E77->u, U+1E78->u, U+1E79->u, U+1E7A->u, U+1E7B->u, U+1E7C->v, U+1E7D->v, U+1E7E->v, U+1E7F->v, U+1E80->w, U+1E81->w, U+1E82->w, U+1E83->w, U+1E84->w, U+1E85->w, U+1E86->w, U+1E87->w, U+1E88->w, U+1E89->w, U+1E8A->x, U+1E8B->x, U+1E8C->x, U+1E8D->x, U+1E8E->y, U+1E8F->y, U+1E96->h, U+1E97->t, U+1E98->w, U+1E99->y, U+1EA0->a, U+1EA1->a, U+1EA2->a, U+1EA3->a, U+1EA4->a, U+1EA5->a, U+1EA6->a, U+1EA7->a, U+1EA8->a, U+1EA9->a, U+1EAA->a, U+1EAB->a, U+1EAC->a, U+1EAD->a, U+1EAE->a, U+1EAF->a, U+1EB0->a, U+1EB1->a, U+1EB2->a, U+1EB3->a, U+1EB4->a, U+1EB5->a, U+1EB6->a, U+1EB7->a, U+1EB8->e, U+1EB9->e, U+1EBA->e, U+1EBB->e, U+1EBC->e, U+1EBD->e, U+1EBE->e, U+1EBF->e, U+1EC0->e, U+1EC1->e, U+1EC2->e, U+1EC3->e, U+1EC4->e, U+1EC5->e, U+1EC6->e, U+1EC7->e, U+1EC8->i, U+1EC9->i, U+1ECA->i, U+1ECB->i, U+1ECC->o, U+1ECD->o, U+1ECE->o, U+1ECF->o, U+1ED0->o, U+1ED1->o, U+1ED2->o, U+1ED3->o, U+1ED4->o, U+1ED5->o, U+1ED6->o, U+1ED7->o, U+1ED8->o, U+1ED9->o, U+1EDA->o, U+1EDB->o, U+1EDC->o, U+1EDD->o, U+1EDE->o, U+1EDF->o, U+1EE0->o, U+1EE1->o, U+1EE2->o, U+1EE3->o, U+1EE4->u, U+1EE5->u, U+1EE6->u, U+1EE7->u, U+1EE8->u, U+1EE9->u, U+1EEA->u, U+1EEB->u, U+1EEC->u, U+1EED->u, U+1EEE->u, U+1EEF->u, U+1EF0->u, U+1EF1->u, U+1EF2->y, U+1EF3->y, U+1EF4->y, U+1EF5->y, U+1EF6->y, U+1EF7->y, U+1EF8->y, U+1EF9->y" # specify all indexed models to decrease loading time in development mode # and avoid obscurly missing models in production mode.
+ # maps accented characters to their 'base' character (e.g. é => e)
+ # furthermore, the following punctuation characters are indexed: _-.:
+ charset_table: "0..9, a..z, _, -, U+002E->., U+003A, A..Z->a..z, U+00C0->a, U+00C1->a, U+00C2->a, U+00C3->a, U+00C4->a, U+00C5->a, U+00C7->c, U+00C8->e, U+00C9->e, U+00CA->e, U+00CB->e, U+00CC->i, U+00CD->i, U+00CE->i, U+00CF->i, U+00D1->n, U+00D2->o, U+00D3->o, U+00D4->o, U+00D5->o, U+00D6->o, U+00D9->u, U+00DA->u, U+00DB->u, U+00DC->u, U+00DD->y, U+00E0->a, U+00E1->a, U+00E2->a, U+00E3->a, U+00E4->a, U+00E5->a, U+00E7->c, U+00E8->e, U+00E9->e, U+00EA->e, U+00EB->e, U+00EC->i, U+00ED->i, U+00EE->i, U+00EF->i, U+00F1->n, U+00F2->o, U+00F3->o, U+00F4->o, U+00F5->o, U+00F6->o, U+00F9->u, U+00FA->u, U+00FB->u, U+00FC->u, U+00FD->y, U+00FF->y, U+0100->a, U+0101->a, U+0102->a, U+0103->a, U+0104->a, U+0105->a, U+0106->c, U+0107->c, U+0108->c, U+0109->c, U+010A->c, U+010B->c, U+010C->c, U+010D->c, U+010E->d, U+010F->d, U+0112->e, U+0113->e, U+0114->e, U+0115->e, U+0116->e, U+0117->e, U+0118->e, U+0119->e, U+011A->e, U+011B->e, U+011C->g, U+011D->g, U+011E->g, U+011F->g, U+0120->g, U+0121->g, U+0122->g, U+0123->g, U+0124->h, U+0125->h, U+0128->i, U+0129->i, U+012A->i, U+012B->i, U+012C->i, U+012D->i, U+012E->i, U+012F->i, U+0130->i, U+0134->j, U+0135->j, U+0136->k, U+0137->k, U+0139->l, U+013A->l, U+013B->l, U+013C->l, U+013D->l, U+013E->l, U+0142->l, U+0143->n, U+0144->n, U+0145->n, U+0146->n, U+0147->n, U+0148->n, U+014C->o, U+014D->o, U+014E->o, U+014F->o, U+0150->o, U+0151->o, U+0154->r, U+0155->r, U+0156->r, U+0157->r, U+0158->r, U+0159->r, U+015A->s, U+015B->s, U+015C->s, U+015D->s, U+015E->s, U+015F->s, U+0160->s, U+0161->s, U+0162->t, U+0163->t, U+0164->t, U+0165->t, U+0168->u, U+0169->u, U+016A->u, U+016B->u, U+016C->u, U+016D->u, U+016E->u, U+016F->u, U+0170->u, U+0171->u, U+0172->u, U+0173->u, U+0174->w, U+0175->w, U+0176->y, U+0177->y, U+0178->y, U+0179->z, U+017A->z, U+017B->z, U+017C->z, U+017D->z, U+017E->z, U+01A0->o, U+01A1->o, U+01AF->u, U+01B0->u, U+01CD->a, U+01CE->a, U+01CF->i, U+01D0->i, U+01D1->o, U+01D2->o, U+01D3->u, U+01D4->u, U+01D5->u, U+01D6->u, U+01D7->u, U+01D8->u, U+01D9->u, U+01DA->u, U+01DB->u, U+01DC->u, U+01DE->a, U+01DF->a, U+01E0->a, U+01E1->a, U+01E6->g, U+01E7->g, U+01E8->k, U+01E9->k, U+01EA->o, U+01EB->o, U+01EC->o, U+01ED->o, U+01F0->j, U+01F4->g, U+01F5->g, U+01F8->n, U+01F9->n, U+01FA->a, U+01FB->a, U+0200->a, U+0201->a, U+0202->a, U+0203->a, U+0204->e, U+0205->e, U+0206->e, U+0207->e, U+0208->i, U+0209->i, U+020A->i, U+020B->i, U+020C->o, U+020D->o, U+020E->o, U+020F->o, U+0210->r, U+0211->r, U+0212->r, U+0213->r, U+0214->u, U+0215->u, U+0216->u, U+0217->u, U+0218->s, U+0219->s, U+021A->t, U+021B->t, U+021E->h, U+021F->h, U+0226->a, U+0227->a, U+0228->e, U+0229->e, U+022A->o, U+022B->o, U+022C->o, U+022D->o, U+022E->o, U+022F->o, U+0230->o, U+0231->o, U+0232->y, U+0233->y, U+1E00->a, U+1E01->a, U+1E02->b, U+1E03->b, U+1E04->b, U+1E05->b, U+1E06->b, U+1E07->b, U+1E08->c, U+1E09->c, U+1E0A->d, U+1E0B->d, U+1E0C->d, U+1E0D->d, U+1E0E->d, U+1E0F->d, U+1E10->d, U+1E11->d, U+1E12->d, U+1E13->d, U+1E14->e, U+1E15->e, U+1E16->e, U+1E17->e, U+1E18->e, U+1E19->e, U+1E1A->e, U+1E1B->e, U+1E1C->e, U+1E1D->e, U+1E1E->f, U+1E1F->f, U+1E20->g, U+1E21->g, U+1E22->h, U+1E23->h, U+1E24->h, U+1E25->h, U+1E26->h, U+1E27->h, U+1E28->h, U+1E29->h, U+1E2A->h, U+1E2B->h, U+1E2C->i, U+1E2D->i, U+1E2E->i, U+1E2F->i, U+1E30->k, U+1E31->k, U+1E32->k, U+1E33->k, U+1E34->k, U+1E35->k, U+1E36->l, U+1E37->l, U+1E38->l, U+1E39->l, U+1E3A->l, U+1E3B->l, U+1E3C->l, U+1E3D->l, U+1E3E->m, U+1E3F->m, U+1E40->m, U+1E41->m, U+1E42->m, U+1E43->m, U+1E44->n, U+1E45->n, U+1E46->n, U+1E47->n, U+1E48->n, U+1E49->n, U+1E4A->n, U+1E4B->n, U+1E4C->o, U+1E4D->o, U+1E4E->o, U+1E4F->o, U+1E50->o, U+1E51->o, U+1E52->o, U+1E53->o, U+1E54->p, U+1E55->p, U+1E56->p, U+1E57->p, U+1E58->r, U+1E59->r, U+1E5A->r, U+1E5B->r, U+1E5C->r, U+1E5D->r, U+1E5E->r, U+1E5F->r, U+1E60->s, U+1E61->s, U+1E62->s, U+1E63->s, U+1E64->s, U+1E65->s, U+1E66->s, U+1E67->s, U+1E68->s, U+1E69->s, U+1E6A->t, U+1E6B->t, U+1E6C->t, U+1E6D->t, U+1E6E->t, U+1E6F->t, U+1E70->t, U+1E71->t, U+1E72->u, U+1E73->u, U+1E74->u, U+1E75->u, U+1E76->u, U+1E77->u, U+1E78->u, U+1E79->u, U+1E7A->u, U+1E7B->u, U+1E7C->v, U+1E7D->v, U+1E7E->v, U+1E7F->v, U+1E80->w, U+1E81->w, U+1E82->w, U+1E83->w, U+1E84->w, U+1E85->w, U+1E86->w, U+1E87->w, U+1E88->w, U+1E89->w, U+1E8A->x, U+1E8B->x, U+1E8C->x, U+1E8D->x, U+1E8E->y, U+1E8F->y, U+1E96->h, U+1E97->t, U+1E98->w, U+1E99->y, U+1EA0->a, U+1EA1->a, U+1EA2->a, U+1EA3->a, U+1EA4->a, U+1EA5->a, U+1EA6->a, U+1EA7->a, U+1EA8->a, U+1EA9->a, U+1EAA->a, U+1EAB->a, U+1EAC->a, U+1EAD->a, U+1EAE->a, U+1EAF->a, U+1EB0->a, U+1EB1->a, U+1EB2->a, U+1EB3->a, U+1EB4->a, U+1EB5->a, U+1EB6->a, U+1EB7->a, U+1EB8->e, U+1EB9->e, U+1EBA->e, U+1EBB->e, U+1EBC->e, U+1EBD->e, U+1EBE->e, U+1EBF->e, U+1EC0->e, U+1EC1->e, U+1EC2->e, U+1EC3->e, U+1EC4->e, U+1EC5->e, U+1EC6->e, U+1EC7->e, U+1EC8->i, U+1EC9->i, U+1ECA->i, U+1ECB->i, U+1ECC->o, U+1ECD->o, U+1ECE->o, U+1ECF->o, U+1ED0->o, U+1ED1->o, U+1ED2->o, U+1ED3->o, U+1ED4->o, U+1ED5->o, U+1ED6->o, U+1ED7->o, U+1ED8->o, U+1ED9->o, U+1EDA->o, U+1EDB->o, U+1EDC->o, U+1EDD->o, U+1EDE->o, U+1EDF->o, U+1EE0->o, U+1EE1->o, U+1EE2->o, U+1EE3->o, U+1EE4->u, U+1EE5->u, U+1EE6->u, U+1EE7->u, U+1EE8->u, U+1EE9->u, U+1EEA->u, U+1EEB->u, U+1EEC->u, U+1EED->u, U+1EEE->u, U+1EEF->u, U+1EF0->u, U+1EF1->u, U+1EF2->y, U+1EF3->y, U+1EF4->y, U+1EF5->y, U+1EF6->y, U+1EF7->y, U+1EF8->y, U+1EF9->y" # specify all indexed models to decrease loading time in development mode # and avoid obscurly missing models in production mode.
indexed_models:
- - Person
+ - Event
- Group
+ - Person
development:
<<: *generic
diff --git a/db/migrate/20130625071410_add_multiple_choices_field_to_questions.rb b/db/migrate/20130625071410_add_multiple_choices_field_to_questions.rb
index 2e7571bfdc..bb196519e4 100644
--- a/db/migrate/20130625071410_add_multiple_choices_field_to_questions.rb
+++ b/db/migrate/20130625071410_add_multiple_choices_field_to_questions.rb
@@ -7,6 +7,6 @@
class AddMultipleChoicesFieldToQuestions < ActiveRecord::Migration
def change
- add_column(:event_questions, :multiple_choices, :boolean, default: false)
+ add_column(:event_questions, :multiple_choices, :boolean, default: false, null: false)
end
end
diff --git a/db/migrate/20140128145128_create_translation_tables.rb b/db/migrate/20140128145128_create_translation_tables.rb
index d7f24766e7..08f93b8a55 100644
--- a/db/migrate/20140128145128_create_translation_tables.rb
+++ b/db/migrate/20140128145128_create_translation_tables.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class CreateTranslationTables < ActiveRecord::Migration
def up
CustomContent.create_translation_table!(
diff --git a/db/migrate/20140129150113_add_qualified_to_participation.rb b/db/migrate/20140129150113_add_qualified_to_participation.rb
index 70ca136529..f309974c21 100644
--- a/db/migrate/20140129150113_add_qualified_to_participation.rb
+++ b/db/migrate/20140129150113_add_qualified_to_participation.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddQualifiedToParticipation < ActiveRecord::Migration
def up
diff --git a/db/migrate/20140211092343_create_versions.rb b/db/migrate/20140211092343_create_versions.rb
index 6fc2be1659..8869011dc6 100644
--- a/db/migrate/20140211092343_create_versions.rb
+++ b/db/migrate/20140211092343_create_versions.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class CreateVersions < ActiveRecord::Migration
def self.up
create_table :versions do |t|
diff --git a/db/migrate/20140303120546_add_lockable_fields.rb b/db/migrate/20140303120546_add_lockable_fields.rb
index a5e0d0c580..047b2cfcd9 100644
--- a/db/migrate/20140303120546_add_lockable_fields.rb
+++ b/db/migrate/20140303120546_add_lockable_fields.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddLockableFields < ActiveRecord::Migration
def change
add_column :people, :failed_attempts, :integer, default: 0
diff --git a/db/migrate/20140326103939_create_additional_emails.rb b/db/migrate/20140326103939_create_additional_emails.rb
index 45cd931142..2416f54a3a 100644
--- a/db/migrate/20140326103939_create_additional_emails.rb
+++ b/db/migrate/20140326103939_create_additional_emails.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class CreateAdditionalEmails < ActiveRecord::Migration
def change
create_table :additional_emails do |t|
diff --git a/db/migrate/20140702083911_add_authentication_token_to_people.rb b/db/migrate/20140702083911_add_authentication_token_to_people.rb
index 71f0bca7dd..198f19c8bb 100644
--- a/db/migrate/20140702083911_add_authentication_token_to_people.rb
+++ b/db/migrate/20140702083911_add_authentication_token_to_people.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddAuthenticationTokenToPeople < ActiveRecord::Migration
def change
add_column :people, :authentication_token, :string
diff --git a/db/migrate/20140715093651_add_required_to_event_questions.rb b/db/migrate/20140715093651_add_required_to_event_questions.rb
index ce1b4ecc6e..5d2f9312ed 100644
--- a/db/migrate/20140715093651_add_required_to_event_questions.rb
+++ b/db/migrate/20140715093651_add_required_to_event_questions.rb
@@ -1,5 +1,12 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddRequiredToEventQuestions < ActiveRecord::Migration
def change
- add_column(:event_questions, :required, :boolean)
+ add_column(:event_questions, :required, :boolean, null: false, default: false)
end
end
diff --git a/db/migrate/20140717080643_add_representative_event_participant_count.rb b/db/migrate/20140717080643_add_representative_event_participant_count.rb
index 9286bb15fe..2d56dc3f37 100644
--- a/db/migrate/20140717080643_add_representative_event_participant_count.rb
+++ b/db/migrate/20140717080643_add_representative_event_participant_count.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddRepresentativeEventParticipantCount < ActiveRecord::Migration
def change
add_column :events, :representative_participant_count, :integer, default: 0
diff --git a/db/migrate/20140731181038_add_indexes_to_event_dates.rb b/db/migrate/20140731181038_add_indexes_to_event_dates.rb
index 1dde498c94..32818734dd 100644
--- a/db/migrate/20140731181038_add_indexes_to_event_dates.rb
+++ b/db/migrate/20140731181038_add_indexes_to_event_dates.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddIndexesToEventDates < ActiveRecord::Migration
def change
add_index(:event_dates, [:event_id, :start_at])
diff --git a/db/migrate/20140813074515_people_relations.rb b/db/migrate/20140813074515_people_relations.rb
index ad7f2c6f02..e1f3f5a597 100644
--- a/db/migrate/20140813074515_people_relations.rb
+++ b/db/migrate/20140813074515_people_relations.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2014, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class PeopleRelations < ActiveRecord::Migration
def change
create_table :people_relations do |t|
diff --git a/db/migrate/20141218140203_modify_event_counts.rb b/db/migrate/20141218140203_modify_event_counts.rb
index f9743c125e..120b1e70b8 100644
--- a/db/migrate/20141218140203_modify_event_counts.rb
+++ b/db/migrate/20141218140203_modify_event_counts.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2014, insieme Schweiz. This file is part of
-# hitobito_insieme and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_insieme.
+# https://github.com/hitobito/hitobito.
class ModifyEventCounts < ActiveRecord::Migration
def change
diff --git a/db/migrate/20150123100928_remove_other_than_descendants_or_self_group_subscriptions.rb b/db/migrate/20150123100928_remove_other_than_descendants_or_self_group_subscriptions.rb
index fa3281ed3d..a622b860f6 100644
--- a/db/migrate/20150123100928_remove_other_than_descendants_or_self_group_subscriptions.rb
+++ b/db/migrate/20150123100928_remove_other_than_descendants_or_self_group_subscriptions.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2015, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class RemoveOtherThanDescendantsOrSelfGroupSubscriptions < ActiveRecord::Migration
def up
diff --git a/db/migrate/20150210084002_convert_person_zip_to_string.rb b/db/migrate/20150210084002_convert_person_zip_to_string.rb
index 920d536b06..e8041f0dc7 100644
--- a/db/migrate/20150210084002_convert_person_zip_to_string.rb
+++ b/db/migrate/20150210084002_convert_person_zip_to_string.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2014, Pfadibewegung Schweiz. This file is part of
-# hitobito_pbs and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_pbs.
+# https://github.com/hitobito/hitobito.
class ConvertPersonZipToString < ActiveRecord::Migration
def change
diff --git a/db/migrate/20150212103138_add_anyone_may_post_to_mailing_list.rb b/db/migrate/20150212103138_add_anyone_may_post_to_mailing_list.rb
index 89ed7ff5cf..0d048b2453 100644
--- a/db/migrate/20150212103138_add_anyone_may_post_to_mailing_list.rb
+++ b/db/migrate/20150212103138_add_anyone_may_post_to_mailing_list.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2014, Pfadibewegung Schweiz. This file is part of
-# hitobito_pbs and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_pbs.
+# https://github.com/hitobito/hitobito.
class AddAnyoneMayPostToMailingList < ActiveRecord::Migration
def change
diff --git a/db/migrate/20150701123049_add_information_and_conditions_to_event_kinds.rb b/db/migrate/20150701123049_add_information_and_conditions_to_event_kinds.rb
index 8890aa0c5a..516e2367e8 100644
--- a/db/migrate/20150701123049_add_information_and_conditions_to_event_kinds.rb
+++ b/db/migrate/20150701123049_add_information_and_conditions_to_event_kinds.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2015, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddInformationAndConditionsToEventKinds < ActiveRecord::Migration
def change
add_column(:event_kinds, :general_information, :text)
diff --git a/db/migrate/20150706123121_add_signature_fields_to_events.rb b/db/migrate/20150706123121_add_signature_fields_to_events.rb
index 1b0c31b91e..78c411a2f5 100644
--- a/db/migrate/20150706123121_add_signature_fields_to_events.rb
+++ b/db/migrate/20150706123121_add_signature_fields_to_events.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2015, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddSignatureFieldsToEvents < ActiveRecord::Migration
def up
diff --git a/db/migrate/20150707053351_add_locations.rb b/db/migrate/20150707053351_add_locations.rb
index 39d0a21290..e9aa33bcbb 100644
--- a/db/migrate/20150707053351_add_locations.rb
+++ b/db/migrate/20150707053351_add_locations.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
-# hitobito_pbs and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_pbs.
+# https://github.com/hitobito/hitobito.
class AddLocations < ActiveRecord::Migration
def change
diff --git a/db/migrate/20150722094419_add_event_application_waiting_list_comment.rb b/db/migrate/20150722094419_add_event_application_waiting_list_comment.rb
index 7096408336..c37aa907c1 100644
--- a/db/migrate/20150722094419_add_event_application_waiting_list_comment.rb
+++ b/db/migrate/20150722094419_add_event_application_waiting_list_comment.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2015, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddEventApplicationWaitingListComment < ActiveRecord::Migration
def change
add_column :event_applications, :waiting_list_comment, :text
diff --git a/db/migrate/20151007090030_fix_event_kind_globalized_fields.rb b/db/migrate/20151007090030_fix_event_kind_globalized_fields.rb
index 8aa6c6db4b..448a462edd 100644
--- a/db/migrate/20151007090030_fix_event_kind_globalized_fields.rb
+++ b/db/migrate/20151007090030_fix_event_kind_globalized_fields.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2015, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class FixEventKindGlobalizedFields < ActiveRecord::Migration
def up
Event::Kind.add_translation_fields!({ general_information: :text }, migrate_data: true)
diff --git a/db/migrate/20151007121335_add_event_userstamps.rb b/db/migrate/20151007121335_add_event_userstamps.rb
index 92c76768bd..d1020ef32e 100644
--- a/db/migrate/20151007121335_add_event_userstamps.rb
+++ b/db/migrate/20151007121335_add_event_userstamps.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2015, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class AddEventUserstamps < ActiveRecord::Migration
def change
change_table :events do |t|
diff --git a/db/migrate/20151103115637_update_peoples_primary_group.rb b/db/migrate/20151103115637_update_peoples_primary_group.rb
index 1518e67b8d..d4cda21f2c 100644
--- a/db/migrate/20151103115637_update_peoples_primary_group.rb
+++ b/db/migrate/20151103115637_update_peoples_primary_group.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2015, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class UpdatePeoplesPrimaryGroup < ActiveRecord::Migration
def up
# people with no primary group and only one active role
diff --git a/db/migrate/20151125132550_regenerate_event_participant_counts.rb b/db/migrate/20151125132550_regenerate_event_participant_counts.rb
index 44203f9731..6c71b83294 100644
--- a/db/migrate/20151125132550_regenerate_event_participant_counts.rb
+++ b/db/migrate/20151125132550_regenerate_event_participant_counts.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2015, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class RegenerateEventParticipantCounts < ActiveRecord::Migration
def up
# Recalculate the counts of all events as teamers got omitted in certain cases
diff --git a/db/migrate/20151229124153_create_person_add_request_ignored_approvers.rb b/db/migrate/20151229124153_create_person_add_request_ignored_approvers.rb
index c455665161..3c05b3e3bd 100644
--- a/db/migrate/20151229124153_create_person_add_request_ignored_approvers.rb
+++ b/db/migrate/20151229124153_create_person_add_request_ignored_approvers.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class CreatePersonAddRequestIgnoredApprovers < ActiveRecord::Migration
def change
create_table :person_add_request_ignored_approvers do |t|
diff --git a/db/migrate/20151230101630_create_event_attachments.rb b/db/migrate/20151230101630_create_event_attachments.rb
index 002f6b5020..3772fdfa32 100644
--- a/db/migrate/20151230101630_create_event_attachments.rb
+++ b/db/migrate/20151230101630_create_event_attachments.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2015, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class CreateEventAttachments < ActiveRecord::Migration
def change
create_table :event_attachments do |t|
diff --git a/db/migrate/20160314124800_create_person_note.rb b/db/migrate/20160314124800_create_person_note.rb
index e9ef0aa18c..2a04afb032 100644
--- a/db/migrate/20160314124800_create_person_note.rb
+++ b/db/migrate/20160314124800_create_person_note.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
+# https://github.com/hitobito/hitobito.
class CreatePersonNote < ActiveRecord::Migration
def change
diff --git a/db/migrate/20160324134656_create_tag.rb b/db/migrate/20160324134656_create_tag.rb
index 006385abb9..f35baaf2a0 100644
--- a/db/migrate/20160324134656_create_tag.rb
+++ b/db/migrate/20160324134656_create_tag.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2016, Dachverband Schweizer Jugendparlamente. This file is part of
-# hitobito_dsj and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_dsj.
+# https://github.com/hitobito/hitobito.
class CreateTag < ActiveRecord::Migration
def change
diff --git a/db/migrate/20161019081518_rename_tags.rb b/db/migrate/20161019081518_rename_tags.rb
index 84ccf5190d..1fa9cb457f 100644
--- a/db/migrate/20161019081518_rename_tags.rb
+++ b/db/migrate/20161019081518_rename_tags.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class RenameTags < ActiveRecord::Migration
def change
rename_table :tags, :old_tags
diff --git a/db/migrate/20161019081524_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb b/db/migrate/20161019081524_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb
index 6bbd5594ea..8116864785 100644
--- a/db/migrate/20161019081524_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20161019081524_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
# This migration comes from acts_as_taggable_on_engine (originally 1)
class ActsAsTaggableOnMigration < ActiveRecord::Migration
def self.up
diff --git a/db/migrate/20161019081525_add_missing_unique_indices.acts_as_taggable_on_engine.rb b/db/migrate/20161019081525_add_missing_unique_indices.acts_as_taggable_on_engine.rb
index 4bbb042c51..19084e9801 100644
--- a/db/migrate/20161019081525_add_missing_unique_indices.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20161019081525_add_missing_unique_indices.acts_as_taggable_on_engine.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
# This migration comes from acts_as_taggable_on_engine (originally 2)
class AddMissingUniqueIndices < ActiveRecord::Migration
def self.up
diff --git a/db/migrate/20161019081526_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb b/db/migrate/20161019081526_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb
index 8edb508078..5b938a9d9a 100644
--- a/db/migrate/20161019081526_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20161019081526_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
# This migration comes from acts_as_taggable_on_engine (originally 3)
class AddTaggingsCounterCacheToTags < ActiveRecord::Migration
def self.up
diff --git a/db/migrate/20161019081527_add_missing_taggable_index.acts_as_taggable_on_engine.rb b/db/migrate/20161019081527_add_missing_taggable_index.acts_as_taggable_on_engine.rb
index 71f2d7f433..44cd4b71a9 100644
--- a/db/migrate/20161019081527_add_missing_taggable_index.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20161019081527_add_missing_taggable_index.acts_as_taggable_on_engine.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
# This migration comes from acts_as_taggable_on_engine (originally 4)
class AddMissingTaggableIndex < ActiveRecord::Migration
def self.up
diff --git a/db/migrate/20161019081528_change_collation_for_tag_names.acts_as_taggable_on_engine.rb b/db/migrate/20161019081528_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
index bfb06bc7cd..78013b3d77 100644
--- a/db/migrate/20161019081528_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20161019081528_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
# This migration comes from acts_as_taggable_on_engine (originally 5)
# This migration is added to circumvent issue #623 and have special characters
# work properly
diff --git a/db/migrate/20161019081705_migrate_old_tags.rb b/db/migrate/20161019081705_migrate_old_tags.rb
index 2184f8bd3d..3705b61182 100644
--- a/db/migrate/20161019081705_migrate_old_tags.rb
+++ b/db/migrate/20161019081705_migrate_old_tags.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class MigrateOldTags < ActiveRecord::Migration
def change
migrate_tags
diff --git a/db/migrate/20161122144630_add_cancel_participation_enabled_to_event.rb b/db/migrate/20161122144630_add_cancel_participation_enabled_to_event.rb
new file mode 100644
index 0000000000..4025def19d
--- /dev/null
+++ b/db/migrate/20161122144630_add_cancel_participation_enabled_to_event.rb
@@ -0,0 +1,12 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class AddCancelParticipationEnabledToEvent < ActiveRecord::Migration
+ def change
+ add_column :events, :applications_cancelable, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20161128120439_add_user_id_to_label_format.rb b/db/migrate/20161128120439_add_user_id_to_label_format.rb
new file mode 100644
index 0000000000..c07a86d1ee
--- /dev/null
+++ b/db/migrate/20161128120439_add_user_id_to_label_format.rb
@@ -0,0 +1,13 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class AddUserIdToLabelFormat < ActiveRecord::Migration
+ def change
+ add_column :label_formats, :person_id, :integer
+ add_column :people, :show_global_label_formats, :boolean, default: true, null: false
+ end
+end
diff --git a/db/migrate/20161129134942_add_nickname_to_label_format.rb b/db/migrate/20161129134942_add_nickname_to_label_format.rb
new file mode 100644
index 0000000000..6cf641ccd0
--- /dev/null
+++ b/db/migrate/20161129134942_add_nickname_to_label_format.rb
@@ -0,0 +1,13 @@
+# encoding: utf-8
+
+# Copyright (c) 2016, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class AddNicknameToLabelFormat < ActiveRecord::Migration
+ def change
+ add_column :label_formats, :nickname, :boolean, null: false, default: false
+ add_column :label_formats, :pp_post, :string, limit: 23 # PLZ + {18}
+ end
+end
diff --git a/db/migrate/20170103142035_change_locations_zip_code_to_strings.rb b/db/migrate/20170103142035_change_locations_zip_code_to_strings.rb
index cef88b5090..d5edbf43bc 100644
--- a/db/migrate/20170103142035_change_locations_zip_code_to_strings.rb
+++ b/db/migrate/20170103142035_change_locations_zip_code_to_strings.rb
@@ -1,3 +1,10 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, hitobito AG. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
class ChangeLocationsZipCodeToStrings < ActiveRecord::Migration
def change
change_column :locations, :zip_code, :string, null: false
diff --git a/db/migrate/20170314130759_add_event_kind_qualification_kind_grouping.rb b/db/migrate/20170314130759_add_event_kind_qualification_kind_grouping.rb
new file mode 100644
index 0000000000..0d2c73dd9c
--- /dev/null
+++ b/db/migrate/20170314130759_add_event_kind_qualification_kind_grouping.rb
@@ -0,0 +1,5 @@
+class AddEventKindQualificationKindGrouping < ActiveRecord::Migration
+ def change
+ add_column :event_kind_qualification_kinds, :grouping, :integer
+ end
+end
diff --git a/db/migrate/20170322144544_add_contact_attrs_to_event.rb b/db/migrate/20170322144544_add_contact_attrs_to_event.rb
new file mode 100644
index 0000000000..1964181868
--- /dev/null
+++ b/db/migrate/20170322144544_add_contact_attrs_to_event.rb
@@ -0,0 +1,8 @@
+class AddContactAttrsToEvent < ActiveRecord::Migration
+
+ def change
+ add_column :events, :required_contact_attrs, :string
+ add_column :events, :hidden_contact_attrs, :string
+ end
+
+end
diff --git a/db/migrate/20170404103510_change_event_contact_attrs.rb b/db/migrate/20170404103510_change_event_contact_attrs.rb
new file mode 100644
index 0000000000..e3f6617a80
--- /dev/null
+++ b/db/migrate/20170404103510_change_event_contact_attrs.rb
@@ -0,0 +1,8 @@
+class ChangeEventContactAttrs < ActiveRecord::Migration
+
+ def change
+ change_column :events, :required_contact_attrs, :text
+ change_column :events, :hidden_contact_attrs, :text
+ end
+
+end
diff --git a/db/migrate/20170404113313_add_event_questions_admin.rb b/db/migrate/20170404113313_add_event_questions_admin.rb
new file mode 100644
index 0000000000..8569123a14
--- /dev/null
+++ b/db/migrate/20170404113313_add_event_questions_admin.rb
@@ -0,0 +1,5 @@
+class AddEventQuestionsAdmin < ActiveRecord::Migration
+ def change
+ add_column :event_questions, :admin, :boolean, null: false, default: false
+ end
+end
diff --git a/db/migrate/20170410094209_add_event_display_booking_info_flag.rb b/db/migrate/20170410094209_add_event_display_booking_info_flag.rb
new file mode 100644
index 0000000000..8557c37f71
--- /dev/null
+++ b/db/migrate/20170410094209_add_event_display_booking_info_flag.rb
@@ -0,0 +1,5 @@
+class AddEventDisplayBookingInfoFlag < ActiveRecord::Migration
+ def change
+ add_column :events, :display_booking_info, :boolean, null: false, default: true
+ end
+end
diff --git a/db/migrate/20170529134942_create_notes.rb b/db/migrate/20170529134942_create_notes.rb
new file mode 100644
index 0000000000..83850d725d
--- /dev/null
+++ b/db/migrate/20170529134942_create_notes.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Dachverband Schweizer Jugendparlamente. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class CreateNotes < ActiveRecord::Migration
+ def change
+ rename_table :person_notes, :notes
+ add_column :notes, :subject_type, :string
+ rename_column :notes, :person_id, :subject_id
+
+ Note.update_all(subject_type: Person.name)
+ end
+end
diff --git a/db/migrate/20170607111148_add_people_filter_chain.rb b/db/migrate/20170607111148_add_people_filter_chain.rb
new file mode 100644
index 0000000000..43e9403559
--- /dev/null
+++ b/db/migrate/20170607111148_add_people_filter_chain.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class AddPeopleFilterChain < ActiveRecord::Migration
+ def change
+ add_column :people_filters, :filter_chain, :text
+ add_column :people_filters, :range, :string, default: 'deep'
+
+ add_column :people_filters, :created_at, :timestamp
+ add_column :people_filters, :updated_at, :timestamp
+
+ PeopleFilter.reset_column_information
+
+ PeopleFilter.find_each do |filter|
+ types = RelatedRoleType.where(relation: filter).pluck(:role_type)
+ filter.update!(range: 'deep', filter_chain: { role: { role_types: types } })
+ end
+ end
+end
diff --git a/db/migrate/20170911123243_add_invoice_models.rb b/db/migrate/20170911123243_add_invoice_models.rb
new file mode 100644
index 0000000000..28e3021ea4
--- /dev/null
+++ b/db/migrate/20170911123243_add_invoice_models.rb
@@ -0,0 +1,70 @@
+class AddInvoiceModels < ActiveRecord::Migration
+
+ def change
+ create_table :invoice_configs do |t|
+ t.integer :sequence_number, null: false, default: 1
+ t.integer :due_days, null: false, default: 30
+ t.belongs_to :group, index: true, null: false
+ t.integer :contact_id, index: true
+ t.integer :page_size, default: 15
+ t.text :address
+ t.text :payment_information
+ end
+
+ create_table :invoices do |t|
+ t.string :title, null: false
+
+ t.string :sequence_number, null: false, index: :unique
+ t.string :state, null: false, default: :draft
+ t.string :esr_number, null: false, index: :unique
+
+ t.text :description
+
+ t.string :recipient_email
+ t.text :recipient_address
+
+ t.date :sent_at
+ t.date :due_at
+
+ t.belongs_to :group, index: true, null: false
+ t.belongs_to :recipient, index: true, null: false
+
+ t.decimal :total, precision: 12, scale: 2
+
+ t.timestamps null: false
+ end
+
+ create_table :invoice_items do |t|
+ t.belongs_to :invoice, index: true, null: false
+
+ t.string :name, null: false
+ t.text :description
+
+ t.decimal :vat_rate, precision: 5, scale: 2
+ t.decimal :unit_cost, precision: 12, scale: 2, null: false
+ t.integer :count, default: 1, null: false
+ end
+
+ create_table :payments do |t|
+ t.belongs_to :invoice, index: true, null: false
+ t.decimal :amount, precision: 12, scale: 2, null: false
+ t.date :received_at, null: :false
+ end
+
+ create_table :payment_reminders do |t|
+ t.belongs_to :invoice, index: true, null: false
+ t.text :message
+ t.date :due_at, null: false
+ t.timestamps null: false
+ end
+
+ reversible do |dir|
+ dir.up do
+ ids = select_values("SELECT id FROM groups WHERE id = layer_group_id")
+ values = ids.collect { |id| "(#{id})" }.join(', ')
+ execute("INSERT INTO invoice_configs(group_id) VALUES #{values}") if ids.present?
+ end
+ end
+ end
+
+end
diff --git a/db/migrate/20171120181851_create_invoice_articles.rb b/db/migrate/20171120181851_create_invoice_articles.rb
new file mode 100644
index 0000000000..6ae54d524e
--- /dev/null
+++ b/db/migrate/20171120181851_create_invoice_articles.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+# Copyright (c) 2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class CreateInvoiceArticles < ActiveRecord::Migration
+ def change
+ create_table :invoice_articles do |t|
+ t.string :number
+ t.string :name, null: false
+ t.string :description
+ t.string :category
+ t.decimal :net_price, precision: 12, scale: 2
+ t.decimal :vat_rate, precision: 5, scale: 2
+ t.string :cost_center
+ t.string :account
+
+ t.timestamps null: false
+
+ t.index :number, unique: true
+ end
+ end
+end
diff --git a/db/migrate/20171123102231_change_invoice_articles.rb b/db/migrate/20171123102231_change_invoice_articles.rb
new file mode 100644
index 0000000000..31109592bb
--- /dev/null
+++ b/db/migrate/20171123102231_change_invoice_articles.rb
@@ -0,0 +1,19 @@
+class ChangeInvoiceArticles < ActiveRecord::Migration
+ def change
+ # Workaround because SQLite cannot add new column with null false
+ add_column(:invoice_articles, :group_id, :integer, index: true)
+ change_column(:invoice_articles, :group_id, :integer, null: false, index: true)
+
+ rename_column(:invoice_articles, :net_price, :unit_cost)
+
+ reversible do |dir|
+ dir.up do
+ change_column(:invoice_articles, :description, :text)
+ end
+
+ dir.down do
+ change_column(:invoice_articles, :description, :string)
+ end
+ end
+ end
+end
diff --git a/db/migrate/20171129105145_add_attributes_to_invoice.rb b/db/migrate/20171129105145_add_attributes_to_invoice.rb
new file mode 100644
index 0000000000..bcf7e95162
--- /dev/null
+++ b/db/migrate/20171129105145_add_attributes_to_invoice.rb
@@ -0,0 +1,8 @@
+class AddAttributesToInvoice < ActiveRecord::Migration
+ def change
+ add_column :invoice_configs, :account_number, :string
+
+ add_column :invoices, :account_number, :string
+ add_column :invoices, :address, :text
+ end
+end
diff --git a/db/migrate/20171201094234_allow_null_recipient_in_invoice.rb b/db/migrate/20171201094234_allow_null_recipient_in_invoice.rb
new file mode 100644
index 0000000000..a7bd4cb6dd
--- /dev/null
+++ b/db/migrate/20171201094234_allow_null_recipient_in_invoice.rb
@@ -0,0 +1,12 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class AllowNullRecipientInInvoice < ActiveRecord::Migration
+ def change
+ change_column_null :invoices, :recipient_id, true
+ end
+end
diff --git a/db/migrate/20171205085115_change_payments_received_at_to_not_null.rb b/db/migrate/20171205085115_change_payments_received_at_to_not_null.rb
new file mode 100644
index 0000000000..fb635d4899
--- /dev/null
+++ b/db/migrate/20171205085115_change_payments_received_at_to_not_null.rb
@@ -0,0 +1,13 @@
+# encoding: utf-8
+
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
+# hitobito and licensed under the Affero General Public License version 3
+# or later. See the COPYING file at the top-level directory or at
+# https://github.com/hitobito/hitobito.
+
+class ChangePaymentsReceivedAtToNotNull < ActiveRecord::Migration
+ def change
+ Payment.delete_all
+ change_column_null :payments, :received_at, false
+ end
+end
diff --git a/db/migrate/20171205122949_add_issued_at_to_invoices.rb b/db/migrate/20171205122949_add_issued_at_to_invoices.rb
new file mode 100644
index 0000000000..d3fa13c57e
--- /dev/null
+++ b/db/migrate/20171205122949_add_issued_at_to_invoices.rb
@@ -0,0 +1,5 @@
+class AddIssuedAtToInvoices < ActiveRecord::Migration
+ def change
+ add_column :invoices, :issued_at, :date
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8704507716..35b8cd0694 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170103142035) do
+ActiveRecord::Schema.define(version: 20171205122949) do
create_table "additional_emails", force: :cascade do |t|
t.integer "contactable_id", limit: 4, null: false
@@ -22,7 +22,7 @@
t.boolean "mailings", default: true, null: false
end
- add_index "additional_emails", ["contactable_id", "contactable_type"], name: "index_additional_emails_on_contactable_id_and_contactable_type", using: :btree
+ add_index "additional_emails", ["contactable_id", "contactable_type"], name: "index_additional_emails_on_contactable_id_and_contactable_type"
create_table "custom_content_translations", force: :cascade do |t|
t.integer "custom_content_id", limit: 4, null: false
@@ -34,8 +34,8 @@
t.text "body", limit: 65535
end
- add_index "custom_content_translations", ["custom_content_id"], name: "index_custom_content_translations_on_custom_content_id", using: :btree
- add_index "custom_content_translations", ["locale"], name: "index_custom_content_translations_on_locale", using: :btree
+ add_index "custom_content_translations", ["custom_content_id"], name: "index_custom_content_translations_on_custom_content_id"
+ add_index "custom_content_translations", ["locale"], name: "index_custom_content_translations_on_locale"
create_table "custom_contents", force: :cascade do |t|
t.string "key", limit: 255, null: false
@@ -57,7 +57,7 @@
t.datetime "updated_at"
end
- add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
+ add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority"
create_table "event_answers", force: :cascade do |t|
t.integer "participation_id", limit: 4, null: false
@@ -65,7 +65,7 @@
t.string "answer", limit: 255
end
- add_index "event_answers", ["participation_id", "question_id"], name: "index_event_answers_on_participation_id_and_question_id", unique: true, using: :btree
+ add_index "event_answers", ["participation_id", "question_id"], name: "index_event_answers_on_participation_id_and_question_id", unique: true
create_table "event_applications", force: :cascade do |t|
t.integer "priority_1_id", limit: 4, null: false
@@ -82,7 +82,7 @@
t.string "file", limit: 255, null: false
end
- add_index "event_attachments", ["event_id"], name: "index_event_attachments_on_event_id", using: :btree
+ add_index "event_attachments", ["event_id"], name: "index_event_attachments_on_event_id"
create_table "event_dates", force: :cascade do |t|
t.integer "event_id", limit: 4, null: false
@@ -92,18 +92,19 @@
t.string "location", limit: 255
end
- add_index "event_dates", ["event_id", "start_at"], name: "index_event_dates_on_event_id_and_start_at", using: :btree
- add_index "event_dates", ["event_id"], name: "index_event_dates_on_event_id", using: :btree
+ add_index "event_dates", ["event_id", "start_at"], name: "index_event_dates_on_event_id_and_start_at"
+ add_index "event_dates", ["event_id"], name: "index_event_dates_on_event_id"
create_table "event_kind_qualification_kinds", force: :cascade do |t|
t.integer "event_kind_id", limit: 4, null: false
t.integer "qualification_kind_id", limit: 4, null: false
t.string "category", limit: 255, null: false
t.string "role", limit: 255, null: false
+ t.integer "grouping"
end
- add_index "event_kind_qualification_kinds", ["category"], name: "index_event_kind_qualification_kinds_on_category", using: :btree
- add_index "event_kind_qualification_kinds", ["role"], name: "index_event_kind_qualification_kinds_on_role", using: :btree
+ add_index "event_kind_qualification_kinds", ["category"], name: "index_event_kind_qualification_kinds_on_category"
+ add_index "event_kind_qualification_kinds", ["role"], name: "index_event_kind_qualification_kinds_on_role"
create_table "event_kind_translations", force: :cascade do |t|
t.integer "event_kind_id", limit: 4, null: false
@@ -116,8 +117,8 @@
t.text "application_conditions", limit: 65535
end
- add_index "event_kind_translations", ["event_kind_id"], name: "index_event_kind_translations_on_event_kind_id", using: :btree
- add_index "event_kind_translations", ["locale"], name: "index_event_kind_translations_on_locale", using: :btree
+ add_index "event_kind_translations", ["event_kind_id"], name: "index_event_kind_translations_on_event_kind_id"
+ add_index "event_kind_translations", ["locale"], name: "index_event_kind_translations_on_locale"
create_table "event_kinds", force: :cascade do |t|
t.datetime "created_at"
@@ -137,9 +138,9 @@
t.boolean "qualified"
end
- add_index "event_participations", ["event_id", "person_id"], name: "index_event_participations_on_event_id_and_person_id", unique: true, using: :btree
- add_index "event_participations", ["event_id"], name: "index_event_participations_on_event_id", using: :btree
- add_index "event_participations", ["person_id"], name: "index_event_participations_on_person_id", using: :btree
+ add_index "event_participations", ["event_id", "person_id"], name: "index_event_participations_on_event_id_and_person_id", unique: true
+ add_index "event_participations", ["event_id"], name: "index_event_participations_on_event_id"
+ add_index "event_participations", ["person_id"], name: "index_event_participations_on_person_id"
create_table "event_questions", force: :cascade do |t|
t.integer "event_id", limit: 4
@@ -147,9 +148,10 @@
t.string "choices", limit: 255
t.boolean "multiple_choices", default: false
t.boolean "required"
+ t.boolean "admin", default: false, null: false
end
- add_index "event_questions", ["event_id"], name: "index_event_questions_on_event_id", using: :btree
+ add_index "event_questions", ["event_id"], name: "index_event_questions_on_event_id"
create_table "event_roles", force: :cascade do |t|
t.string "type", limit: 255, null: false
@@ -157,8 +159,8 @@
t.string "label", limit: 255
end
- add_index "event_roles", ["participation_id"], name: "index_event_roles_on_participation_id", using: :btree
- add_index "event_roles", ["type"], name: "index_event_roles_on_type", using: :btree
+ add_index "event_roles", ["participation_id"], name: "index_event_roles_on_participation_id"
+ add_index "event_roles", ["type"], name: "index_event_roles_on_type"
create_table "events", force: :cascade do |t|
t.string "type", limit: 255
@@ -189,16 +191,20 @@
t.string "signature_confirmation_text", limit: 255
t.integer "creator_id", limit: 4
t.integer "updater_id", limit: 4
+ t.boolean "applications_cancelable", default: false, null: false
+ t.text "required_contact_attrs"
+ t.text "hidden_contact_attrs"
+ t.boolean "display_booking_info", default: true, null: false
end
- add_index "events", ["kind_id"], name: "index_events_on_kind_id", using: :btree
+ add_index "events", ["kind_id"], name: "index_events_on_kind_id"
create_table "events_groups", id: false, force: :cascade do |t|
t.integer "event_id", limit: 4
t.integer "group_id", limit: 4
end
- add_index "events_groups", ["event_id", "group_id"], name: "index_events_groups_on_event_id_and_group_id", unique: true, using: :btree
+ add_index "events_groups", ["event_id", "group_id"], name: "index_events_groups_on_event_id_and_group_id", unique: true
create_table "groups", force: :cascade do |t|
t.integer "parent_id", limit: 4
@@ -223,9 +229,75 @@
t.boolean "require_person_add_requests", default: false, null: false
end
- add_index "groups", ["layer_group_id"], name: "index_groups_on_layer_group_id", using: :btree
- add_index "groups", ["lft", "rgt"], name: "index_groups_on_lft_and_rgt", using: :btree
- add_index "groups", ["parent_id"], name: "index_groups_on_parent_id", using: :btree
+ add_index "groups", ["layer_group_id"], name: "index_groups_on_layer_group_id"
+ add_index "groups", ["lft", "rgt"], name: "index_groups_on_lft_and_rgt"
+ add_index "groups", ["parent_id"], name: "index_groups_on_parent_id"
+
+ create_table "invoice_articles", force: :cascade do |t|
+ t.string "number", limit: 255
+ t.string "name", limit: 255, null: false
+ t.text "description", limit: 65535
+ t.string "category", limit: 255
+ t.decimal "unit_cost", precision: 12, scale: 2
+ t.decimal "vat_rate", precision: 5, scale: 2
+ t.string "cost_center", limit: 255
+ t.string "account", limit: 255
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "group_id", null: false
+ end
+
+ add_index "invoice_articles", ["number"], name: "index_invoice_articles_on_number", unique: true
+
+ create_table "invoice_configs", force: :cascade do |t|
+ t.integer "sequence_number", default: 1, null: false
+ t.integer "due_days", default: 30, null: false
+ t.integer "group_id", null: false
+ t.integer "contact_id"
+ t.integer "page_size", default: 15
+ t.text "address"
+ t.text "payment_information"
+ t.string "account_number"
+ end
+
+ add_index "invoice_configs", ["contact_id"], name: "index_invoice_configs_on_contact_id"
+ add_index "invoice_configs", ["group_id"], name: "index_invoice_configs_on_group_id"
+
+ create_table "invoice_items", force: :cascade do |t|
+ t.integer "invoice_id", null: false
+ t.string "name", null: false
+ t.text "description"
+ t.decimal "vat_rate", precision: 5, scale: 2
+ t.decimal "unit_cost", precision: 12, scale: 2, null: false
+ t.integer "count", default: 1, null: false
+ end
+
+ add_index "invoice_items", ["invoice_id"], name: "index_invoice_items_on_invoice_id"
+
+ create_table "invoices", force: :cascade do |t|
+ t.string "title", null: false
+ t.string "sequence_number", null: false
+ t.string "state", default: "draft", null: false
+ t.string "esr_number", null: false
+ t.text "description"
+ t.string "recipient_email"
+ t.text "recipient_address"
+ t.date "sent_at"
+ t.date "due_at"
+ t.integer "group_id", null: false
+ t.integer "recipient_id"
+ t.decimal "total", precision: 12, scale: 2
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "account_number"
+ t.text "address"
+ t.date "issued_at"
+ end
+
+ add_index "invoices", ["esr_number"], name: "index_invoices_on_esr_number"
+ add_index "invoices", ["group_id"], name: "index_invoices_on_group_id"
+ add_index "invoices", ["recipient_id"], name: "index_invoices_on_recipient_id"
+ add_index "invoices", ["sequence_number"], name: "index_invoices_on_sequence_number"
create_table "label_format_translations", force: :cascade do |t|
t.integer "label_format_id", limit: 4, null: false
@@ -235,8 +307,8 @@
t.string "name", limit: 255, null: false
end
- add_index "label_format_translations", ["label_format_id"], name: "index_label_format_translations_on_label_format_id", using: :btree
- add_index "label_format_translations", ["locale"], name: "index_label_format_translations_on_locale", using: :btree
+ add_index "label_format_translations", ["label_format_id"], name: "index_label_format_translations_on_label_format_id"
+ add_index "label_format_translations", ["locale"], name: "index_label_format_translations_on_locale"
create_table "label_formats", force: :cascade do |t|
t.string "page_size", limit: 255, default: "A4", null: false
@@ -248,6 +320,9 @@
t.integer "count_vertical", limit: 4, null: false
t.float "padding_top", limit: 24, null: false
t.float "padding_left", limit: 24, null: false
+ t.integer "person_id", limit: 4
+ t.boolean "nickname", default: false, null: false
+ t.string "pp_post", limit: 23
end
create_table "locations", force: :cascade do |t|
@@ -256,7 +331,7 @@
t.string "zip_code", limit: 255, null: false
end
- add_index "locations", ["zip_code", "canton", "name"], name: "index_locations_on_zip_code_and_canton_and_name", unique: true, using: :btree
+ add_index "locations", ["zip_code", "canton", "name"], name: "index_locations_on_zip_code_and_canton_and_name", unique: true
create_table "mailing_lists", force: :cascade do |t|
t.string "name", limit: 255, null: false
@@ -270,55 +345,89 @@
t.boolean "anyone_may_post", default: false, null: false
end
- add_index "mailing_lists", ["group_id"], name: "index_mailing_lists_on_group_id", using: :btree
+ add_index "mailing_lists", ["group_id"], name: "index_mailing_lists_on_group_id"
+
+ create_table "notes", force: :cascade do |t|
+ t.integer "subject_id", limit: 4, null: false
+ t.integer "author_id", limit: 4, null: false
+ t.text "text", limit: 65535
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "subject_type", limit: 255
+ end
+
+ add_index "notes", ["subject_id"], name: "index_notes_on_subject_id"
+
+ create_table "payment_reminders", force: :cascade do |t|
+ t.integer "invoice_id", null: false
+ t.text "message"
+ t.date "due_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "payment_reminders", ["invoice_id"], name: "index_payment_reminders_on_invoice_id"
+
+ create_table "payments", force: :cascade do |t|
+ t.integer "invoice_id", null: false
+ t.decimal "amount", precision: 12, scale: 2, null: false
+ t.date "received_at", null: false
+ end
+
+ add_index "payments", ["invoice_id"], name: "index_payments_on_invoice_id"
create_table "people", force: :cascade do |t|
- t.string "first_name", limit: 255
- t.string "last_name", limit: 255
- t.string "company_name", limit: 255
- t.string "nickname", limit: 255
- t.boolean "company", default: false, null: false
- t.string "email", limit: 255
- t.string "address", limit: 1024
- t.string "zip_code", limit: 255
- t.string "town", limit: 255
- t.string "country", limit: 255
- t.string "gender", limit: 1
+ t.string "first_name", limit: 255
+ t.string "last_name", limit: 255
+ t.string "company_name", limit: 255
+ t.string "nickname", limit: 255
+ t.boolean "company", default: false, null: false
+ t.string "email", limit: 255
+ t.string "address", limit: 1024
+ t.string "zip_code", limit: 255
+ t.string "town", limit: 255
+ t.string "country", limit: 255
+ t.string "gender", limit: 1
t.date "birthday"
- t.text "additional_information", limit: 65535
- t.boolean "contact_data_visible", default: false, null: false
+ t.text "additional_information", limit: 65535
+ t.boolean "contact_data_visible", default: false, null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.string "encrypted_password", limit: 255
- t.string "reset_password_token", limit: 255
+ t.string "encrypted_password", limit: 255
+ t.string "reset_password_token", limit: 255
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", limit: 4, default: 0
+ t.integer "sign_in_count", limit: 4, default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
- t.string "current_sign_in_ip", limit: 255
- t.string "last_sign_in_ip", limit: 255
- t.string "picture", limit: 255
- t.integer "last_label_format_id", limit: 4
- t.integer "creator_id", limit: 4
- t.integer "updater_id", limit: 4
- t.integer "primary_group_id", limit: 4
- t.integer "failed_attempts", limit: 4, default: 0
+ t.string "current_sign_in_ip", limit: 255
+ t.string "last_sign_in_ip", limit: 255
+ t.string "picture", limit: 255
+ t.integer "last_label_format_id", limit: 4
+ t.integer "creator_id", limit: 4
+ t.integer "updater_id", limit: 4
+ t.integer "primary_group_id", limit: 4
+ t.integer "failed_attempts", limit: 4, default: 0
t.datetime "locked_at"
- t.string "authentication_token", limit: 255
+ t.string "authentication_token", limit: 255
+ t.boolean "show_global_label_formats", default: true, null: false
end
- add_index "people", ["authentication_token"], name: "index_people_on_authentication_token", using: :btree
- add_index "people", ["email"], name: "index_people_on_email", unique: true, using: :btree
- add_index "people", ["reset_password_token"], name: "index_people_on_reset_password_token", unique: true, using: :btree
+ add_index "people", ["authentication_token"], name: "index_people_on_authentication_token"
+ add_index "people", ["email"], name: "index_people_on_email", unique: true
+ add_index "people", ["reset_password_token"], name: "index_people_on_reset_password_token", unique: true
create_table "people_filters", force: :cascade do |t|
- t.string "name", limit: 255, null: false
- t.integer "group_id", limit: 4
- t.string "group_type", limit: 255
+ t.string "name", limit: 255, null: false
+ t.integer "group_id", limit: 4
+ t.string "group_type", limit: 255
+ t.text "filter_chain", limit: 65535
+ t.string "range", limit: 255, default: "deep"
+ t.datetime "created_at"
+ t.datetime "updated_at"
end
- add_index "people_filters", ["group_id", "group_type"], name: "index_people_filters_on_group_id_and_group_type", using: :btree
+ add_index "people_filters", ["group_id", "group_type"], name: "index_people_filters_on_group_id_and_group_type"
create_table "people_relations", force: :cascade do |t|
t.integer "head_id", limit: 4, null: false
@@ -326,15 +435,15 @@
t.string "kind", limit: 255, null: false
end
- add_index "people_relations", ["head_id"], name: "index_people_relations_on_head_id", using: :btree
- add_index "people_relations", ["tail_id"], name: "index_people_relations_on_tail_id", using: :btree
+ add_index "people_relations", ["head_id"], name: "index_people_relations_on_head_id"
+ add_index "people_relations", ["tail_id"], name: "index_people_relations_on_tail_id"
create_table "person_add_request_ignored_approvers", force: :cascade do |t|
t.integer "group_id", limit: 4, null: false
t.integer "person_id", limit: 4, null: false
end
- add_index "person_add_request_ignored_approvers", ["group_id", "person_id"], name: "person_add_request_ignored_approvers_index", unique: true, using: :btree
+ add_index "person_add_request_ignored_approvers", ["group_id", "person_id"], name: "person_add_request_ignored_approvers_index", unique: true
create_table "person_add_requests", force: :cascade do |t|
t.integer "person_id", limit: 4, null: false
@@ -345,18 +454,8 @@
t.datetime "created_at", null: false
end
- add_index "person_add_requests", ["person_id"], name: "index_person_add_requests_on_person_id", using: :btree
- add_index "person_add_requests", ["type", "body_id"], name: "index_person_add_requests_on_type_and_body_id", using: :btree
-
- create_table "person_notes", force: :cascade do |t|
- t.integer "person_id", limit: 4, null: false
- t.integer "author_id", limit: 4, null: false
- t.text "text", limit: 65535
- t.datetime "created_at"
- t.datetime "updated_at"
- end
-
- add_index "person_notes", ["person_id"], name: "index_person_notes_on_person_id", using: :btree
+ add_index "person_add_requests", ["person_id"], name: "index_person_add_requests_on_person_id"
+ add_index "person_add_requests", ["type", "body_id"], name: "index_person_add_requests_on_type_and_body_id"
create_table "phone_numbers", force: :cascade do |t|
t.integer "contactable_id", limit: 4, null: false
@@ -366,7 +465,7 @@
t.boolean "public", default: true, null: false
end
- add_index "phone_numbers", ["contactable_id", "contactable_type"], name: "index_phone_numbers_on_contactable_id_and_contactable_type", using: :btree
+ add_index "phone_numbers", ["contactable_id", "contactable_type"], name: "index_phone_numbers_on_contactable_id_and_contactable_type"
create_table "qualification_kind_translations", force: :cascade do |t|
t.integer "qualification_kind_id", limit: 4, null: false
@@ -377,8 +476,8 @@
t.string "description", limit: 1023
end
- add_index "qualification_kind_translations", ["locale"], name: "index_qualification_kind_translations_on_locale", using: :btree
- add_index "qualification_kind_translations", ["qualification_kind_id"], name: "index_qualification_kind_translations_on_qualification_kind_id", using: :btree
+ add_index "qualification_kind_translations", ["locale"], name: "index_qualification_kind_translations_on_locale"
+ add_index "qualification_kind_translations", ["qualification_kind_id"], name: "index_qualification_kind_translations_on_qualification_kind_id"
create_table "qualification_kinds", force: :cascade do |t|
t.integer "validity", limit: 4
@@ -396,8 +495,8 @@
t.string "origin", limit: 255
end
- add_index "qualifications", ["person_id"], name: "index_qualifications_on_person_id", using: :btree
- add_index "qualifications", ["qualification_kind_id"], name: "index_qualifications_on_qualification_kind_id", using: :btree
+ add_index "qualifications", ["person_id"], name: "index_qualifications_on_person_id"
+ add_index "qualifications", ["qualification_kind_id"], name: "index_qualifications_on_qualification_kind_id"
create_table "related_role_types", force: :cascade do |t|
t.integer "relation_id", limit: 4
@@ -405,8 +504,8 @@
t.string "relation_type", limit: 255
end
- add_index "related_role_types", ["relation_id", "relation_type"], name: "index_related_role_types_on_relation_id_and_relation_type", using: :btree
- add_index "related_role_types", ["role_type"], name: "index_related_role_types_on_role_type", using: :btree
+ add_index "related_role_types", ["relation_id", "relation_type"], name: "index_related_role_types_on_relation_id_and_relation_type"
+ add_index "related_role_types", ["role_type"], name: "index_related_role_types_on_role_type"
create_table "roles", force: :cascade do |t|
t.integer "person_id", limit: 4, null: false
@@ -418,8 +517,8 @@
t.datetime "deleted_at"
end
- add_index "roles", ["person_id", "group_id"], name: "index_roles_on_person_id_and_group_id", using: :btree
- add_index "roles", ["type"], name: "index_roles_on_type", using: :btree
+ add_index "roles", ["person_id", "group_id"], name: "index_roles_on_person_id_and_group_id"
+ add_index "roles", ["type"], name: "index_roles_on_type"
create_table "sessions", force: :cascade do |t|
t.string "session_id", limit: 255, null: false
@@ -428,8 +527,8 @@
t.datetime "updated_at"
end
- add_index "sessions", ["session_id"], name: "index_sessions_on_session_id", using: :btree
- add_index "sessions", ["updated_at"], name: "index_sessions_on_updated_at", using: :btree
+ add_index "sessions", ["session_id"], name: "index_sessions_on_session_id"
+ add_index "sessions", ["updated_at"], name: "index_sessions_on_updated_at"
create_table "social_accounts", force: :cascade do |t|
t.integer "contactable_id", limit: 4, null: false
@@ -439,7 +538,7 @@
t.boolean "public", default: true, null: false
end
- add_index "social_accounts", ["contactable_id", "contactable_type"], name: "index_social_accounts_on_contactable_id_and_contactable_type", using: :btree
+ add_index "social_accounts", ["contactable_id", "contactable_type"], name: "index_social_accounts_on_contactable_id_and_contactable_type"
create_table "subscriptions", force: :cascade do |t|
t.integer "mailing_list_id", limit: 4, null: false
@@ -448,8 +547,8 @@
t.boolean "excluded", default: false, null: false
end
- add_index "subscriptions", ["mailing_list_id"], name: "index_subscriptions_on_mailing_list_id", using: :btree
- add_index "subscriptions", ["subscriber_id", "subscriber_type"], name: "index_subscriptions_on_subscriber_id_and_subscriber_type", using: :btree
+ add_index "subscriptions", ["mailing_list_id"], name: "index_subscriptions_on_mailing_list_id"
+ add_index "subscriptions", ["subscriber_id", "subscriber_type"], name: "index_subscriptions_on_subscriber_id_and_subscriber_type"
create_table "taggings", force: :cascade do |t|
t.integer "tag_id", limit: 4
@@ -461,15 +560,15 @@
t.datetime "created_at"
end
- add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree
- add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
+ add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true
+ add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context"
create_table "tags", force: :cascade do |t|
t.string "name", limit: 255
t.integer "taggings_count", limit: 4, default: 0
end
- add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
+ add_index "tags", ["name"], name: "index_tags_on_name", unique: true
create_table "versions", force: :cascade do |t|
t.string "item_type", limit: 255, null: false
@@ -483,7 +582,7 @@
t.datetime "created_at"
end
- add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id", using: :btree
- add_index "versions", ["main_id", "main_type"], name: "index_versions_on_main_id_and_main_type", using: :btree
+ add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id"
+ add_index "versions", ["main_id", "main_type"], name: "index_versions_on_main_id_and_main_type"
end
diff --git a/db/seeds/custom_contents.rb b/db/seeds/custom_contents.rb
index fb79492a54..11e5fc2102 100644
--- a/db/seeds/custom_contents.rb
+++ b/db/seeds/custom_contents.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
-# Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
+# Copyright (c) 2012-2017, Jungwacht Blauring Schweiz. This file is part of
# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
@@ -18,6 +18,10 @@
placeholders_required: 'participant-name, event-details, application-url',
placeholders_optional: 'recipient-names'},
+ {key: Event::ParticipationMailer::CONTENT_CANCEL,
+ placeholders_required: 'event-details',
+ placeholders_optional: 'recipient-name'},
+
{key: Event::RegisterMailer::CONTENT_REGISTER_LOGIN,
placeholders_required: 'event-url',
placeholders_optional: 'recipient-name, event-name'},
@@ -41,17 +45,43 @@
{ key: Person::AddRequestMailer::CONTENT_ADD_REQUEST_REJECTED,
placeholders_required: 'person-name, request-body',
placeholders_optional: 'recipient-name, rejecter-name, rejecter-roles' },
+
+ { key: Export::SubscriptionsMailer::CONTENT_SUBSCRIPTIONS_EXPORT,
+ placeholders_required: nil,
+ placeholders_optional: 'recipient-name, mailing-list-name' },
+
+ { key: Export::PeopleExportMailer::CONTENT_PEOPLE_EXPORT,
+ placeholders_required: nil,
+ placeholders_optional: 'recipient-name' },
+
+ { key: Export::EventsExportMailer::CONTENT_EVENTS_EXPORT,
+ placeholders_required: nil,
+ placeholders_optional: 'recipient-name' },
+
+ { key: Export::EventParticipationsExportMailer::CONTENT_EVENT_PARTICIPATIONS_EXPORT,
+ placeholders_required: nil,
+ placeholders_optional: 'recipient-name' },
+
+ { key: InvoiceMailer::CONTENT_INVOICE_NOTIFICATION,
+ placeholders_required: 'invoice-items, invoice-total, payment-information',
+ placeholders_optional: 'recipient-name, group-name, invoice-number' },
)
send_login_id = CustomContent.get(Person::LoginMailer::CONTENT_LOGIN).id
participation_confirmation_id = CustomContent.get(Event::ParticipationMailer::CONTENT_CONFIRMATION).id
participation_approval_id = CustomContent.get(Event::ParticipationMailer::CONTENT_APPROVAL).id
+cancel_application_id = CustomContent.get(Event::ParticipationMailer::CONTENT_CANCEL).id
temp_login_id = CustomContent.get(Event::RegisterMailer::CONTENT_REGISTER_LOGIN).id
login_form_id = CustomContent.get('views/devise/sessions/info').id
add_request_person_id = CustomContent.get(Person::AddRequestMailer::CONTENT_ADD_REQUEST_PERSON).id
add_request_responsibles_id = CustomContent.get(Person::AddRequestMailer::CONTENT_ADD_REQUEST_RESPONSIBLES).id
add_request_approved_id = CustomContent.get(Person::AddRequestMailer::CONTENT_ADD_REQUEST_APPROVED).id
add_request_rejected_id = CustomContent.get(Person::AddRequestMailer::CONTENT_ADD_REQUEST_REJECTED).id
+subscriptions_export_id = CustomContent.get(Export::SubscriptionsMailer::CONTENT_SUBSCRIPTIONS_EXPORT).id
+people_export_id = CustomContent.get(Export::PeopleExportMailer::CONTENT_PEOPLE_EXPORT).id
+events_export_id = CustomContent.get(Export::EventsExportMailer::CONTENT_EVENTS_EXPORT).id
+event_participations_export_id = CustomContent.get(Export::EventParticipationsExportMailer::CONTENT_EVENT_PARTICIPATIONS_EXPORT).id
+invoice_notification_id = CustomContent.get(InvoiceMailer::CONTENT_INVOICE_NOTIFICATION).id
CustomContent::Translation.seed_once(:custom_content_id, :locale,
@@ -102,6 +132,26 @@
locale: 'it',
label: "Evento: E-mail per l'affermazione della inscrizione"},
+ {custom_content_id: cancel_application_id,
+ locale: 'de',
+ label: 'Anlass: E-Mail Abmeldebestätigung',
+ subject: 'Bestätigung der Abmeldung',
+ body: "Hallo {recipient-name} " \
+ "Du hast dich von folgendem Anlass abgemeldet: " \
+ "{event-details} "},
+
+ {custom_content_id: cancel_application_id,
+ locale: 'fr',
+ label: "Événement: E-Mail de confirmation de la désinscription"},
+
+ {custom_content_id: cancel_application_id,
+ locale: 'en',
+ label: 'Event: Deregistration confirmation email'},
+
+ {custom_content_id: cancel_application_id,
+ locale: 'it',
+ label: "Evento: E-mail per l'affermazione della disinscrizione"},
+
{custom_content_id: participation_approval_id,
locale: 'de',
label: 'Anlass: E-Mail Freigabe der Anmeldung',
@@ -253,4 +303,104 @@
locale: 'it',
label: "Richiesta dei dati personali: Email abilitazione rifiutata"},
+ {custom_content_id: subscriptions_export_id,
+ locale: 'de',
+ label: 'Export der Abonnenten',
+ subject: 'Export der Abonnenten',
+ body: "Hallo {recipient-name} " \
+ "Der Export der Abonnenten von {mailing-list-name} ist fertig und an dieser Mail angehängt. " },
+
+ {custom_content_id: subscriptions_export_id,
+ locale: 'en',
+ label: 'Export of Mailinglist' },
+
+ {custom_content_id: subscriptions_export_id,
+ locale: 'fr',
+ label: 'Export der Abonnenten' },
+
+ {custom_content_id: subscriptions_export_id,
+ locale: 'it',
+ label: 'Export der Abonnenten' },
+
+ {custom_content_id: people_export_id,
+ locale: 'de',
+ label: 'Export der Personen',
+ subject: 'Export der Personen',
+ body: "Hallo {recipient-name} " \
+ "Der Export der Personen ist fertig und an dieser Mail angehängt. " },
+
+ {custom_content_id: people_export_id,
+ locale: 'en',
+ label: 'Export of People' },
+
+ {custom_content_id: people_export_id,
+ locale: 'fr',
+ label: 'Export der Personen' },
+
+ {custom_content_id: people_export_id,
+ locale: 'it',
+ label: 'Export der Personen' },
+
+ {custom_content_id: events_export_id,
+ locale: 'de',
+ label: 'Export der Anlässe',
+ subject: 'Export der Anlässe',
+ body: "Hallo {recipient-name} " \
+ "Der Export der Anlässe ist fertig und an dieser Mail angehängt. " },
+
+ {custom_content_id: events_export_id,
+ locale: 'en',
+ label: 'Export of Events' },
+
+ {custom_content_id: events_export_id,
+ locale: 'fr',
+ label: 'Export der Anlässe' },
+
+ {custom_content_id: events_export_id,
+ locale: 'it',
+ label: 'Export der Anlässe' },
+
+ {custom_content_id: event_participations_export_id,
+ locale: 'de',
+ label: 'Export der Event-Teilnehmer',
+ subject: 'Export der Event-Teilnehmer',
+ body: "Hallo {recipient-name} " \
+ "Der Export der Event-Teilnehmer ist fertig und an dieser Mail angehängt. " },
+
+ {custom_content_id: event_participations_export_id,
+ locale: 'en',
+ label: 'Export of Event Participants' },
+
+ {custom_content_id: event_participations_export_id,
+ locale: 'fr',
+ label: 'Export der Event-Teilnehmer' },
+
+ {custom_content_id: event_participations_export_id,
+ locale: 'it',
+ label: 'Export der Event-Teilnehmer' },
+
+ {custom_content_id: invoice_notification_id,
+ locale: 'de',
+ label: 'Rechnung',
+ subject: 'Rechnung {invoice-number} von {group-name}',
+ body: " Hallo {recipient-name}
" \
+ "Rechnung von:
" \
+ "Absender: Verband, Verbandstrasse 23, 3000 Verbandort
" \
+ " " \
+ "{invoice-items} " \
+ "{invoice-total} " \
+ "{payment-information} " },
+
+ {custom_content_id: invoice_notification_id,
+ locale: 'en',
+ label: 'Rechnung' },
+
+ {custom_content_id: invoice_notification_id,
+ locale: 'fr',
+ label: 'Rechnung' },
+
+ {custom_content_id: invoice_notification_id,
+ locale: 'it',
+ label: 'Rechnung' },
+
)
diff --git a/db/seeds/support/event_seeder.rb b/db/seeds/support/event_seeder.rb
index 7a986a3d01..650c659e6d 100644
--- a/db/seeds/support/event_seeder.rb
+++ b/db/seeds/support/event_seeder.rb
@@ -39,7 +39,7 @@ def seed_base_event(values)
seed_questions(event) if true?
seed_leaders(event)
3.times do
- event.class.participant_types.each do |type|
+ event.participant_types.each do |type|
seed_event_role(event, type)
end
end
@@ -104,7 +104,7 @@ def seed_leaders(event)
def seed_participants(event)
3.times do
- event.class.participant_types.each do |type|
+ event.participant_types.each do |type|
p = seed_event_role(event, type)
seed_application(p)
end
diff --git a/db/seeds/support/group_seeder.rb b/db/seeds/support/group_seeder.rb
index 81245e2d76..61c986743e 100644
--- a/db/seeds/support/group_seeder.rb
+++ b/db/seeds/support/group_seeder.rb
@@ -4,9 +4,9 @@
class GroupSeeder
def group_attributes
- { short_name: ('A'..'Z').to_a.sample(2).join,
+ {
address: Faker::Address.street_address,
- zip_code: Faker::Address.zip,
+ zip_code: Faker::Address.zip_code[0..3],
town: Faker::Address.city,
email: Faker::Internet.safe_email
}
@@ -37,4 +37,4 @@ def seed_social_accounts(group)
public: true }
)
end
-end
\ No newline at end of file
+end
diff --git a/db/seeds/support/location_seeder.rb b/db/seeds/support/location_seeder.rb
index f851f881d5..f4f920559f 100644
--- a/db/seeds/support/location_seeder.rb
+++ b/db/seeds/support/location_seeder.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
# Copyright (c) 2012-2015, Pfadibewegung Schweiz. This file is part of
-# hitobito_pbs and licensed under the Affero General Public License version 3
+# hitobito and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
-# https://github.com/hitobito/hitobito_pbs.
+# https://github.com/hitobito/hitobito.
require 'csv'
diff --git a/doc/README.md b/doc/README.md
index c87e3027b2..635310f1e9 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -4,6 +4,7 @@ Folgende Dokumentationen sind hier zu finden:
* [Architektur](architecture/README.md)
* [Entwicklung](development/README.md)
+* [E-Mail](e-mail/README.md)
Um die Code Dokumentation zu generieren, kann `rake doc:app` ausgeführt werden.
diff --git a/doc/architecture/08_konzepte.md b/doc/architecture/08_konzepte.md
index 8b058a5c4c..d0fa155607 100644
--- a/doc/architecture/08_konzepte.md
+++ b/doc/architecture/08_konzepte.md
@@ -33,7 +33,6 @@ Qualifikationseigenschaften.
haben und dadurch ebenfalls als E-Mail Liste verwendet werden können. Einzelne Personen, jedoch auch
bestimmte Rollen einer Gruppe oder Teilnehmende eines Events können Abonnenten sein.
-
### Wagons
Die Applikation ist aufgeteilt in Core (generischer Teil) und Wagons (Verbandsspezifische
@@ -44,7 +43,6 @@ werden soll, können einfach weitere Wagons erstellt werden.
In einem Wagon können Tabellen um weitere Attribute ergänzt werden, Funktionalitäten, Berechtigungen
und Darstellungen angepasst und hinzugefügt werden.
-
### Gruppen- und Rollentypen
Hitobito verfügt über ein mächtiges Metamodell um Gruppenstrukturen zu beschreiben. Gruppen sind
@@ -238,26 +236,7 @@ oder nicht. Sonst ist sie immer aktiv.
### Mailing Listen / Abos
-Hitobito stellt eine simple Implementation von Mailing Listen zur Verfügung. Diese können in der
-Applikation beliebig erstellt und verwaltet werden. Dies geschieht in den Modellen `MailingList`
-und `Subscription`.
-
-Alle E-Mails an die Applikationsdomain (z.B `news@db.jubla.ch`) werden über einen Catch-All Mail
-Account gesammelt. Dabei muss der Mailserver den zusätzlichen E-Mail Header `X-Envelope-To` setzen,
-welcher den ursprünglichen Empfänger enthält (z.B. `news`). Von der Applikation wird dieser Account
-in einem Background Job über POP3 regelmässig gepollt. Die eingetroffenen E-Mails werden danach wie
-folgt verarbeitet:
-
-1. Verwerfe das Email, falls der Empfänger keine definierte Mailing Liste ist.
-1. Sende eine Rückweisungsemail, falls der Absender nicht berechtigt ist.
-1. Leite das Email weiter an alle Empfänger der Mailing Liste.
-
-Die Berechtigung, um auf eine Mailing Liste zu schreiben, kann konfiguriert werden. Der Absender
-wird über seine Haupt- oder zusätzlichen E-Mail Adressen identifiziert. Standardmässig können alle
-Personen, welche die Liste Bearbeiten können, sowie die Gruppe, welcher das Abo gehört, E-Mails
-schreiben. Optional können zusätzlich spezifische E-Mail Adressen, alle Abonnenten der Gruppe oder
-beliebige Absender (auch nicht in hitobito erfasste) berechtigt werden.
-
+siehe [Mailing Listen / Abos](https://github.com/hitobito/hitobito/tree/master/doc/e-mail)
### Single Table Inheritance
diff --git a/doc/development/01_setup.md b/doc/development/01_setup.md
index c4671df9f2..939cccff69 100644
--- a/doc/development/01_setup.md
+++ b/doc/development/01_setup.md
@@ -38,7 +38,7 @@ Dazu muss Git installiert sein.
cp hitobito/Gemfile.lock hitobito_[wagon]/
-Siehe [Wagon erstellen](#wagon-erstellen), wenn du frisch startest und einen Wagon für eine neue
+Siehe [Wagon erstellen](04_wagons.md#wagon-erstellen), wenn du frisch startest und einen Wagon für eine neue
Organisation erstellen willst.
@@ -59,7 +59,11 @@ Initialisieren der Datenbank, laden der Seeds und Wagons:
Starten des Entwicklungsservers:
rails server
-
+
+oder gleich aller wichtigen Prozesse:
+
+ gem install foreman
+ foreman start
### Tests
@@ -129,6 +133,11 @@ Achtung: Der Index wird grundsätzlich nur über diesen Aufruf aktualisiert! Än
werden für die Volltextsuche also erst sichtbar, wenn wieder neu indexiert wurde. Auf der Produktion
läuft dazu alle 10 Minuten ein Delayed Job.
+Hinweis: Falls beim Indexieren der Fehler ``ERROR: index 'group_core': sql_fetch_row: Out of sort memory, consider increasing server sort buffer size.`` auftritt, muss in der MySql-Konfiguration (je nach Distro im File ``/etc/mysql/mysql.conf.d/mysqld.cnf`` oder ``/etc/mysql/my.cnf``) folgende Buffergrösse erhöht werden:
+
+ [mysqld]
+ sort_buffer_size = 2M
+
### Delayed Job
@@ -165,64 +174,3 @@ und dann mittles Browser auf `http://localhost:1080` E-Mails liest.
| `rake ci:nightly` | Führt die Tasks für einen Nightly Build aus. |
| `rake ci:wagon` | Führt die Tasks für die Wagon Commit Builds aus. |
| `rake ci:wagon:nightly` | Führt die Tasks für die Wagon Nightly Builds aus. |
-
-
-### Wagon erstellen
-
-Um für hitobito eine Gruppenstruktur zu defnieren, die Grundfunktionaliäten zu erweitern oder
-gewisse Features für mehrere Organisationen gemeinsam verfügbar zu machen, können Wagons verwendet
-werden. Siehe dazu auch die Wagon Guidelines. Die Grundstruktur eines neuen Wagons kann sehr
-einfach im Hauptprojekt generiert werden (Die Templates dazu befinden sich in `lib/templates/wagon`):
-
- rails generate wagon [name]
-
-Danach müssen noch folgende spezifischen Anpassungen gemacht werden:
-
-* Dateien von `hitobito/vendor/wagons/[name]` nach `hitobito_[name]` verschieben.
-* Eigenes Git Repo für den Wagon erzeugen.
-* `Gemfile.lock` vom Core in den Wagon kopieren.
-* Organisation im Lizenz Generator (`lib/tasks/license.rake`) anpassen und überall Lizenzen
- hinzufügen: `rake app:license:insert`.
-* Organisation in `COPYING` ergänzen.
-* `AUTHORS` ergänzen.
-* In `hitobito_[name].gemspec` authors, email, summary und description anpassen.
-
-Falls der Wagon für eine neue Organisation ist, können noch diese Punkte angepasst werden:
-
-* In den Seeddaten Entwickler- und Kundenaccount hinzufügen: `db/seed/development/1_people.rb` unter `devs`.
-* Die gewünschte E-Mail des Root Users in `config/settings.yml` eintragen.
-* Falls die Applikation mehrsprachig sein soll: Transifex Projekt erstellen und vorbereiten.
- Siehe dazu auch die Mehrsprachigkeits Guidelines.
-
-Falls der Wagon nicht für eine spezifische Organisation ist und keine Gruppenstruktur definiert,
-sollten folgende generierten Dateien gelöscht werden:
-
-* Gruppen Models: `rm -rf app/models/group/root.rb app/models/[name]/group.rb`
-* Übersetzungen der Models in `config/locales/models.[name].de.yml`
-* Seeddaten: `rm -rf db/seeds`
-
-Damit entsprechende Testdaten für Tests sowie Tarantula vorhanden sind, müssen die Fixtures im Wagon entsprechend der generierten Organisationsstruktur angepasst werden.
-* Anpassen der Fixtures für people, groups, roles, events, usw. (`spec/fixtures`)
-* Anpassen der Tarantula Tests im Wagon (`test/tarantula/tarantula_test.rb`)
-
-### Gruppenstruktur erstellen
-
-Nachdem für eine Organisation ein neuer Wagon erstellt worden ist, muss oft auch eine
-Gruppenstruktur definiert werden. Wie die entsprechenden Modelle aufgebaut sind, ist in der
-Architekturdokumentation beschrieben. Hier die einzelnen Schritte, welche für das Aufsetzen der
-Entwicklungsumgebung noch vorgenommen werden müssen:
-
-* Am Anfang steht die alleroberste Gruppe. Die Klasse in `app/models/group/root.rb` entsprechend
- umbenennen (z.B. nach "Dachverband") und erste Rollen definieren.
-* `app/models/[name]/group.rb#root_types` entsprechend anpassen.
-* In `config/locales/models.[name].de.yml` Übersetzungen für Gruppe und Rollen hinzufügen.
-* In `db/seed/development/1_people.rb` die Admin Rolle für die Entwickler anpassen.
-* In `db/seed/groups.rb` den Seed der Root Gruppe anpassen.
-* In `spec/fixtures/groups.yml` den Typ der Root Gruppe anpassen.
-* In `spec/fixtures/roles.yml` die Rollentypen anpassen.
-* Tests ausführen
-* Weitere Gruppen und Rollen inklusive Übersetzungen definieren.
-* In `db/seed/development/0_groups.rb` Seed Daten für die definierten Gruppentypen definieren.
-* In `spec/fixtures/groups.yml` Fixtures für die definierten Gruppentypen definieren. Es empfielt
- sich, die selben Gruppen wie in den Development Seeds zu verwenden.
-* `README.md` mit Output von `rake app:hitobito:roles` ergänzen.
diff --git a/doc/development/03_guidelines.md b/doc/development/03_guidelines.md
index 27fb686640..a7059f0f38 100644
--- a/doc/development/03_guidelines.md
+++ b/doc/development/03_guidelines.md
@@ -13,55 +13,6 @@ Violations sind unmittelbar zu korrigieren.
Das selbe gilt für Warnungen, welche im Jenkins auftreten (Brakeman, ...).
-### Wagons
-
-Die Applikation ist aufgeteilt in Core (generischer Teil) und Wagon (Verbandsspezifische
-Erweiterungen). Im Development und Production Mode sind jeweils beide Teile geladen, in den Tests
-nur der Core bzw. in den Wagon Tests der Core und der spezifische Wagon. Dies wird über das Gemfile
-gesteuert. Zur Funktionsweise von Wagons allgemein siehe auch
-[wagons](http://github.com/codez/wagons).
-
-Einige grundlegende Dinge, welche in Zusammenhang mit Wagons zu beachten sind:
-
-* Der hitobito Core und alle Wagon Verzeichnisse müssen im gleichen Haupverzeichnis sein.
-* Zu Entwicklung kann die Datei `Wagonfile.ci` nach `Wagonfile` kopiert werden, um alle Wagons in
-benachbarten Verzeichnissen zu laden. Falls nur bestimmte Wagons aktiviert werden sollen, kann dies
-ebenfalls im `Wagonfile` konfiguriert werden.
-* Wagons verwenden die gleiche Datenbank wie der Core. Wenn im Core Migrationen erstellt werden,
-müssen alle Wagon Migrationen daraus entfernt werden, bevor das `schema.rb` generiert werden kann.
-Dies geht am einfachsten, indem die development Datenbank komplett gelöscht und wiederhergestellt
-wird.
-* Wenn neue Gems zum Core hinzugefügt werden, müssen alle `Gemfile.lock` Dateien in den Wagons
-aktualisert werden. Dies geschieht am einfachsten mit `rake wagon:bundle:update`, oder manuell mit
-`cp Gemfile.lock ../hitobito_[wagon]/`. Dasselbe gilt, wenn Gems beim Umstellen einer Wagon Version
-nicht mehr passen. Das `Gemfile.lock` eines Wagons wird NIE ins Git eingecheckt.
-* Ein neuer Wagon kann mit `rails g wagon [name]` erstellt werden. Danach sollte dieser von
-`vendor/wagons` in ein benachbartes Verzeichnis des Cores verschoben werden und die Datei
-`app_root.rb` des Wagons entsprechend angepasst werden.
-
-
-#### Entwickeln für mehrere Verbände/Instanzen
-
-Es kann immer nur ein 'Haupt'-Wagon aktiv sein, welcher die Verbandsstruktur definiert. Um zwischen
-verschiedenen aktiven Verbänden zu wechseln, empfiehlt sich das Speichern der einzelnen Development
-Datenbanken, damit die jeweiligen Seed Daten nicht immer neu geladen werden müssen (Diese Files
-nicht ins Git einchecken!). Danach erfolgt die Umstellung von einer Konfiguration auf die andere:
-
-1. Alle aktiven Prozesse (Server, Console, ...) stoppen.
-1. Im `Wagonfile` den [new wagon] aktivieren, andere auskommentieren.
-1. `cp db/development-[new_wagon].sqlite3 db/development.sqlite3`
-1. `rm -rf tmp/cache` (Falls customized CSS vorhanden).
-1. Prozesse (Server, ...) wieder starten.
-
-Falls `spring` im Einsatz ist, muss vor dem Wechsel `spring stop` ausgeführt werden.
-
-#### Stylesheets in allen Wagons überprüfen
-
-Wenn an den Core Stylesheets Anpassungen vorgenommen werden, müssen diese bei allen Wagons,
-insbesondere denjenigen mit customized Styles (z.B. Jubla) überprüft werden, damit die auch dort
-funktionieren.
-
-
### Spezifische Guidelines
Allgemeine Konventionen und Erklärungen für spezifische Bereiche.
@@ -96,7 +47,14 @@ Action?
* Sind in jedem Fall die richtigen Menu Items als aktiv markiert?
* Sind in allen Texten Gendergerechte Bezeichnungen, falls nötig in der Form "/-in" verwendet?
-#### Checkliste für neue Attribute
+#### Stylesheets in allen Wagons überprüfen
+
+Wenn an den Core Stylesheets Anpassungen vorgenommen werden, müssen diese bei allen Wagons,
+insbesondere denjenigen mit customized Styles (z.B. Jubla) überprüft werden, damit die auch dort
+funktionieren.
+
+
+### Checkliste für neue Attribute
Folgende Punkte sind zu berücksichtigen, wenn neue Attribute zu Hitobito Modellen hinzugefügt
werden. Da hitobito über diverse Schnittstellen verfügt, gehen beim Definieren von Attributen rasch
@@ -116,13 +74,15 @@ sowie im JSON API die selben Regeln. Ist also z.B. ein Attribut öffentlich, wir
und in der JSON Personen Liste angezeigt, wenn nicht, nur im Full CSV und im Einzelperson JSON,
falls die Berechtigung dafür vorhanden ist.
-##### Personenattribute
+Ein beispielhafte Anleitung, wie in einem Wagon Attribute hinzugefügt werden können, findest du im Kapitel [Wagons](04_wagons.md#attribute-hinzuf-gen).
+
+#### Personenattribute
* CSV Import
* CSV Export (Adressexport? Voller Export?)
* Log (Papertrail)
-##### Rollen umbennen / entfernen
+#### Rollen umbennen / entfernen
* Migration aller betroffenen `Role` Instanzen (`with_deleted`!).
* Migration aller betroffenen `RelatedRoleType` Instanzen.
diff --git a/doc/development/04_wagons.md b/doc/development/04_wagons.md
new file mode 100644
index 0000000000..6b52b6c71b
--- /dev/null
+++ b/doc/development/04_wagons.md
@@ -0,0 +1,321 @@
+## Wagons
+
+Hitobito ist aufgeteilt in Core (generischer Teil) und Wagon(s) (Verbandsspezifische Erweiterungen). Um eine Gruppenstruktur zu definieren, das Verhalten der Applikation auf benutzerspezifische Bedürfnisse anzupassen oder gewisse Features für mehrere Organisationen gemeinsam verfügbar zu machen, können Wagons verwendet werden. Damit Hitobito lauffähig ist, wird mindestens ein Wagon mit einer Gruppenstruktur benötigt. Jeder Wagon wird in einem eigenen Git Repo verwaltet.
+
+### Grundlegendes
+
+Im Development und Production Mode sind jeweils beide Teile geladen, in den Tests
+nur der Core bzw. in den Wagon Tests der Core und der spezifische Wagon. Dies wird über das Gemfile
+gesteuert. Zur Funktionsweise von Wagons allgemein siehe auch
+[wagons](http://github.com/codez/wagons).
+
+
+Einige grundlegende Dinge, welche in Zusammenhang mit Wagons zu beachten sind:
+
+* Der hitobito Core und alle Wagon Verzeichnisse müssen im gleichen Haupverzeichnis sein.
+* Zu Entwicklung kann die Datei `Wagonfile.ci` nach `Wagonfile` kopiert werden, um alle Wagons in
+benachbarten Verzeichnissen zu laden. Falls nur bestimmte Wagons aktiviert werden sollen, kann dies
+ebenfalls im `Wagonfile` konfiguriert werden.
+* Wagons verwenden die gleiche Datenbank wie der Core. Wenn im Core Migrationen erstellt werden,
+müssen alle Wagon Migrationen daraus entfernt werden, bevor das `schema.rb` generiert werden kann.
+Dies geht am einfachsten, indem die development Datenbank komplett gelöscht und wiederhergestellt
+wird.
+* Wenn neue Gems zum Core hinzugefügt werden, müssen alle `Gemfile.lock` Dateien in den Wagons
+aktualisert werden. Dies geschieht am einfachsten mit `rake wagon:bundle:update`, oder manuell mit
+`cp Gemfile.lock ../hitobito_[wagon]/`. Dasselbe gilt, wenn Gems beim Umstellen einer Wagon Version
+nicht mehr passen. Das `Gemfile.lock` eines Wagons wird NIE ins Git eingecheckt.
+* Ein neuer Wagon kann mit `rails g wagon [name]` erstellt werden. Danach sollte dieser von
+`vendor/wagons` in ein benachbartes Verzeichnis des Cores verschoben werden und die Datei
+`app_root.rb` des Wagons entsprechend angepasst werden.
+
+
+### Entwickeln für mehrere Verbände/Instanzen
+
+Es kann immer nur ein 'Haupt'-Wagon aktiv sein, welcher die Verbandsstruktur definiert. Um zwischen
+verschiedenen aktiven Verbänden zu wechseln, empfiehlt sich das Speichern der einzelnen Development
+Datenbanken, damit die jeweiligen Seed Daten nicht immer neu geladen werden müssen (Diese Files
+nicht ins Git einchecken!). Danach erfolgt die Umstellung von einer Konfiguration auf die andere:
+
+1. Alle aktiven Prozesse (Server, Console, ...) stoppen.
+1. Im `Wagonfile` den [new wagon] aktivieren, andere auskommentieren.
+1. `cp db/development-[new_wagon].sqlite3 db/development.sqlite3`
+1. `rm -rf tmp/cache` (Falls customized CSS vorhanden).
+1. Prozesse (Server, ...) wieder starten.
+
+Falls `spring` im Einsatz ist, muss vor dem Wechsel `spring stop` ausgeführt werden.
+
+
+### Anleitung: Wagon erstellen
+
+Die Grundstruktur eines neuen Wagons kann sehr
+einfach im Hauptprojekt generiert werden (Die Templates dazu befinden sich in `lib/templates/wagon`):
+
+ rails generate wagon [name]
+
+Danach müssen noch folgende spezifischen Anpassungen gemacht werden:
+
+* Dateien von `hitobito/vendor/wagons/[name]` nach `hitobito_[name]` verschieben.
+* Eigenes Git Repo für den Wagon erzeugen.
+* `Gemfile.lock` vom Core in den Wagon kopieren.
+* Organisation im Lizenz Generator (`lib/tasks/license.rake`) anpassen und überall Lizenzen
+ hinzufügen: `rake app:license:insert`.
+* Organisation in `COPYING` ergänzen.
+* `AUTHORS` ergänzen.
+* In `hitobito_[name].gemspec` authors, email, summary und description anpassen.
+
+Falls der Wagon für eine neue Organisation ist, können noch diese Punkte angepasst werden:
+
+* In den Seeddaten Entwickler- und Kundenaccount hinzufügen: `db/seed/development/1_people.rb` unter `devs`.
+* Die gewünschte E-Mail des Root Users in `config/settings.yml` eintragen.
+* Falls die Applikation mehrsprachig sein soll: Transifex Projekt erstellen und vorbereiten.
+ Siehe dazu auch die Mehrsprachigkeits Guidelines.
+
+Falls der Wagon nicht für eine spezifische Organisation ist und keine Gruppenstruktur definiert,
+sollten folgende generierten Dateien gelöscht werden:
+
+* Gruppen Models: `rm -rf app/models/group/root.rb app/models/[name]/group.rb`
+* Übersetzungen der Models in `config/locales/models.[name].de.yml`
+* Seeddaten: `rm -rf db/seeds`
+
+Damit entsprechende Testdaten für Tests sowie Tarantula vorhanden sind, müssen die Fixtures im Wagon entsprechend der generierten Organisationsstruktur angepasst werden.
+* Anpassen der Fixtures für people, groups, roles, events, usw. (`spec/fixtures`)
+* Anpassen der Tarantula Tests im Wagon (`test/tarantula/tarantula_test.rb`)
+
+### Anleitung: Gruppenstruktur definieren
+
+Nachdem für eine Organisation ein neuer Wagon erstellt worden ist, muss oft auch eine
+Gruppenstruktur definiert werden. Wie die entsprechenden Modelle aufgebaut sind, ist in der
+Architekturdokumentation beschrieben. Hier die einzelnen Schritte, welche für das Aufsetzen der
+Entwicklungsumgebung noch vorgenommen werden müssen:
+
+* Am Anfang steht die alleroberste Gruppe. Die Klasse in `app/models/group/root.rb` entsprechend
+ umbenennen (z.B. nach "Dachverband") und erste Rollen definieren.
+* `app/models/[name]/group.rb#root_types` entsprechend anpassen.
+* In `config/locales/models.[name].de.yml` Übersetzungen für Gruppe und Rollen hinzufügen.
+* In `db/seed/development/1_people.rb` die Admin Rolle für die Entwickler anpassen.
+* In `db/seed/groups.rb` den Seed der Root Gruppe anpassen.
+* In `spec/fixtures/groups.yml` den Typ der Root Gruppe anpassen.
+* In `spec/fixtures/roles.yml` die Rollentypen anpassen.
+* Tests ausführen
+* Weitere Gruppen und Rollen inklusive Übersetzungen definieren.
+* In `db/seed/development/0_groups.rb` Seed Daten für die definierten Gruppentypen definieren.
+* In `spec/fixtures/groups.yml` Fixtures für die definierten Gruppentypen definieren. Es empfielt
+ sich, die selben Gruppen wie in den Development Seeds zu verwenden.
+* `README.md` mit Output von `rake app:hitobito:roles` ergänzen.
+
+
+### Anleitung: Einzelne Methode anpassen
+
+Im PBS Wagon wurde die Methode `full_name` auf dem `Person` Model angepasst.
+
+Die Implementation im Core sieht dabei folgendermassen aus: (`hitobito/app/models/person.rb`)
+
+ def full_name(format = :default)
+ case format
+ when :list then "#{last_name} #{first_name}".strip
+ else "#{first_name} #{last_name}".strip
+ end
+ end
+
+Im PBS Wagon gibt es ein entsprechendes Modul mit dem benutzerspezifischen Code für die Person Model Klasse: (`hitobito_pbs/app/models/pbs/person.rb`)
+
+ module Pbs::Person
+ ...
+ extend ActiveSupport::Concern
+
+ included do
+ ...
+ alias_method_chain :full_name, :title
+ ...
+ end
+ ...
+
+ def full_name_with_title(format = :default)
+ case format
+ when :list then full_name_without_title(format)
+ else "#{title} #{full_name_without_title(format)}".strip
+ end
+ end
+
+Mit `alias_method_chain` wird beim Aufruf von `#full_name` die Methode `#full_name_with_title` aufgerufen. Diese Methode wird ebenfalls in diesem Modul definiert. Die Implementation aus dem Core steht unter `#full_name_without_title` zur Verfügung.
+
+Damit der Code in diesem Module entsprechend für das Person Model übernommen wird, wird dies in der `wagon.rb` entsprechend included: (`hitobito_pbs/lib/hitobito_pbs/wagon.rb`)
+
+ module HitobitoPbs
+ class Wagon < Rails::Engine
+ include Wagons::Wagon
+ ...
+ Person.send :include, Pbs::Person
+ ...
+
+
+### Anleitung: Attribute hinzufügen
+
+The following documentation describes how new attributes can be added to a model in an own wagon. For reasons of simplification, this documentation follows an example where the generic wagon is going to be adapted and the `Person` model gets two new attributes called `title` and `salutation`.
+
+All mentioned files are created/adjusted in a dedicated wagon, not in the core application.
+
+#### Add new attributes to the database
+
+In order to adapt the database structure and add the desired new attributes to the model, a new migration must be created by the following command, which is executed in the root directory of the wagon:
+
+ $ bin/rails generate migration AddPeopleAttrs
+
+This command will create a new migration file in the path `db/migrate/YYYYMMDDHHMMSS_add_people_attrs.rb` which in the end should look as follows:
+
+ class AddPeopleAttrs < ActiveRecord::Migration
+ def change
+ add_column :people, :title, :string
+ add_column :people, :salutation, :string
+ end
+ end
+
+In this example, the data types of the attributes are set to strings.
+
+#### Permit attributes for editing
+
+The new attributes must be included in the application logic. To do so, a new controller has to be created in `app/controllers//people_controller.rb` which permits the two attributes to be updated:
+
+ module
+ module PeopleController
+ extend ActiveSupport::Concern
+ included do
+ self.permitted_attrs += [:title, :salutation]
+ end
+ end
+ end
+
+#### Show and edit attributes in the view
+
+There are two views which have to be adapted regarding the `Person` model: On one side the show view of the person and on the other side the edit view of the person.
+
+Create a new file in `app/views/people/_details_.html.haml` with the following content:
+
+ = render_attrs(entry, :title, :salutation)
+
+Create a new file in `app/views/people/_fields_.html.haml` with the following content:
+
+ = f.labeled_input_fields :title, :salutation
+
+It is important that these files start with `_details` respectively `_fields`. The core-application automatically includes/renders all files starting with `_details` and `_fields`. The subsequent characters (`_`) can be chosen arbitrarily.
+
+#### Translate the attribute names
+
+In order to display the attribute names properly in each language, the language files of all used languages must be adapted by simply adding the following lines to the `config/locales/models...yml`-files:
+
+ attributes:
+ person:
+ title:
+ salutation:
+
+#### Include attributes in the CSV/Excel Exports
+
+If wished, the attributes can be included in the CSV-File that is generated when performing a contact export. For this inclusion, a new file in `app/domain//export/tabular/people/people_address.rb` with the following content must be created:
+
+ module
+ module Export
+ module Tabular
+ module People
+ module PeopleAddress
+ extend ActiveSupport::Concern
+
+ included do
+ alias_method_chain :person_attributes, :title
+ end
+
+ def person_attributes_with_title
+ person_attributes_without_title + [:title, :salutation]
+ end
+ end
+ end
+ end
+ end
+ end
+
+#### Make attributes searchable
+
+The new attributes must be indexed in `app/indices/person_index.rb` where all indexes for Sphinx (the search tool that is used by hitobito) are defined.
+
+ ThinkingSphinx::Index.define_partial :person do
+ indexes title
+ end
+
+#### Output attributes in the API
+
+In order to provide the additional attributes in the API (the JSON-file of the object), the serializer for the people must be extended in `app/serializers//person_serializer.rb`:
+
+ module ::PersonSerializer
+ extend ActiveSupport::Concern
+ included do
+ extension(:details) do |_|
+ map_properties :title, :salutation
+ end
+ end
+ end
+
+#### Wire up above extensions
+
+The newly created or updated `PeopleController`, the CSV export file and the serializer file for the API must also be defined in the wagon configuration file which is located in `lib//wagon.rb`.
+
+ config.to_prepare do
+ ...
+ PeopleController.send :include, ::PeopleController
+ Export::Tabular::People::PeopleAddress.send :include, ::Export::Tabular::People::PeopleAddress
+ PersonSerializer.send :include, ::PersonSerializer
+ end
+
+#### Write tests for attributes
+
+Arbitrary tests cases can be defined in the `spec/` directory of the wagon. As an example, the following file (`spec/domain/export/tabular/people/people_address_spec.rb`) proposes a test case that checks whether the attributes are exported properly into the CSV-file:
+
+ require 'spec_helper'
+ require 'csv'
+
+ describe Export::Tabular::People::PeopleAddress do
+
+ let(:person) { people(:admin) }
+ let(:simple_headers) do
+ %w(Vorname Nachname Übername Firmenname Firma Haupt-E-Mail Adresse PLZ Ort Land
+ Geschlecht Geburtstag Rollen Titel Anrede)
+ end
+ let(:list) { Person.where(id: person) }
+ let(:data) { Export::Tabular::People::PeopleAddress.csv(list) }
+ let(:csv) { CSV.parse(data, headers: true, col_sep: Settings.csv.separator) }
+
+ subject { csv }
+
+ before do
+ person.update!(title: 'Dr.', salutation: 'Herr', town: 'Bern')
+ end
+
+ context 'export' do
+ its(:headers) { should == simple_headers }
+
+ context 'first row' do
+ subject { csv[0] }
+
+ its(['Vorname']) { should eq person.first_name }
+ its(['Nachname']) { should eq person.last_name }
+ its(['Haupt-E-Mail']) { should eq person.email }
+ its(['Ort']) { should eq person.town }
+ its(['Geschlecht']) { should eq person.gender_label }
+ its(['Rollen']) { should eq 'Administrator Verband' }
+ its(['Titel']) { should eq 'Dr.' }
+ its(['Anrede']) { should eq 'Herr' }
+ end
+ end
+
+ context 'export_full' do
+ its(:headers) { should include('Titel') }
+ its(:headers) { should include('Anrede') }
+
+ let(:data) { Export::Tabular::People::PeopleFull.csv(list) }
+
+ context 'first row' do
+ subject { csv[0] }
+
+ its(['Titel']) { should eq 'Dr.' }
+ its(['Anrede']) { should eq 'Herr' }
+ end
+ end
+ end
diff --git a/doc/development/04_rest_api.md b/doc/development/05_rest_api.md
similarity index 73%
rename from doc/development/04_rest_api.md
rename to doc/development/05_rest_api.md
index 30869037a4..e6d815081a 100644
--- a/doc/development/04_rest_api.md
+++ b/doc/development/05_rest_api.md
@@ -4,10 +4,15 @@ Das JSON Format folgt den Konventionen von [json:api](http://jsonapi.org).
### Authentisierung
-Die folgenden Methoden dienen zur Authentisierung und Verwaltung des Authentisierungstokens.
-Als Parameter müssen immer `person[email]` und `person[password]` übergeben werden. In der Antwort
-ist der Wert des `authentication_token` enthalten, welches für die folgenden Requests jeweils
-mitgegeben werden muss.
+Für die Verwendung der API ist ein Authentisierungstoken nötig. Jeder Benutzeraccount
+kann ein solches Token erstellen. Dieses Token ist danach mit dem Benutzeraccount
+verknüpft und hat die selben Zugriffsrechte wie der/die Benutzer/in.
+
+Ein Token ist für unbeschränkte Zeit gültig, es kann also z.B. in eine Webseite
+eingebunden werden, ohne dass das Passwort für den Benutzeraccount
+verwendet werden muss.
+
+Für die Verwaltung des Tokens dienen die folgenden HTTP Endpunkte:
| Methode | Pfad | Funktion |
| --- | --- | --- |
@@ -15,8 +20,15 @@ mitgegeben werden muss.
| POST | /users/token.json | Token neu generieren |
| DELETE | /users/token.json | Token löschen |
-Sobald das Authentisierungstoken bekannt ist, können verschiedene Endpunkte abgefragt werden.
-Dazu bestehen zwei Möglichkeiten:
+Als Parameter müssen immer `person[email]` und `person[password]` übergeben werden.
+
+Mit `curl` geht das so:
+
+ curl -d "person[email]=mitglied@hitobito.ch" \
+ -d "person[password]=demo" \
+ http://demo.hitobito.ch/users/sign_in.json
+
+Um das Token bei der restlichen API zu verwenden, bestehen zwei Möglichkeiten:
* **Parameter**: `user_email` und `user_token` werden als Pfadparameter angegeben, der Pfad muss
mit `.json` enden (Bsp: `/groups/1.json?user_email=zumkehr@puzzle.ch&user_token=abcdef`).
diff --git a/doc/development/05_jenkins_setup.md b/doc/development/06_jenkins_setup.md
similarity index 92%
rename from doc/development/05_jenkins_setup.md
rename to doc/development/06_jenkins_setup.md
index f42812cf52..c926d64f3f 100644
--- a/doc/development/05_jenkins_setup.md
+++ b/doc/development/06_jenkins_setup.md
@@ -1,4 +1,4 @@
-#Jenkins Setup
+## Jenkins Setup
Um mit den verschiedenen Hitobito Projekten nicht zuviel Overhead zu erzeugen, werden die Jenkins Jobs wie folgt aufgeteilt. Damit werden die jeweiligen Tests nur einmal ausgeführt.
@@ -18,7 +18,7 @@ Als Home Verzeichnis für alle Tasks und Reports muss danach hitobito gesetzt we
Damit die Änderungen am Core Repo bei Wagon Jobs keinen Commit Build triggert, müssen dort für das Core Repo alle Dateien ausgeschlossen werden (Erweitert > Included Regions: 'none'). Änderungen am Core, welche bei Wagons Probleme verursachen könnten, werden somit spätestens in den Nightly Builds erkannt (Core Repo Daten nicht ausgeschlossen). Dies ist ein Trade-off zwischen ständig laufenden Jobs, auf welche gewartet werden muss, und unmittelbarem Feedback.
-##Umgebungsvariablen
+### Umgebungsvariablen
Für alle Jobs muss eine ensprechende Test DB erstellt und konfiguriert werden. Jeder Job muss eine eigene DB erstellen, damit sich diese nicht in die Quere kommen. Dabei ist der Prefix 'jenkins_hitobito_' zu wählen. Die Jobs müssen ebenfalls einen eindeutigen RAILS_SPHINX_PORT sowie einen CAPYBARA_SERVER_PORT definieren.
@@ -36,7 +36,7 @@ Set environment variable names:
RAILS_DB_PASSWORD=..
-##hitobito-core: Commit Build für den Core
+### hitobito-core: Commit Build für den Core
Läuft nach jedem Commit auf dem Core Repo (pitc_hit/hitobito.git), Master und Stable Branch.
* Läuft die Rubocop Hard Conventions
@@ -46,7 +46,7 @@ Rake Task:
ci
-##hitobito-core-nightly_master/stable: Nightly Build für den Core
+### hitobito-core-nightly_master/stable: Nightly Build für den Core
Läuft jede Nacht bei Changes auf dem Core Repo Master und Stable Branch. Die gesamte Build History wird archiviert.
@@ -58,7 +58,7 @@ Läuft jede Nacht bei Changes auf dem Core Repo Master und Stable Branch. Die ge
Rake Task: bundle exec rake ci:nightly tx:auth tx:push -t
-##hitobito-[wagon]: Commit Build für einen Wagon
+### hitobito-[wagon]: Commit Build für einen Wagon
Läuft nach jedem Commit auf dem Wagon Repo, je nach dem auf dem Master oder Stable Branch.
@@ -71,7 +71,7 @@ Rake Task:
Script: bin/ci/wagon_commit.sh
-##hitobito-[wagon]-nightly_master: Nightly Build für einen Wagon, Master Branch
+### hitobito-[wagon]-nightly_master: Nightly Build für einen Wagon, Master Branch
Läuft jede Nacht bei Changes auf dem Wagon Repo, Master Branch, falls der hitobito-core-nightly Job erfolgreich war. Die gesamte Build History wird archiviert.
@@ -91,7 +91,7 @@ Script:
bin/ci/wagon_nightly_master.sh
-##Abhängigkeiten
+#### Abhängigkeiten
Zum überprüfen, ob der hitobito-core-nightly Job erfolgreich war, wurde folgendes Script als erster Build Step verwendet. Dieses funktioniert leider nicht mehr, da die Jobs auf unterschiedlichen Build Nodes ablaufen können und daher keinen Zugriff aufeinander haben. D.h., dass momentan ein Wagon Nightly Job erfolgreich sein kann, obwohl der Core Nightly failed. Damit können fehlerhafte Cores deployt werden.
@@ -109,7 +109,7 @@ Zum überprüfen, ob der hitobito-core-nightly Job erfolgreich war, wurde folgen
Dadurch failt der Job direkt, falls hitobito-core-nightly nicht successfull ist.
-##hitobito-[wagon]-nightly_stable: Nightly Build für einen Wagon, Stable Branch
+### hitobito-[wagon]-nightly_stable: Nightly Build für einen Wagon, Stable Branch
Läuft jede Nacht bei Changes auf dem Wagon Repo, Stable Branch, falls der hitobito-core-nightly Job erfolgreich war.
@@ -124,7 +124,7 @@ Script:
bin/ci/wagon_nightly_stable.sh
-##hitobito-[wagon]-rpm: RPM Build für einen Wagon
+### hitobito-[wagon]-rpm: RPM Build für einen Wagon
Läuft nach dem entsprechenden hitobito-[wagon]-nightly Job und erstellt ein RPM. Master oder Stable Branch.
diff --git a/doc/development/README.md b/doc/development/README.md
index 83a752084b..997bac1519 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -7,6 +7,8 @@ Diese Dokumente beschreiben verschiedene Aspekte, welche bei der Entwicklung zu
* [Entwicklungsumgebung](01_setup.md)
* [Deployment](02_deployment.md)
* [Guidelines](03_guidelines.md)
-* [REST API](04_rest_api.md)
+* [Wagons](04_wagons.md)
+* [REST API](05_rest_api.md)
+* [Jenkins Setup](06_jenkins_setup.md)
Alle Diagramme werden mit [Draw.io](http://draw.io) erstellt und jeweils als Original .xml sowie als .svg abgespeichert.
diff --git a/doc/e-mail/README.md b/doc/e-mail/README.md
new file mode 100644
index 0000000000..28994611e5
--- /dev/null
+++ b/doc/e-mail/README.md
@@ -0,0 +1,47 @@
+## E-Mail
+
+Hier findest du eine Übersicht sowie die Dokumentation der E-Mail Features von Hitobito
+
+### Mailing Listen / Abos
+
+Hitobito stellt eine simple Implementation von Mailing Listen zur Verfügung. Diese können in der
+Applikation beliebig erstellt und verwaltet werden. Dies geschieht in den Modellen `MailingList`
+und `Subscription`.
+
+Alle E-Mails an die Applikationsdomain (z.B `news@db.jubla.ch`) werden über einen Catch-All Mail
+Account gesammelt. Dabei muss der Mailserver den zusätzlichen E-Mail Header `X-Envelope-To` setzen,
+welcher den ursprünglichen Empfänger enthält (z.B. `news`). Von der Applikation wird dieser Account
+in einem Background Job über POP3 regelmässig gepollt. Die eingetroffenen E-Mails werden danach wie
+folgt verarbeitet:
+
+1. Verwerfe das Email, falls der Empfänger keine definierte Mailing Liste ist.
+1. Sende eine Rückweisungsemail, falls der Absender nicht berechtigt ist.
+1. Leite das Email weiter an alle Empfänger der Mailing Liste.
+
+DiDa man aus diversen Gründen (BCC, Mail Aliase) den eigentlichen Empfänger nicht aus dem To: Header lesen kann, muss ein zusätzlicher Header mit der Empfängeradresse vom Mailserver gesetzt werden. Als quasi Standard hat sich für solche Zwecke hier der X-Envelope-to Header etabliert.e Berechtigung, um auf eine Mailing Liste zu schreiben, kann konfiguriert werden. Der Absender
+wird über seine Haupt- oder zusätzlichen E-Mail Adressen identifiziert. Standardmässig können alle
+Personen, welche die Liste Bearbeiten können, sowie die Gruppe, welcher das Abo gehört, E-Mails
+schreiben. Optional können zusätzlich spezifische E-Mail Adressen, alle Abonnenten der Gruppe oder
+beliebige Absender (auch nicht in hitobito erfasste) berechtigt werden.
+
+Jede Gruppe kann beliebig viele Abos haben, welche optional eine E-Mail Adresse
+haben und dadurch ebenfalls als E-Mail Liste verwendet werden können. Einzelne Personen, jedoch auch
+bestimmte Rollen einer Gruppe oder Teilnehmende eines Events können Abonnenten sein.
+
+#### X-Envelope-To Header
+
+Da man aus diversen Gründen (BCC, Mail Aliase) den eigentlichen Empfänger nicht aus dem To: Header lesen kann, muss ein zusätzlicher Header mit der Empfängeradresse vom Mailserver gesetzt werden. Als quasi Standard hat sich für solche Zwecke hier der X-Envelope-to Header etabliert.
+
+### Mailversand
+
+Bei folgenden Aktionen werden Mails versendet: (Wagon Features sind hier nicht berücksichtigt)
+
+| Aktion | Mailer Class | DelayedJob | Attachment ? |
+| --- | --- | --- | --- |
+| Passwort vergessen | via Devise gem | - | nein |
+| Passwort Reset / Login erstellen (durch Fremdperson) | Person::LoginMailer | Person::SendLoginJob | nein |
+| Zugriffsanfrage Person | Person::AddRequestMailer | Person::SendAddRequestJob | nein |
+| Event Bestätigung Teilnahme | Event::ParticipationMailer | Event::ParticipationConfirmationJob | ja |
+| Event Abmeldung Teilnahme | Event::ParticipationMailer | Event::CancelApplicationJob | nein |
+| Event Einladung Registrierung | Event::RegisterMailer | Event::SendRegisterLoginJob | nein |
+| Export der Abonnenten einer Mailingliste| Export::SubscriptionsMailer | Export::SubscriptionsJob | ja |
diff --git a/doc/template/skeleton.html b/doc/template/skeleton.html
index 3b03435cf5..1a894f2535 100644
--- a/doc/template/skeleton.html
+++ b/doc/template/skeleton.html
@@ -10,57 +10,30 @@
-
-