From c989427d30aff7b48bcbb86b7f640af128f6207a Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 23 Dec 2021 11:47:39 +0100 Subject: [PATCH 001/163] Update influxdb-client --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a1f818a2..3e4d236a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -269,9 +269,9 @@ GEM image_processing (1.12.1) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - influxdb-client (1.16.0) - influxdb-client-apis (1.16.0) - influxdb-client (= 1.16.0) + influxdb-client (2.1.0) + influxdb-client-apis (2.1.0) + influxdb-client (= 2.1.0) typhoeus (~> 1.0, >= 1.0.1) jbuilder (2.11.2) activesupport (>= 5.0.0) From b85d0d31b85df3a7706df2125df8b50029aeda75 Mon Sep 17 00:00:00 2001 From: Thomas Burkhalter Date: Tue, 1 Mar 2022 17:50:57 +0100 Subject: [PATCH 002/163] Update version --- .tool-versions | 2 +- config/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.tool-versions b/.tool-versions index 9eb38ed7..2db3e235 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 2.7.2 +ruby 2.5.5 diff --git a/config/version.rb b/config/version.rb index 7e4d477e..f50e153b 100644 --- a/config/version.rb +++ b/config/version.rb @@ -1,3 +1,3 @@ module Puzzletime - VERSION = '2.6' + VERSION = '2.7' end From 5a7db222dbc4729a5760aaa9d9145e6cc5ac8645 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Tue, 22 Mar 2022 18:14:23 +0100 Subject: [PATCH 003/163] Implement new model Workplace and add as employee attribute, fixes #57579 --- app/controllers/employees_controller.rb | 2 +- app/controllers/workplaces_controller.rb | 8 +++++++ app/models/ability.rb | 4 +++- app/models/employee.rb | 1 + app/models/workplace.rb | 15 ++++++++++++ app/views/configurations/index.html.haml | 3 +++ app/views/employees/_form.html.haml | 1 + config/locales/models.de-CH.yml | 4 ++++ config/routes.rb | 2 ++ .../20220322152042_create_workplaces.rb | 8 +++++++ db/schema.rb | 8 ++++++- .../controllers/workplaces_controller_test.rb | 24 +++++++++++++++++++ test/fixtures/employees.yml | 2 ++ test/fixtures/workplaces.yml | 10 ++++++++ test/models/workplace_test.rb | 20 ++++++++++++++++ 15 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 app/controllers/workplaces_controller.rb create mode 100644 app/models/workplace.rb create mode 100644 db/migrate/20220322152042_create_workplaces.rb create mode 100644 test/controllers/workplaces_controller_test.rb create mode 100644 test/fixtures/workplaces.yml create mode 100644 test/models/workplace_test.rb diff --git a/app/controllers/employees_controller.rb b/app/controllers/employees_controller.rb index 632ca39e..a4ea2ff5 100644 --- a/app/controllers/employees_controller.rb +++ b/app/controllers/employees_controller.rb @@ -5,7 +5,7 @@ class EmployeesController < ManageController self.permitted_attrs = [:firstname, :lastname, :shortname, :email, :ldapname, - :department_id, :crm_key, :probation_period_end_date, + :department_id, :workplace_id, :crm_key, :probation_period_end_date, :graduation, :management, :phone_office, :phone_private, :street, :postal_code, :city, :birthday, :emergency_contact_name, :emergency_contact_phone, :marital_status, diff --git a/app/controllers/workplaces_controller.rb b/app/controllers/workplaces_controller.rb new file mode 100644 index 00000000..7d39686a --- /dev/null +++ b/app/controllers/workplaces_controller.rb @@ -0,0 +1,8 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +class WorkplacesController < ManageController + self.permitted_attrs = [:name] +end diff --git a/app/models/ability.rb b/app/models/ability.rb index d9e01470..44ee5d2e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -54,7 +54,9 @@ def management_abilities EmploymentRoleLevel, EmploymentRoleCategory, Reports::Workload, - WorkItem] + WorkItem, + Workplace + ] # :crud instead of :manage because cannot change settings of other employees can [:crud, diff --git a/app/models/employee.rb b/app/models/employee.rb index 0ac6dab7..d5eb273e 100644 --- a/app/models/employee.rb +++ b/app/models/employee.rb @@ -69,6 +69,7 @@ class Employee < ActiveRecord::Base # All dependencies between the models are listed below. belongs_to :department, optional: true + belongs_to :workplace, optional: true has_and_belongs_to_many :invoices diff --git a/app/models/workplace.rb b/app/models/workplace.rb new file mode 100644 index 00000000..602f21e9 --- /dev/null +++ b/app/models/workplace.rb @@ -0,0 +1,15 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +class Workplace < ActiveRecord::Base + validates_by_schema + validates :name, uniqueness: { case_sensitive: false } + + scope :list, -> { order(:name) } + + def to_s + name + end +end \ No newline at end of file diff --git a/app/views/configurations/index.html.haml b/app/views/configurations/index.html.haml index d4c70acc..6e345a82 100644 --- a/app/views/configurations/index.html.haml +++ b/app/views/configurations/index.html.haml @@ -12,3 +12,6 @@ = render 'group', title: 'Aufträge', models: [OrderKind, OrderStatus, TargetScope] = render 'group', title: 'Funktionen', models: [EmploymentRole, EmploymentRoleLevel, EmploymentRoleCategory] + +.row + = render 'group', title: 'Stammdaten', models: [Workplace] diff --git a/app/views/employees/_form.html.haml b/app/views/employees/_form.html.haml index 7b139026..cdfe6c1d 100644 --- a/app/views/employees/_form.html.haml +++ b/app/views/employees/_form.html.haml @@ -11,6 +11,7 @@ :email, :ldapname, :department_id, + :workplace_id, :probation_period_end_date, :graduation diff --git a/config/locales/models.de-CH.yml b/config/locales/models.de-CH.yml index 29f2a964..764aec6f 100644 --- a/config/locales/models.de-CH.yml +++ b/config/locales/models.de-CH.yml @@ -115,6 +115,9 @@ de-CH: worktime: one: Arbeitszeit other: Arbeitszeiten + workplace: + one: Arbeitsort + other: Arbeitsorte attributes: absence: @@ -206,6 +209,7 @@ de-CH: crm_key: Highrise ID identity_card_type: Ausweistyp identity_card_valid_until: Ausweis gültig bis + workplace_id: Arbeitsort employee/marital_statuses: married: Verheiratet single: Ledig diff --git a/config/routes.rb b/config/routes.rb index 789ae54f..24af575f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -151,6 +151,8 @@ resources :worktimes, only: [:index] + resources :workplaces + resources :ordertimes do collection do get :existing diff --git a/db/migrate/20220322152042_create_workplaces.rb b/db/migrate/20220322152042_create_workplaces.rb new file mode 100644 index 00000000..993f223a --- /dev/null +++ b/db/migrate/20220322152042_create_workplaces.rb @@ -0,0 +1,8 @@ +class CreateWorkplaces < ActiveRecord::Migration[5.2] + def change + create_table :workplaces do |t| + t.string :name + end + add_reference :employees, :workplace + end +end diff --git a/db/schema.rb b/db/schema.rb index b678a79f..82352e0f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_12_31_163202) do +ActiveRecord::Schema.define(version: 2022_03_22_152042) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -195,8 +195,10 @@ t.datetime "remember_created_at" t.datetime "created_at", default: "2021-07-13 19:37:08", null: false t.datetime "updated_at", default: "2021-07-13 19:37:08", null: false + t.bigint "workplace_id" t.index ["department_id"], name: "index_employees_on_department_id" t.index ["shortname"], name: "chk_unique_name", unique: true + t.index ["workplace_id"], name: "index_employees_on_workplace_id" end create_table "employees_invoices", id: false, force: :cascade do |t| @@ -471,6 +473,10 @@ t.decimal "must_hours_per_day", precision: 4, scale: 2, null: false end + create_table "workplaces", force: :cascade do |t| + t.string "name" + end + create_table "worktimes", force: :cascade do |t| t.integer "absence_id" t.integer "employee_id" diff --git a/test/controllers/workplaces_controller_test.rb b/test/controllers/workplaces_controller_test.rb new file mode 100644 index 00000000..f14534ac --- /dev/null +++ b/test/controllers/workplaces_controller_test.rb @@ -0,0 +1,24 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +require 'test_helper' + +class WorkplacesControllerTest < ActionController::TestCase + include CrudControllerTestHelper + + setup :login + + private + + # Test object used in several tests. + def test_entry + workplaces(:zurich) + end + + # Attribute hash used in several tests. + def test_entry_attrs + { name: "Ouagadougou" } + end +end diff --git a/test/fixtures/employees.yml b/test/fixtures/employees.yml index 348790dd..52ce94b1 100644 --- a/test/fixtures/employees.yml +++ b/test/fixtures/employees.yml @@ -102,6 +102,7 @@ pascal: email: pz@bla.ch management: false department: devtwo + workplace: bern mark: id: 7 @@ -118,6 +119,7 @@ mark: - '-1m' - '0y' - '-1y' + workplace: jona lucien: id: 8 diff --git a/test/fixtures/workplaces.yml b/test/fixtures/workplaces.yml new file mode 100644 index 00000000..1ce601e8 --- /dev/null +++ b/test/fixtures/workplaces.yml @@ -0,0 +1,10 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +bern: + name: Bern + +zurich: + name: Zürich \ No newline at end of file diff --git a/test/models/workplace_test.rb b/test/models/workplace_test.rb new file mode 100644 index 00000000..be7a0f88 --- /dev/null +++ b/test/models/workplace_test.rb @@ -0,0 +1,20 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +# == Schema Information +# +# Table name: workplace +# +# id :integer not null, primary key +# name :string not null +# + +require 'test_helper' + +class WorkplaceTest < ActiveSupport::TestCase + test 'string representation matches name' do + assert_equal workplaces(:zurich).to_s, 'Zürich' + end +end From 09b888e35bc6fa79c6b0d0ad14ab02caa792c43d Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 24 Mar 2022 10:51:37 +0100 Subject: [PATCH 004/163] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f5acb3..4810b59d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ ### Improvements * **Spesen:** Spesenbelege werden nun beim Hochladen herunterskaliert * **Kundenauswertung:** Auftrag verlinkt, um schneller hin und her navigieren zu können -* **Mitarbeiter-Stammdaten:** Attribut "Telefon privat" umbenannt in "Mobiltelefon" -* **Mitarbeiter-Stammdaten:** Anstellungsprozente und Funktionsanteile können nun in 2.5% Schritten konfiguriert werden +* **Mitarbeiter-Stammdaten:** + + Attribut "Telefon privat" umbenannt in "Mobiltelefon" + + Anstellungsprozente und Funktionsanteile können nun in 2.5% Schritten konfiguriert werden + + Neues Attribut "Arbeitsort", verfügbare Werte konfigurierbar unter "Verwalten" * **Mitarbeiterliste:** Sortierbar gemacht nach Vorname, Nachname * **Zeiterfassung:** Leerschläge vor und nach der Ticketnummer werden entfernt From 45fb978fcba74e989a369a2bdc932da984b5f6e7 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 24 Mar 2022 11:07:56 +0100 Subject: [PATCH 005/163] Fix Employee timestamps default --- .../20220324100350_fix_employees_timestamps_default.rb | 6 ++++++ db/schema.rb | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20220324100350_fix_employees_timestamps_default.rb diff --git a/db/migrate/20220324100350_fix_employees_timestamps_default.rb b/db/migrate/20220324100350_fix_employees_timestamps_default.rb new file mode 100644 index 00000000..aad0cd86 --- /dev/null +++ b/db/migrate/20220324100350_fix_employees_timestamps_default.rb @@ -0,0 +1,6 @@ +class FixEmployeesTimestampsDefault < ActiveRecord::Migration[5.2] + def up + change_column_default :employees, :created_at, -> { "now()" } + change_column_default :employees, :updated_at, -> { "now()" } + end +end diff --git a/db/schema.rb b/db/schema.rb index 82352e0f..0bdad825 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_03_22_152042) do +ActiveRecord::Schema.define(version: 2022_03_24_100350) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -193,8 +193,8 @@ t.date "identity_card_valid_until" t.string "encrypted_password", default: "" t.datetime "remember_created_at" - t.datetime "created_at", default: "2021-07-13 19:37:08", null: false - t.datetime "updated_at", default: "2021-07-13 19:37:08", null: false + t.datetime "created_at", default: -> { "now()" }, null: false + t.datetime "updated_at", default: -> { "now()" }, null: false t.bigint "workplace_id" t.index ["department_id"], name: "index_employees_on_department_id" t.index ["shortname"], name: "chk_unique_name", unique: true From c066a37858580b8222f48f74cd2ccca99b0eb97e Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Mon, 28 Mar 2022 17:53:44 +0200 Subject: [PATCH 006/163] Fix bin/bundle binstub --- bin/bundle | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/bin/bundle b/bin/bundle index f19acf5b..374a0a1f 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,3 +1,114 @@ #!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) -load Gem.bin_path('bundler', 'bundle') +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../../Gemfile", __FILE__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end From 3b1cb84c2835459f09aba0791c71a9230b7b1ab1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Mar 2022 23:21:28 +0000 Subject: [PATCH 007/163] Bump image_processing from 1.12.1 to 1.12.2 Bumps [image_processing](https://github.com/janko/image_processing) from 1.12.1 to 1.12.2. - [Release notes](https://github.com/janko/image_processing/releases) - [Changelog](https://github.com/janko/image_processing/blob/master/CHANGELOG.md) - [Commits](https://github.com/janko/image_processing/compare/v1.12.1...v1.12.2) --- updated-dependencies: - dependency-name: image_processing dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3e4d236a..dc572e14 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -242,7 +242,7 @@ GEM faraday-patron (1.0.0) fast_jsonapi (1.5) activesupport (>= 4.2) - ffi (1.15.3) + ffi (1.15.5) globalid (0.4.2) activesupport (>= 4.2.0) haml (5.2.1) @@ -266,7 +266,7 @@ GEM i18n (1.8.10) concurrent-ruby (~> 1.0) i18n_data (0.13.0) - image_processing (1.12.1) + image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) influxdb-client (2.1.0) @@ -479,7 +479,7 @@ GEM ruby-saml (1.12.2) nokogiri (>= 1.10.5) rexml - ruby-vips (2.1.2) + ruby-vips (2.1.4) ffi (~> 1.12) ruby2_keywords (0.0.4) rubyzip (2.3.2) From 11858c430c569e31b40dd1caa9dd2215770b7ccc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Mar 2022 21:56:36 +0000 Subject: [PATCH 008/163] Bump puma from 5.5.1 to 5.6.4 Bumps [puma](https://github.com/puma/puma) from 5.5.1 to 5.6.4. - [Release notes](https://github.com/puma/puma/releases) - [Changelog](https://github.com/puma/puma/blob/master/History.md) - [Commits](https://github.com/puma/puma/compare/v5.5.1...v5.6.4) --- updated-dependencies: - dependency-name: puma dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index dc572e14..d6d9b6d3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -383,7 +383,7 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.6) - puma (5.5.1) + puma (5.6.4) nio4r (~> 2.0) racc (1.5.2) rack (2.2.3) From 70ff61a20d258c1b7df932500062b8f9ad5ff9f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 07:54:16 +0000 Subject: [PATCH 009/163] Bump nokogiri from 1.12.5 to 1.13.2 Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.12.5 to 1.13.2. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.12.5...v1.13.2) --- updated-dependencies: - dependency-name: nokogiri dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d6d9b6d3..5461bcfe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -311,7 +311,7 @@ GEM method_source (1.0.0) mini_magick (4.11.0) mini_mime (1.1.0) - mini_portile2 (2.6.1) + mini_portile2 (2.8.0) minitest (5.14.4) minitest-reporters (1.4.3) ansi @@ -329,8 +329,8 @@ GEM rails (>= 3.2.0) net-ldap (0.17.0) nio4r (2.5.8) - nokogiri (1.12.5) - mini_portile2 (~> 2.6.1) + nokogiri (1.13.2) + mini_portile2 (~> 2.8.0) racc (~> 1.4) oauth2 (1.4.7) faraday (>= 0.8, < 2.0) @@ -385,7 +385,7 @@ GEM public_suffix (4.0.6) puma (5.6.4) nio4r (~> 2.0) - racc (1.5.2) + racc (1.6.0) rack (2.2.3) rack-protection (2.1.0) rack From c3aaaa291bd695cdb2e8c682d10c11eacf2e2aae Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Mon, 11 Apr 2022 10:16:33 +0200 Subject: [PATCH 010/163] Upgrade rails to 5.2.7 to fix CVEs --- Gemfile.lock | 106 +++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5461bcfe..b9565326 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,51 +14,51 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (5.2.6) - actionpack (= 5.2.6) + actioncable (5.2.7) + actionpack (= 5.2.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.6) - actionpack (= 5.2.6) - actionview (= 5.2.6) - activejob (= 5.2.6) + actionmailer (5.2.7) + actionpack (= 5.2.7) + actionview (= 5.2.7) + activejob (= 5.2.7) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.6) - actionview (= 5.2.6) - activesupport (= 5.2.6) + actionpack (5.2.7) + actionview (= 5.2.7) + activesupport (= 5.2.7) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.6) - activesupport (= 5.2.6) + actionview (5.2.7) + activesupport (= 5.2.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.6) - activesupport (= 5.2.6) + activejob (5.2.7) + activesupport (= 5.2.7) globalid (>= 0.3.6) - activemodel (5.2.6) - activesupport (= 5.2.6) + activemodel (5.2.7) + activesupport (= 5.2.7) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (5.2.6) - activemodel (= 5.2.6) - activesupport (= 5.2.6) + activerecord (5.2.7) + activemodel (= 5.2.7) + activesupport (= 5.2.7) arel (>= 9.0) activeresource (5.1.1) activemodel (>= 5.0, < 7) activemodel-serializers-xml (~> 1.0) activesupport (>= 5.0, < 7) - activestorage (5.2.6) - actionpack (= 5.2.6) - activerecord (= 5.2.6) + activestorage (5.2.7) + actionpack (= 5.2.7) + activerecord (= 5.2.7) marcel (~> 1.0.0) - activesupport (5.2.6) + activesupport (5.2.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -145,7 +145,7 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.9) + concurrent-ruby (1.1.10) config (3.1.0) deep_merge (~> 1.2, >= 1.2.1) dry-validation (~> 1.0, >= 1.0.0) @@ -243,8 +243,8 @@ GEM fast_jsonapi (1.5) activesupport (>= 4.2) ffi (1.15.5) - globalid (0.4.2) - activesupport (>= 4.2.0) + globalid (1.0.0) + activesupport (>= 5.0) haml (5.2.1) temple (>= 0.8.0) tilt @@ -263,7 +263,7 @@ GEM activeresource (>= 3.2.13) hpricot (0.8.6) htmlentities (4.3.4) - i18n (1.8.10) + i18n (1.10.0) concurrent-ruby (~> 1.0) i18n_data (0.13.0) image_processing (1.12.2) @@ -302,17 +302,17 @@ GEM kaminari (>= 0.13.0) rails kaminari-core (1.2.1) - loofah (2.10.0) + loofah (2.16.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) - marcel (1.0.1) + marcel (1.0.2) method_source (1.0.0) mini_magick (4.11.0) - mini_mime (1.1.0) - mini_portile2 (2.8.0) - minitest (5.14.4) + mini_mime (1.1.2) + mini_portile2 (2.6.1) + minitest (5.15.0) minitest-reporters (1.4.3) ansi builder @@ -329,8 +329,8 @@ GEM rails (>= 3.2.0) net-ldap (0.17.0) nio4r (2.5.8) - nokogiri (1.13.2) - mini_portile2 (~> 2.8.0) + nokogiri (1.12.5) + mini_portile2 (~> 2.6.1) racc (~> 1.4) oauth2 (1.4.7) faraday (>= 0.8, < 2.0) @@ -391,18 +391,18 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.6) - actioncable (= 5.2.6) - actionmailer (= 5.2.6) - actionpack (= 5.2.6) - actionview (= 5.2.6) - activejob (= 5.2.6) - activemodel (= 5.2.6) - activerecord (= 5.2.6) - activestorage (= 5.2.6) - activesupport (= 5.2.6) + rails (5.2.7) + actioncable (= 5.2.7) + actionmailer (= 5.2.7) + actionpack (= 5.2.7) + actionview (= 5.2.7) + activejob (= 5.2.7) + activemodel (= 5.2.7) + activerecord (= 5.2.7) + activestorage (= 5.2.7) + activesupport (= 5.2.7) bundler (>= 1.3.0) - railties (= 5.2.6) + railties (= 5.2.7) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -416,16 +416,16 @@ GEM activesupport (>= 4.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) - rails-html-sanitizer (1.3.0) + rails-html-sanitizer (1.4.2) loofah (~> 2.3) rails-i18n (5.1.3) i18n (>= 0.7, < 2) railties (>= 5.0, < 6) rails_autolink (1.1.6) rails (> 3.1) - railties (5.2.6) - actionpack (= 5.2.6) - activesupport (= 5.2.6) + railties (5.2.7) + actionpack (= 5.2.7) + activesupport (= 5.2.7) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) @@ -514,17 +514,17 @@ GEM sort_alphabetical (1.1.0) unicode_utils (>= 1.2.2) spring (2.1.1) - sprockets (4.0.2) + sprockets (4.0.3) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) swagger-blocks (3.0.0) sysexits (1.2.0) temple (0.8.2) - thor (1.1.0) + thor (1.2.1) thread_safe (0.3.6) tilt (2.0.10) timeliness (0.4.4) From 6233e36045e29e83fb595ecbb94c65391d3684cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 08:19:21 +0000 Subject: [PATCH 011/163] Bump nokogiri from 1.12.5 to 1.13.2 Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.12.5 to 1.13.2. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.12.5...v1.13.2) --- updated-dependencies: - dependency-name: nokogiri dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b9565326..90446191 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -311,7 +311,7 @@ GEM method_source (1.0.0) mini_magick (4.11.0) mini_mime (1.1.2) - mini_portile2 (2.6.1) + mini_portile2 (2.8.0) minitest (5.15.0) minitest-reporters (1.4.3) ansi @@ -329,8 +329,8 @@ GEM rails (>= 3.2.0) net-ldap (0.17.0) nio4r (2.5.8) - nokogiri (1.12.5) - mini_portile2 (~> 2.6.1) + nokogiri (1.13.2) + mini_portile2 (~> 2.8.0) racc (~> 1.4) oauth2 (1.4.7) faraday (>= 0.8, < 2.0) From da933bd53e034e926f33aa19ef030cd247cb5510 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Tue, 5 Apr 2022 11:22:40 +0200 Subject: [PATCH 012/163] Validate contact email syntax, fixes #46693 --- Gemfile | 1 + Gemfile.lock | 8 ++++++++ app/models/contact.rb | 1 + app/models/util/email_validator.rb | 7 +++++++ config/locales/error_messages.de-CH.yml | 2 ++ test/models/contact_test.rb | 26 +++++++++++++++++++++++++ 6 files changed, 45 insertions(+) create mode 100644 app/models/util/email_validator.rb create mode 100644 test/models/contact_test.rb diff --git a/Gemfile b/Gemfile index 79197e83..bae81989 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ gem 'dalli' gem 'delayed_cron_job' gem 'delayed_job_active_record' gem 'devise' +gem 'email_address' gem 'fast_jsonapi' gem 'haml' gem 'highrise' diff --git a/Gemfile.lock b/Gemfile.lock index 90446191..c6d20c1d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -216,6 +216,8 @@ GEM dry-equalizer (~> 0.2) dry-initializer (~> 3.0) dry-schema (~> 1.5, >= 1.5.2) + email_address (0.2.2) + simpleidn erubi (1.10.0) ethon (0.14.0) ffi (>= 1.15.0) @@ -510,6 +512,8 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.3) + simpleidn (0.2.1) + unf (~> 0.1.4) sixarm_ruby_unaccent (1.2.0) sort_alphabetical (1.1.0) unicode_utils (>= 1.2.2) @@ -538,6 +542,9 @@ GEM thread_safe (~> 0.1) uglifier (4.2.0) execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.1) unicode-display_width (2.0.0) unicode_utils (1.4.0) uniform_notifier (1.14.2) @@ -598,6 +605,7 @@ DEPENDENCIES delayed_cron_job delayed_job_active_record devise + email_address execjs fabrication faker diff --git a/app/models/contact.rb b/app/models/contact.rb index f942a7ad..f6b0a264 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -32,6 +32,7 @@ class Contact < ActiveRecord::Base validates_by_schema validates :firstname, :lastname, :client_id, presence: true + validates :email, email: true, allow_blank: true validates :invoicing_key, uniqueness: true, allow_blank: true scope :list, -> { order(:lastname, :firstname) } diff --git a/app/models/util/email_validator.rb b/app/models/util/email_validator.rb new file mode 100644 index 00000000..c74bb48e --- /dev/null +++ b/app/models/util/email_validator.rb @@ -0,0 +1,7 @@ +class EmailValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + return if EmailAddress.valid?(value, host_validation: :syntax) + + record.errors.add attribute, (options[:message] || I18n.t('error.message.invalid_email')) + end +end diff --git a/config/locales/error_messages.de-CH.yml b/config/locales/error_messages.de-CH.yml index fdc237a7..fb4c6a54 100644 --- a/config/locales/error_messages.de-CH.yml +++ b/config/locales/error_messages.de-CH.yml @@ -15,3 +15,5 @@ de-CH: login: ldapname_not_found: 'Der Member mit dem LDAP Name: "%{ldapname}" konnte nicht gefunden werden.' + message: + invalid_email: 'ist keine gültige Email Adresse' diff --git a/test/models/contact_test.rb b/test/models/contact_test.rb new file mode 100644 index 00000000..aacc30fd --- /dev/null +++ b/test/models/contact_test.rb @@ -0,0 +1,26 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +require 'test_helper' + +class ContactTest < ActiveSupport::TestCase + def contact(email:) + Fabricate.build(:contact, email: email, client: clients(:puzzle)) + end + + test 'email can be blank' do + assert contact(email: nil).valid? + assert contact(email: '').valid? + end + + test 'email must be valid' do + assert contact(email: 'test.email+tag@example.com').valid? + refute contact(email: 'test').valid? + refute contact(email: 'example.com').valid? + refute contact(email: '@example.com').valid? + refute contact(email: 'test@email@example.com').valid? + refute contact(email: 'andré@example.com').valid? + end +end From 92d0b5391b25a191e27a8de199887db1199b1f04 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Tue, 12 Apr 2022 09:28:26 +0200 Subject: [PATCH 013/163] Update .tool-versions to match ruby version from Dockerfile --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 2db3e235..9eb38ed7 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 2.5.5 +ruby 2.7.2 From 855d8bd0d0d9f3b7e8e23c389bd308a2e2f8b9c0 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 24 Mar 2022 15:35:03 +0100 Subject: [PATCH 014/163] Implement worktime commit reminder email, fixes #55511 --- app/controllers/employees_controller.rb | 6 +- app/helpers/completable_helper.rb | 1 + app/helpers/email_helper.rb | 6 + app/jobs/commit_reminder_job.rb | 14 + app/mailers/application_mailer.rb | 7 + app/mailers/employee_mailer.rb | 13 +- app/models/employee.rb | 3 + app/models/user_notification.rb | 19 - .../worktime_commit_reminder_mail.html.haml | 12 + .../worktime_commit_reminder_mail.text.erb | 7 + .../worktime_deleted_mail.html.haml | 15 + .../worktime_deleted_mail.text.erb | 2 +- app/views/employees/settings.html.haml | 22 +- app/views/layouts/mailer.html.haml | 1464 +++++++++++++++++ config/locales/models.de-CH.yml | 1 + config/settings.yml | 1 + ...8_add_worktimes_commit_reminder_to_user.rb | 5 + db/schema.rb | 3 +- test/controllers/employees_controller_test.rb | 24 + test/fixtures/employees.yml | 8 + .../previews/employee_mailer_preview.rb | 20 + test/models/employee_test.rb | 11 + test/models/user_notification_test.rb | 46 - 23 files changed, 1628 insertions(+), 82 deletions(-) create mode 100644 app/helpers/email_helper.rb create mode 100644 app/jobs/commit_reminder_job.rb create mode 100644 app/mailers/application_mailer.rb create mode 100644 app/views/employee_mailer/worktime_commit_reminder_mail.html.haml create mode 100644 app/views/employee_mailer/worktime_commit_reminder_mail.text.erb create mode 100644 app/views/employee_mailer/worktime_deleted_mail.html.haml create mode 100644 app/views/layouts/mailer.html.haml create mode 100644 db/migrate/20220324143608_add_worktimes_commit_reminder_to_user.rb create mode 100644 test/mailers/previews/employee_mailer_preview.rb diff --git a/app/controllers/employees_controller.rb b/app/controllers/employees_controller.rb index a4ea2ff5..07e946f7 100644 --- a/app/controllers/employees_controller.rb +++ b/app/controllers/employees_controller.rb @@ -33,12 +33,14 @@ def show end def settings + @employee = @user end def update_settings - attrs = (params[:user] && params.require(:user).permit(eval_periods: [])) || {} + @employee = @user + attrs = params.require(:employee).permit(:worktimes_commit_reminder, eval_periods: []) attrs[:eval_periods] = [] if attrs[:eval_periods].blank? - if @user.update_attributes(attrs) + if @employee.update_attributes(attrs) flash[:notice] = 'Die Benutzereinstellungen wurden aktualisiert' redirect_to root_path else diff --git a/app/helpers/completable_helper.rb b/app/helpers/completable_helper.rb index 8075df62..742fbfd9 100644 --- a/app/helpers/completable_helper.rb +++ b/app/helpers/completable_helper.rb @@ -13,6 +13,7 @@ def completed_icon(date) end def recently_completed(date) + # logic should match Employee::pending_worktimes_commit date && date >= Time.zone.today.end_of_month - 1.month end diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb new file mode 100644 index 00000000..9df18482 --- /dev/null +++ b/app/helpers/email_helper.rb @@ -0,0 +1,6 @@ +module EmailHelper + def email_image_tag(image, **options) + attachments[image] = File.read(Rails.root.join("app/assets/images/#{image}")) + image_tag attachments[image].url, **options + end +end diff --git a/app/jobs/commit_reminder_job.rb b/app/jobs/commit_reminder_job.rb new file mode 100644 index 00000000..753ee7f7 --- /dev/null +++ b/app/jobs/commit_reminder_job.rb @@ -0,0 +1,14 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +class CrmSyncJob < CronJob + self.cron_expression = '0 5 2 * *' + + def perform + Employee.current.pending_worktimes_commit.where(worktimes_commit_reminder: true).each do |employee| + EmployeeMailer.worktime_commit_reminder_mail(employee).deliver_now + end + end +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 00000000..4e6af31a --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,7 @@ +class ApplicationMailer < ActionMailer::Base + layout 'mailer' + add_template_helper FormatHelper + add_template_helper EmailHelper + + default from: Settings.mailer.from +end diff --git a/app/mailers/employee_mailer.rb b/app/mailers/employee_mailer.rb index 88bdecc5..dbf7d974 100644 --- a/app/mailers/employee_mailer.rb +++ b/app/mailers/employee_mailer.rb @@ -1,6 +1,4 @@ -class EmployeeMailer < ActionMailer::Base - add_template_helper FormatHelper - +class EmployeeMailer < ApplicationMailer def worktime_deleted_mail(worktime, deleted_by) @worktime = worktime @deleted_by = deleted_by @@ -9,4 +7,13 @@ def worktime_deleted_mail(worktime, deleted_by) to: worktime.employee.email, subject: 'PuzzleTime-Eintrag wurde gelöscht') end + + def worktime_commit_reminder_mail(employee) + @employee = employee + + mail( + to: "#{employee.firstname} #{employee.lastname} <#{employee.email}>", + subject: 'PuzzleTime Zeiten freigeben' + ) + end end diff --git a/app/models/employee.rb b/app/models/employee.rb index d5eb273e..ed7fc12e 100644 --- a/app/models/employee.rb +++ b/app/models/employee.rb @@ -106,6 +106,9 @@ class Employee < ActiveRecord::Base scope :list, -> { order('lastname', 'firstname') } scope :current, -> { joins(:employments).merge(Employment.during(Period.current_day)) } + # logic should match CompletableHelper#recently_completed + scope :pending_worktimes_commit, -> { where("committed_worktimes_at < date_trunc('month', now()) - '1 day'::interval").or(where(committed_worktimes_at: nil)) } + # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, diff --git a/app/models/user_notification.rb b/app/models/user_notification.rb index f3847e3e..006085f4 100644 --- a/app/models/user_notification.rb +++ b/app/models/user_notification.rb @@ -34,7 +34,6 @@ def list_during(period = nil, current_user = nil) period.start_date, period.end_date). reorder('date_from') list = custom.to_a.concat(holiday_notifications(period)) - list.push(worktimes_commit_notification) if show_worktimes_commit_notification?(current_user) list.sort! end @@ -44,26 +43,8 @@ def holiday_notifications(period = nil) regular.collect! { |holiday| new_holiday_notification(holiday) } end - def show_worktimes_commit_notification?(employee) - today = Time.zone.today - - committed = employee && employee.committed_worktimes_at - config = Settings.committed_worktimes.notification - - show_range = today.at_end_of_month - today < config.days_at_end_of_month || - today - today.at_beginning_of_month < config.days_at_beginning_of_month - - !(committed && today - committed <= config.days_at_beginning_of_month) && show_range - end - private - def worktimes_commit_notification - new(date_from: Time.zone.today, - date_to: Time.zone.today, - message: 'Bitte Zeiten bis spätestens am ersten Arbeitstag des Monats freigeben.') - end - def new_holiday_notification(holiday) new(date_from: holiday.holiday_date, date_to: holiday.holiday_date, diff --git a/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml b/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml new file mode 100644 index 00000000..a1e7e967 --- /dev/null +++ b/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml @@ -0,0 +1,12 @@ +%h1.h3{:style => "box-sizing: border-box; margin: 0.67em 0; font-family: inherit; font-weight: 400; line-height: 1.1; color: inherit; margin-top: 20px; font-size: 24px; margin-bottom: 20px;"} + Hallo + = @employee.firstname +%div{:style => "box-sizing: border-box;"} + .lead + Es hat wieder ein neuer Monat begonnen. + Bitte gib noch deine Zeiten frei, Danke! + %br + .text-center + Liebe Grüsse + %br + Dein PuzzleTime diff --git a/app/views/employee_mailer/worktime_commit_reminder_mail.text.erb b/app/views/employee_mailer/worktime_commit_reminder_mail.text.erb new file mode 100644 index 00000000..fc200685 --- /dev/null +++ b/app/views/employee_mailer/worktime_commit_reminder_mail.text.erb @@ -0,0 +1,7 @@ +Hallo <%= @employee.firstname %> + +Es hat wieder ein neuer Monat begonnen. +Bitte gib noch deine Zeiten frei, Danke! + +Liebe Grüsse +Dein PuzzleTime diff --git a/app/views/employee_mailer/worktime_deleted_mail.html.haml b/app/views/employee_mailer/worktime_deleted_mail.html.haml new file mode 100644 index 00000000..3176c143 --- /dev/null +++ b/app/views/employee_mailer/worktime_deleted_mail.html.haml @@ -0,0 +1,15 @@ +%h1.h3{:style => "box-sizing: border-box; margin: 0.67em 0; font-family: inherit; font-weight: 400; line-height: 1.1; color: inherit; margin-top: 20px; font-size: 24px; margin-bottom: 20px;"} + Hallo + = @worktime.employee.firstname +%div{:style => "box-sizing: border-box;"} + .lead + = @deleted_by + hat deinen PuzzleTime-Eintrag gelöscht: + %br + = f(@worktime.work_date) + = @worktime + %br + .text-center + Liebe Grüsse + %br + Dein PuzzleTime diff --git a/app/views/employee_mailer/worktime_deleted_mail.text.erb b/app/views/employee_mailer/worktime_deleted_mail.text.erb index 99507d48..dd2cdeba 100644 --- a/app/views/employee_mailer/worktime_deleted_mail.text.erb +++ b/app/views/employee_mailer/worktime_deleted_mail.text.erb @@ -1,4 +1,4 @@ -Hallo <%= @worktime.employee %> +Hallo <%= @worktime.employee.firstname %> <%= @deleted_by %> hat deinen PuzzleTime-Eintrag gelöscht: <%= f(@worktime.work_date) %> <%= @worktime %> diff --git a/app/views/employees/settings.html.haml b/app/views/employees/settings.html.haml index a107a746..8f5a4bc4 100644 --- a/app/views/employees/settings.html.haml +++ b/app/views/employees/settings.html.haml @@ -12,44 +12,46 @@ %p.form-control-static = link_to 'Ändern', edit_employee_registration_path - = f.labeled('Zeitspannen') do + = f.labeled_input_field :worktimes_commit_reminder + + = f.labeled(:eval_periods) do %table.table-condensed %tr{:valign => "top"} %td %label - = multiple_check_box 'user', 'eval_periods', '0d' + = multiple_check_box 'employee', 'eval_periods', '0d' Heute %br/ %label - = multiple_check_box 'user', 'eval_periods', '-1d' + = multiple_check_box 'employee', 'eval_periods', '-1d' Gestern %td %label - = multiple_check_box 'user', 'eval_periods', '0w' + = multiple_check_box 'employee', 'eval_periods', '0w' Aktuelle Woche %br/ %label - = multiple_check_box 'user', 'eval_periods', '-1w' + = multiple_check_box 'employee', 'eval_periods', '-1w' Vorige Woche %td %label - = multiple_check_box 'user', 'eval_periods', '0m' + = multiple_check_box 'employee', 'eval_periods', '0m' Aktueller Monat %br/ %label - = multiple_check_box 'user', 'eval_periods', '-1m' + = multiple_check_box 'employee', 'eval_periods', '-1m' Voriger Monat %td %label - = multiple_check_box 'user', 'eval_periods', '0y' + = multiple_check_box 'employee', 'eval_periods', '0y' Aktuelles Jahr %br/ %label - = multiple_check_box 'user', 'eval_periods', '-1y' + = multiple_check_box 'employee', 'eval_periods', '-1y' Voriges Jahr %td %label - = multiple_check_box 'user', 'eval_periods', '0' + = multiple_check_box 'employee', 'eval_periods', '0' Total - unless @user.overtime_vacations.empty? diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml new file mode 100644 index 00000000..43e90702 --- /dev/null +++ b/app/views/layouts/mailer.html.haml @@ -0,0 +1,1464 @@ +%div{ style: "border-bottom: 1px solid #D8D8D8; height: 40px; padding-top: 3px; padidng-bottom: 10px; font-size: 22px; font-weight: 400; font-family: 'sans-serif'" } + %div{ style: "inline-block" } + + + +!!! +%html.csspositionsticky{:lang => "de", :style => "box-sizing: border-box; font-family: sans-serif; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; font-size: 10px; -webkit-tap-highlight-color: rgba(0,0,0,0);"} + %head + %meta{:charset => "utf-8"}/ + %title + PuzzleTime + %meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}/ + :css + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fCRc4EsA.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fABc4EsA.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fCBc4EsA.woff2) format('woff2'); + unicode-range: U+1F00-1FFF;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fBxc4EsA.woff2) format('woff2'); + unicode-range: U+0370-03FF;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fCxc4EsA.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fChc4EsA.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmSU5fBBc4.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu72xKOzY.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu5mxKOzY.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu7mxKOzY.woff2) format('woff2'); + unicode-range: U+1F00-1FFF;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu4WxKOzY.woff2) format('woff2'); + unicode-range: U+0370-03FF;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu7WxKOzY.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu7GxKOzY.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fCRc4EsA.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fABc4EsA.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fCBc4EsA.woff2) format('woff2'); + unicode-range: U+1F00-1FFF;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fBxc4EsA.woff2) format('woff2'); + unicode-range: U+0370-03FF;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fCxc4EsA.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;} + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmEU9fBBc4.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;} + @media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + box-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } + + .navbar { + display: none; + } + + .btn>.caret, + .dropup>.btn>.caret { + border-top-color: #000 !important; + } + + .label { + border: 1px solid #000; + } + + .table { + border-collapse: collapse !important; + } + + .table td, + .table th { + background-color: #fff !important; + } + + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } + } + + @media (min-width: 768px) { + .lead { + font-size: 21px; + } + } + @media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .dl-horizontal dd { + margin-left: 180px; + } + } + @media (min-width: 768px) { + .container { + width: 750px; + } + } + @media (min-width: 992px) { + .container { + width: 970px; + } + } + @media (min-width: 1200px) { + .container { + width: 1170px; + } + } + @media (min-width: 768px) { + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11, + .col-sm-12 { + float: left; + } + + .col-sm-1 { + width: 8.3333333333%; + } + + .col-sm-2 { + width: 16.6666666667%; + } + + .col-sm-3 { + width: 25%; + } + + .col-sm-4 { + width: 33.3333333333%; + } + + .col-sm-5 { + width: 41.6666666667%; + } + + .col-sm-6 { + width: 50%; + } + + .col-sm-7 { + width: 58.3333333333%; + } + + .col-sm-8 { + width: 66.6666666667%; + } + + .col-sm-9 { + width: 75%; + } + + .col-sm-10 { + width: 83.3333333333%; + } + + .col-sm-11 { + width: 91.6666666667%; + } + + .col-sm-12 { + width: 100%; + } + + .col-sm-pull-0 { + right: auto; + } + + .col-sm-pull-1 { + right: 8.3333333333%; + } + + .col-sm-pull-2 { + right: 16.6666666667%; + } + + .col-sm-pull-3 { + right: 25%; + } + + .col-sm-pull-4 { + right: 33.3333333333%; + } + + .col-sm-pull-5 { + right: 41.6666666667%; + } + + .col-sm-pull-6 { + right: 50%; + } + + .col-sm-pull-7 { + right: 58.3333333333%; + } + + .col-sm-pull-8 { + right: 66.6666666667%; + } + + .col-sm-pull-9 { + right: 75%; + } + + .col-sm-pull-10 { + right: 83.3333333333%; + } + + .col-sm-pull-11 { + right: 91.6666666667%; + } + + .col-sm-pull-12 { + right: 100%; + } + + .col-sm-push-0 { + left: auto; + } + + .col-sm-push-1 { + left: 8.3333333333%; + } + + .col-sm-push-2 { + left: 16.6666666667%; + } + + .col-sm-push-3 { + left: 25%; + } + + .col-sm-push-4 { + left: 33.3333333333%; + } + + .col-sm-push-5 { + left: 41.6666666667%; + } + + .col-sm-push-6 { + left: 50%; + } + + .col-sm-push-7 { + left: 58.3333333333%; + } + + .col-sm-push-8 { + left: 66.6666666667%; + } + + .col-sm-push-9 { + left: 75%; + } + + .col-sm-push-10 { + left: 83.3333333333%; + } + + .col-sm-push-11 { + left: 91.6666666667%; + } + + .col-sm-push-12 { + left: 100%; + } + + .col-sm-offset-0 { + margin-left: 0%; + } + + .col-sm-offset-1 { + margin-left: 8.3333333333%; + } + + .col-sm-offset-2 { + margin-left: 16.6666666667%; + } + + .col-sm-offset-3 { + margin-left: 25%; + } + + .col-sm-offset-4 { + margin-left: 33.3333333333%; + } + + .col-sm-offset-5 { + margin-left: 41.6666666667%; + } + + .col-sm-offset-6 { + margin-left: 50%; + } + + .col-sm-offset-7 { + margin-left: 58.3333333333%; + } + + .col-sm-offset-8 { + margin-left: 66.6666666667%; + } + + .col-sm-offset-9 { + margin-left: 75%; + } + + .col-sm-offset-10 { + margin-left: 83.3333333333%; + } + + .col-sm-offset-11 { + margin-left: 91.6666666667%; + } + + .col-sm-offset-12 { + margin-left: 100%; + } + } + @media (min-width: 992px) { + .col-md-1, + .col-md-2, + .col-md-3, + .col-md-4, + .col-md-5, + .col-md-6, + .col-md-7, + .col-md-8, + .col-md-9, + .col-md-10, + .col-md-11, + .col-md-12 { + float: left; + } + + .col-md-1 { + width: 8.3333333333%; + } + + .col-md-2 { + width: 16.6666666667%; + } + + .col-md-3 { + width: 25%; + } + + .col-md-4 { + width: 33.3333333333%; + } + + .col-md-5 { + width: 41.6666666667%; + } + + .col-md-6 { + width: 50%; + } + + .col-md-7 { + width: 58.3333333333%; + } + + .col-md-8 { + width: 66.6666666667%; + } + + .col-md-9 { + width: 75%; + } + + .col-md-10 { + width: 83.3333333333%; + } + + .col-md-11 { + width: 91.6666666667%; + } + + .col-md-12 { + width: 100%; + } + + .col-md-pull-0 { + right: auto; + } + + .col-md-pull-1 { + right: 8.3333333333%; + } + + .col-md-pull-2 { + right: 16.6666666667%; + } + + .col-md-pull-3 { + right: 25%; + } + + .col-md-pull-4 { + right: 33.3333333333%; + } + + .col-md-pull-5 { + right: 41.6666666667%; + } + + .col-md-pull-6 { + right: 50%; + } + + .col-md-pull-7 { + right: 58.3333333333%; + } + + .col-md-pull-8 { + right: 66.6666666667%; + } + + .col-md-pull-9 { + right: 75%; + } + + .col-md-pull-10 { + right: 83.3333333333%; + } + + .col-md-pull-11 { + right: 91.6666666667%; + } + + .col-md-pull-12 { + right: 100%; + } + + .col-md-push-0 { + left: auto; + } + + .col-md-push-1 { + left: 8.3333333333%; + } + + .col-md-push-2 { + left: 16.6666666667%; + } + + .col-md-push-3 { + left: 25%; + } + + .col-md-push-4 { + left: 33.3333333333%; + } + + .col-md-push-5 { + left: 41.6666666667%; + } + + .col-md-push-6 { + left: 50%; + } + + .col-md-push-7 { + left: 58.3333333333%; + } + + .col-md-push-8 { + left: 66.6666666667%; + } + + .col-md-push-9 { + left: 75%; + } + + .col-md-push-10 { + left: 83.3333333333%; + } + + .col-md-push-11 { + left: 91.6666666667%; + } + + .col-md-push-12 { + left: 100%; + } + + .col-md-offset-0 { + margin-left: 0%; + } + + .col-md-offset-1 { + margin-left: 8.3333333333%; + } + + .col-md-offset-2 { + margin-left: 16.6666666667%; + } + + .col-md-offset-3 { + margin-left: 25%; + } + + .col-md-offset-4 { + margin-left: 33.3333333333%; + } + + .col-md-offset-5 { + margin-left: 41.6666666667%; + } + + .col-md-offset-6 { + margin-left: 50%; + } + + .col-md-offset-7 { + margin-left: 58.3333333333%; + } + + .col-md-offset-8 { + margin-left: 66.6666666667%; + } + + .col-md-offset-9 { + margin-left: 75%; + } + + .col-md-offset-10 { + margin-left: 83.3333333333%; + } + + .col-md-offset-11 { + margin-left: 91.6666666667%; + } + + .col-md-offset-12 { + margin-left: 100%; + } + } + @media (min-width: 1200px) { + .col-lg-1, + .col-lg-2, + .col-lg-3, + .col-lg-4, + .col-lg-5, + .col-lg-6, + .col-lg-7, + .col-lg-8, + .col-lg-9, + .col-lg-10, + .col-lg-11, + .col-lg-12 { + float: left; + } + + .col-lg-1 { + width: 8.3333333333%; + } + + .col-lg-2 { + width: 16.6666666667%; + } + + .col-lg-3 { + width: 25%; + } + + .col-lg-4 { + width: 33.3333333333%; + } + + .col-lg-5 { + width: 41.6666666667%; + } + + .col-lg-6 { + width: 50%; + } + + .col-lg-7 { + width: 58.3333333333%; + } + + .col-lg-8 { + width: 66.6666666667%; + } + + .col-lg-9 { + width: 75%; + } + + .col-lg-10 { + width: 83.3333333333%; + } + + .col-lg-11 { + width: 91.6666666667%; + } + + .col-lg-12 { + width: 100%; + } + + .col-lg-pull-0 { + right: auto; + } + + .col-lg-pull-1 { + right: 8.3333333333%; + } + + .col-lg-pull-2 { + right: 16.6666666667%; + } + + .col-lg-pull-3 { + right: 25%; + } + + .col-lg-pull-4 { + right: 33.3333333333%; + } + + .col-lg-pull-5 { + right: 41.6666666667%; + } + + .col-lg-pull-6 { + right: 50%; + } + + .col-lg-pull-7 { + right: 58.3333333333%; + } + + .col-lg-pull-8 { + right: 66.6666666667%; + } + + .col-lg-pull-9 { + right: 75%; + } + + .col-lg-pull-10 { + right: 83.3333333333%; + } + + .col-lg-pull-11 { + right: 91.6666666667%; + } + + .col-lg-pull-12 { + right: 100%; + } + + .col-lg-push-0 { + left: auto; + } + + .col-lg-push-1 { + left: 8.3333333333%; + } + + .col-lg-push-2 { + left: 16.6666666667%; + } + + .col-lg-push-3 { + left: 25%; + } + + .col-lg-push-4 { + left: 33.3333333333%; + } + + .col-lg-push-5 { + left: 41.6666666667%; + } + + .col-lg-push-6 { + left: 50%; + } + + .col-lg-push-7 { + left: 58.3333333333%; + } + + .col-lg-push-8 { + left: 66.6666666667%; + } + + .col-lg-push-9 { + left: 75%; + } + + .col-lg-push-10 { + left: 83.3333333333%; + } + + .col-lg-push-11 { + left: 91.6666666667%; + } + + .col-lg-push-12 { + left: 100%; + } + + .col-lg-offset-0 { + margin-left: 0%; + } + + .col-lg-offset-1 { + margin-left: 8.3333333333%; + } + + .col-lg-offset-2 { + margin-left: 16.6666666667%; + } + + .col-lg-offset-3 { + margin-left: 25%; + } + + .col-lg-offset-4 { + margin-left: 33.3333333333%; + } + + .col-lg-offset-5 { + margin-left: 41.6666666667%; + } + + .col-lg-offset-6 { + margin-left: 50%; + } + + .col-lg-offset-7 { + margin-left: 58.3333333333%; + } + + .col-lg-offset-8 { + margin-left: 66.6666666667%; + } + + .col-lg-offset-9 { + margin-left: 75%; + } + + .col-lg-offset-10 { + margin-left: 83.3333333333%; + } + + .col-lg-offset-11 { + margin-left: 91.6666666667%; + } + + .col-lg-offset-12 { + margin-left: 100%; + } + } + + @media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + + .navbar-right .dropdown-menu-left { + left: 0; + right: auto; + } + } + @media (min-width: 768px) { + .nav-justified>li, + .nav-tabs.nav-justified>li { + display: table-cell; + width: 1%; + } + + .nav-justified>li>a, + .nav-tabs.nav-justified>li>a { + margin-bottom: 0; + } + } + @media (min-width: 768px) { + .nav-tabs-justified>li>a, + .nav-tabs.nav-justified>li>a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + + .nav-tabs-justified>.active>a, + .nav-tabs.nav-justified>.active>a, + .nav-tabs-justified>.active>a:hover, + .nav-tabs-justified>.active>a:focus { + border-bottom-color: #fff; + } + } + @media (min-width: 768px) { + .navbar { + border-radius: 0; + } + } + @media (min-width: 768px) { + .navbar-header { + float: left; + } + } + @media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; + } + + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + + .navbar-collapse.in { + overflow-y: visible; + } + + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } + } + @media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } + } + @media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } + } + @media (min-width: 768px) { + .container>.navbar-header, + .container>.navbar-collapse, + .container-fluid>.navbar-header, + main>.navbar-header, + .container-fluid>.navbar-collapse, + main>.navbar-collapse { + margin-right: 0; + margin-left: 0; + } + } + @media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } + } + @media (min-width: 768px) { + .navbar>.container .navbar-brand, + .navbar>.container-fluid .navbar-brand, + .navbar>main .navbar-brand { + margin-left: -15px; + } + } + @media (min-width: 768px) { + .navbar-toggle { + display: none; + } + } + @media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + + .navbar-nav .open .dropdown-menu>li>a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + + .navbar-nav .open .dropdown-menu>li>a { + line-height: 20px; + } + + .navbar-nav .open .dropdown-menu>li>a:hover, + .navbar-nav .open .dropdown-menu>li>a:focus { + background-image: none; + } + } + @media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + + .navbar-nav>li { + float: left; + } + + .navbar-nav>li>a { + padding-top: 15px; + padding-bottom: 15px; + } + } + @media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + + .navbar-form .form-control, + .navbar-form .form-action { + display: inline-block; + width: auto; + vertical-align: middle; + } + + .navbar-form .form-control-static { + display: inline-block; + } + + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control, + .navbar-form .input-group .form-action { + width: auto; + } + + .navbar-form .input-group>.form-control, + .navbar-form .input-group>.form-action { + width: 100%; + } + + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } + } + @media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + + .navbar-form .form-group:last-child { + margin-bottom: 0; + } + } + @media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + box-shadow: none; + } + } + @media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } + } + @media (min-width: 768px) { + .navbar-left { + float: left !important; + } + + .navbar-right { + float: right !important; + margin-right: -15px; + } + + .navbar-right~.navbar-right { + margin-right: 0; + } + } + @media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu>li>a { + color: #999999; + } + + .navbar-default .navbar-nav .open .dropdown-menu>li>a:hover, + .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus { + color: #61B44B; + background-color: transparent; + } + + .navbar-default .navbar-nav .open .dropdown-menu>.active>a, + .navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover, + .navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus { + color: #000; + background-color: transparent; + } + + .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a, + .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover, + .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus { + color: #D8D8D8; + background-color: transparent; + } + } + @media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header { + border-color: #2f6297; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #2f6297; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>li>a { + color: #fff; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus { + color: #fff; + background-color: #61B44B; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a, + .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus { + color: #fff; + background-color: #1E5A96; + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a, + .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus { + color: #D8D8D8; + background-color: transparent; + } + } + + @media (max-width: 767px) { + .visible-xs { + display: block !important; + } + + table.visible-xs { + display: table !important; + } + + tr.visible-xs { + display: table-row !important; + } + + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } + } + @media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } + } + @media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } + } + @media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } + } + @media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + + table.visible-sm { + display: table !important; + } + + tr.visible-sm { + display: table-row !important; + } + + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } + } + @media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } + } + @media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } + } + @media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } + } + @media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + + table.visible-md { + display: table !important; + } + + tr.visible-md { + display: table-row !important; + } + + th.visible-md, + td.visible-md { + display: table-cell !important; + } + } + @media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } + } + @media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } + } + @media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } + } + @media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + + table.visible-lg { + display: table !important; + } + + tr.visible-lg { + display: table-row !important; + } + + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } + } + @media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } + } + @media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } + } + @media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } + } + @media (max-width: 767px) { + .hidden-xs { + display: none !important; + } + } + @media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } + } + @media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } + } + @media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } + } + @media print { + .visible-print { + display: block !important; + } + + table.visible-print { + display: table !important; + } + + tr.visible-print { + display: table-row !important; + } + + th.visible-print, + td.visible-print { + display: table-cell !important; + } + } + @media print { + .visible-print-block { + display: block !important; + } + } + @media print { + .visible-print-inline { + display: inline !important; + } + } + @media print { + .visible-print-inline-block { + display: inline-block !important; + } + } + @media print { + .hidden-print { + display: none !important; + } + } + + @media print { + main { + margin: 0; + } + + .root { + display: block; + } + + a[href]:after { + content: ""; + } + + abbr[title]:after { + content: ""; + } + } + + @media (min-width: 768px) { + #main-header { + position: sticky; + z-index: 11; + top: 0; + } + } + + %body.root.login{:style => "box-sizing: border-box; margin: 0; font-family: Roboto, Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.428571429; color: #4A4A4A; background-color: #fff; min-height: 100vh; display: flex; flex-direction: column;"} + %header#main-header{:style => "box-sizing: border-box; display: block;"} + #headerbar.navbar.navbar-default.lonely{:role => "navigation", :style => "box-sizing: border-box; position: relative; margin-bottom: 0; border: 1px solid transparent; background-color: #fff; border-color: #D8D8D8; z-index: 2; flex-shrink: 0; font-weight: 300; min-height: 40px; border-width: 0; border-bottom-width: 1px;"} + .container-fluid{:style => "box-sizing: border-box; padding-right: 15px; margin-right: auto; margin-left: auto; padding-left: 0;"} + .navbar-header{:style => "box-sizing: border-box; margin-right: -15px; margin-left: -15px;"} + .navbar-brand{:style => "box-sizing: border-box; float: left; padding: 15px 15px; line-height: 20px; color: #000; height: 40px; padding-top: 3px; padding-bottom: 10px; font-size: 22px; font-weight: 400;"} + = email_image_tag("logo.svg", width: 41, height: 40, style: "box-sizing: border-box; border: 0; vertical-align: middle; display: inline-block; position: relative; top: -3px; margin-left: 30px; margin-right: 20px;") + PuzzleTime + %main#content{:role => "main", :style => "box-sizing: border-box; display: block; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; margin: 0 30px 40px; padding: 0; flex: 1;"} + = yield diff --git a/config/locales/models.de-CH.yml b/config/locales/models.de-CH.yml index 764aec6f..781d8370 100644 --- a/config/locales/models.de-CH.yml +++ b/config/locales/models.de-CH.yml @@ -210,6 +210,7 @@ de-CH: identity_card_type: Ausweistyp identity_card_valid_until: Ausweis gültig bis workplace_id: Arbeitsort + worktimes_commit_reminder: Freigabeerinnerung senden employee/marital_statuses: married: Verheiratet single: Ledig diff --git a/config/settings.yml b/config/settings.yml index 3b9383fc..480301a2 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -172,6 +172,7 @@ reports: lower_limit: 0 mailer: + from: <%= ENV['RAILS_PTIME_MAILER_FROM'] || 'time@puzzle.ch' %> employee: worktime_deleted: from: <%= ENV['RAILS_PTIME_MAILER_EMPLOYEE_WORKTIME_DELETED_FROM'] || 'time@puzzle.ch' %> diff --git a/db/migrate/20220324143608_add_worktimes_commit_reminder_to_user.rb b/db/migrate/20220324143608_add_worktimes_commit_reminder_to_user.rb new file mode 100644 index 00000000..3fbc995a --- /dev/null +++ b/db/migrate/20220324143608_add_worktimes_commit_reminder_to_user.rb @@ -0,0 +1,5 @@ +class AddWorktimesCommitReminderToUser < ActiveRecord::Migration[5.2] + def change + add_column :employees, :worktimes_commit_reminder, :boolean, default: true, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 0bdad825..b75f9618 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_03_24_100350) do +ActiveRecord::Schema.define(version: 2022_03_24_143608) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -196,6 +196,7 @@ t.datetime "created_at", default: -> { "now()" }, null: false t.datetime "updated_at", default: -> { "now()" }, null: false t.bigint "workplace_id" + t.boolean "worktimes_commit_reminder", default: true, null: false t.index ["department_id"], name: "index_employees_on_department_id" t.index ["shortname"], name: "chk_unique_name", unique: true t.index ["workplace_id"], name: "index_employees_on_workplace_id" diff --git a/test/controllers/employees_controller_test.rb b/test/controllers/employees_controller_test.rb index 9a07f824..40944715 100644 --- a/test/controllers/employees_controller_test.rb +++ b/test/controllers/employees_controller_test.rb @@ -12,6 +12,23 @@ class EmployeesControllerTest < ActionController::TestCase teardown -> { Crm.instance = nil } + def test_settings + get :settings, params: test_params(id: test_entry.id) + assert_response :success + assert_template 'employees/settings' + assert_attrs_equal test_entry.attributes.slice(:worktimes_commit_reminder, :eval_periods) + end + + def test_update_settings + assert_no_difference("#{model_class.name}.count") do + put :update_settings, params: test_params(id: test_entry.id, + model_identifier => test_settings_attrs) + assert_equal [], entry.errors.full_messages + end + assert_attrs_equal(test_settings_attrs) + assert_redirected_to root_path + end + def test_show_with_crm_existing_profile Crm.instance = Crm::Base.new Crm.instance.expects(:find_people_by_email).with(test_entry.email).returns([OpenStruct.new(id: 123)]) @@ -68,4 +85,11 @@ def test_entry_attrs probation_period_end_date: Date.new(2015, 10, 3), nationalities: ['CH', 'DE'] } end + + def test_settings_attrs + { + worktimes_commit_reminder: false, + eval_periods: ["-1m", "0"] + } + end end diff --git a/test/fixtures/employees.yml b/test/fixtures/employees.yml index 52ce94b1..4010664b 100644 --- a/test/fixtures/employees.yml +++ b/test/fixtures/employees.yml @@ -48,6 +48,7 @@ half_year_maria: email: bla@bla.ch management: true eval_periods: ["-1m", "0"] + worktimes_commit_reminder: true various_pedro: id: 2 @@ -58,6 +59,7 @@ various_pedro: email: bol@bla.ch social_insurance: 123.4567.8910.11 management: false + worktimes_commit_reminder: true next_year_pablo: id: 3 @@ -67,6 +69,7 @@ next_year_pablo: encrypted_password: <%= Employee.encode('ptime') %> email: ps@bla.ch management: false + worktimes_commit_reminder: true left_this_year_macy: id: 4 @@ -76,6 +79,7 @@ left_this_year_macy: encrypted_password: <%= Employee.encode('ptime') %> email: mg@bla.ch management: false + worktimes_commit_reminder: true long_time_john: id: 5 @@ -92,6 +96,7 @@ long_time_john: - UK graduation: Klubschule department: devone + worktimes_commit_reminder: true pascal: id: 6 @@ -103,6 +108,7 @@ pascal: management: false department: devtwo workplace: bern + worktimes_commit_reminder: true mark: id: 7 @@ -120,6 +126,7 @@ mark: - '0y' - '-1y' workplace: jona + worktimes_commit_reminder: true lucien: id: 8 @@ -130,3 +137,4 @@ lucien: email: lw@bla.ch management: false department: devtwo + worktimes_commit_reminder: true diff --git a/test/mailers/previews/employee_mailer_preview.rb b/test/mailers/previews/employee_mailer_preview.rb new file mode 100644 index 00000000..8dcfee1b --- /dev/null +++ b/test/mailers/previews/employee_mailer_preview.rb @@ -0,0 +1,20 @@ +class EmployeeMailerPreview < ActionMailer::Preview + def worktime_deleted_mail + worktime_user = Employee.new(email: 'user@example.com', firstname: 'Peter', lastname: 'Puzzler') + worktime = Ordertime.new( + employee: worktime_user, + account: WorkItem.new(name: 'Lieblingsprojekt', path_shortnames: 'TOP-FAV'), + work_date: Date.today, + hours: 4.33, + report_type: HoursDayType::INSTANCE + ) + management_user = Employee.new(firstname: 'Mad', lastname: 'Manager') + EmployeeMailer.worktime_deleted_mail(worktime, management_user) + end + + def worktime_commit_reminder_mail + employee = Employee.new(email: 'user@example.com', firstname: 'Peter', lastname: 'Puzzler') + EmployeeMailer.worktime_commit_reminder_mail(employee) + end +end + diff --git a/test/models/employee_test.rb b/test/models/employee_test.rb index 317d42e0..fd813410 100644 --- a/test/models/employee_test.rb +++ b/test/models/employee_test.rb @@ -141,6 +141,17 @@ def test_alltime_main_work_items assert_arrays_match employees(:long_time_john, :various_pedro, :next_year_pablo), Employee.current end + test '#pending_worktimes_commit scope' do + Employee.update_all(committed_worktimes_at: nil) + assert Employee.pending_worktimes_commit.present? + + Employee.update_all(committed_worktimes_at: Date.today.beginning_of_month - 1.day) + assert Employee.pending_worktimes_commit.blank? + + Employee.update_all(committed_worktimes_at: Date.today.beginning_of_month - 2.day) + assert Employee.pending_worktimes_commit.present? + end + private def year_period(employee) diff --git a/test/models/user_notification_test.rb b/test/models/user_notification_test.rb index 64fa6668..aebe8db7 100644 --- a/test/models/user_notification_test.rb +++ b/test/models/user_notification_test.rb @@ -28,50 +28,4 @@ class UserNotificationTest < ActiveSupport::TestCase assert_not_includes messages, 'foo' assert_includes messages, 'baz' end - - test 'worktimes commit notification not shown before end of month' do - ActiveSupport::TimeZone.any_instance.stubs(today: Date.new(2015, 9, 23)) - e = employees(:pascal) - assert !UserNotification.show_worktimes_commit_notification?(e) - end - - test 'worktimes commit notification shown at end of month' do - ActiveSupport::TimeZone.any_instance.stubs(today: Date.new(2015, 9, 24)) - e = employees(:pascal) - assert UserNotification.show_worktimes_commit_notification?(e) - end - - test 'worktimes commit notification shown at beginning of month' do - ActiveSupport::TimeZone.any_instance.stubs(today: Date.new(2015, 9, 3)) - e = employees(:pascal) - e.update!(committed_worktimes_at: '2015-7-31') - assert UserNotification.show_worktimes_commit_notification?(e) - end - - test 'worktimes commit notification not shown after beginning of month' do - ActiveSupport::TimeZone.any_instance.stubs(today: Date.new(2015, 9, 4)) - e = employees(:pascal) - assert !UserNotification.show_worktimes_commit_notification?(e) - end - - test 'worktimes commit notification not shown if committed this period end of month' do - ActiveSupport::TimeZone.any_instance.stubs(today: Date.new(2015, 9, 29)) - e = employees(:pascal) - e.update!(committed_worktimes_at: '2015-9-30') - assert !UserNotification.show_worktimes_commit_notification?(e) - end - - test 'worktimes commit notification not shown if committed this period beginning of month' do - ActiveSupport::TimeZone.any_instance.stubs(today: Date.new(2015, 10, 2)) - e = employees(:pascal) - e.update!(committed_worktimes_at: '2015-9-30') - assert !UserNotification.show_worktimes_commit_notification?(e) - end - - test 'worktimes commit notification not shown if committed next period beginning of month' do - ActiveSupport::TimeZone.any_instance.stubs(today: Date.new(2015, 10, 2)) - e = employees(:pascal) - e.update!(committed_worktimes_at: '2015-10-31') - assert !UserNotification.show_worktimes_commit_notification?(e) - end end From fdc7a74cc2c86a10a1fb76c167e2eed322c97234 Mon Sep 17 00:00:00 2001 From: Thomas Burkhalter Date: Tue, 12 Apr 2022 18:14:53 +0200 Subject: [PATCH 015/163] Update tested ruby versions --- .github/workflows/rails-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rails-tests.yaml b/.github/workflows/rails-tests.yaml index 730a521c..61a835a4 100644 --- a/.github/workflows/rails-tests.yaml +++ b/.github/workflows/rails-tests.yaml @@ -20,7 +20,7 @@ jobs: strategy: matrix: - ruby: [ '2.5', '2.6', '2.7' ] + ruby: [ '2.6', '2.7' ] services: postgres: From ad637d408bf71bcc1290a5b9a1c74ad6b7cdad14 Mon Sep 17 00:00:00 2001 From: Thomas Burkhalter Date: Tue, 12 Apr 2022 19:16:15 +0200 Subject: [PATCH 016/163] Fix failing integration test --- test/integration/show_order_test.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/integration/show_order_test.rb b/test/integration/show_order_test.rb index 5a9596f7..55efb6cd 100644 --- a/test/integration/show_order_test.rb +++ b/test/integration/show_order_test.rb @@ -6,7 +6,10 @@ require 'test_helper' class ShowOrder < ActionDispatch::IntegrationTest + setup :crm_start setup :login + teardown :crm_stop + ADDITIONAL_CRM_LABEL_TEXT = 'Weitere Highrise IDs' @@ -29,6 +32,16 @@ class ShowOrder < ActionDispatch::IntegrationTest private + def crm_start + Settings.highrise.api_token = 'test' + Crm.init + end + + def crm_stop + Settings.highrise.api_token = nil + Crm.init + end + def login login_as(:mark) visit(order_path(order)) From 11d265bfcf2920f0e1e41e25c4fef554f173c270 Mon Sep 17 00:00:00 2001 From: Thomas Burkhalter Date: Tue, 12 Apr 2022 20:00:02 +0200 Subject: [PATCH 017/163] Rollback pg version update --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index bae81989..83818abc 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' gem 'rails', '~> 5.2.x' -gem 'pg' +gem 'pg', '~> 0.21.0' gem 'nochmal', github: 'puzzle/nochmal' diff --git a/Gemfile.lock b/Gemfile.lock index c6d20c1d..1a81acd6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -365,7 +365,7 @@ GEM parser (3.0.2.0) ast (~> 2.4.1) pdf-core (0.9.0) - pg (1.2.3) + pg (0.21.0) prawn (2.4.0) pdf-core (~> 0.9.0) ttfunk (~> 1.7) @@ -633,7 +633,7 @@ DEPENDENCIES omniauth-rails_csrf_protection omniauth-saml paper_trail - pg + pg (~> 0.21.0) prawn prometheus_exporter protective @@ -674,4 +674,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.2.27 + 2.3.9 From e489b4754b4659c46f8bf1dbe235959118a1b458 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 14 Apr 2022 15:39:27 +0200 Subject: [PATCH 018/163] Ignore blank ENV['PROMETHEUS_EXPORTER_HOST'] --- config/initializers/prometheus.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/prometheus.rb b/config/initializers/prometheus.rb index 3937f88f..325bb7c9 100644 --- a/config/initializers/prometheus.rb +++ b/config/initializers/prometheus.rb @@ -1,4 +1,4 @@ -if !Rails.env.test? && ENV['PROMETHEUS_EXPORTER_HOST'] +unless Rails.env.test? || ENV['PROMETHEUS_EXPORTER_HOST'].blank? require 'prometheus_exporter/middleware' require 'prometheus_exporter/instrumentation' From ebb12c7cde0fb795720c2d245eab3374119c514e Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 14 Apr 2022 16:34:48 +0200 Subject: [PATCH 019/163] Fix job name, add links to mailer template, refs #55511 --- app/jobs/commit_reminder_job.rb | 4 ++-- app/models/employee.rb | 1 + app/models/employment.rb | 1 + app/models/util/period.rb | 5 +++++ .../worktime_commit_reminder_mail.html.haml | 10 ++++++++-- .../worktime_commit_reminder_mail.text.erb | 4 +++- config/application.rb | 5 +++++ 7 files changed, 25 insertions(+), 5 deletions(-) diff --git a/app/jobs/commit_reminder_job.rb b/app/jobs/commit_reminder_job.rb index 753ee7f7..0ae90537 100644 --- a/app/jobs/commit_reminder_job.rb +++ b/app/jobs/commit_reminder_job.rb @@ -3,11 +3,11 @@ # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. -class CrmSyncJob < CronJob +class CommitReminderJob < CronJob self.cron_expression = '0 5 2 * *' def perform - Employee.current.pending_worktimes_commit.where(worktimes_commit_reminder: true).each do |employee| + Employee.active_employed_last_month.pending_worktimes_commit.where(worktimes_commit_reminder: true).each do |employee| EmployeeMailer.worktime_commit_reminder_mail(employee).deliver_now end end diff --git a/app/models/employee.rb b/app/models/employee.rb index ed7fc12e..30079be8 100644 --- a/app/models/employee.rb +++ b/app/models/employee.rb @@ -108,6 +108,7 @@ class Employee < ActiveRecord::Base # logic should match CompletableHelper#recently_completed scope :pending_worktimes_commit, -> { where("committed_worktimes_at < date_trunc('month', now()) - '1 day'::interval").or(where(committed_worktimes_at: nil)) } + scope :active_employed_last_month, -> { joins(:employments).merge(Employment.active.during(Period.previous_month)) } # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable diff --git a/app/models/employment.rb b/app/models/employment.rb index 5da9624c..7c748234 100644 --- a/app/models/employment.rb +++ b/app/models/employment.rb @@ -43,6 +43,7 @@ class Employment < ActiveRecord::Base before_create :update_previous_end_date scope :list, -> { order('start_date DESC') } + scope :active, -> { where('"employments"."percent" > 0') } class << self def during(period) diff --git a/app/models/util/period.rb b/app/models/util/period.rb index 0db3a396..0af14a64 100644 --- a/app/models/util/period.rb +++ b/app/models/util/period.rb @@ -68,6 +68,11 @@ def past_month(date = Time.zone.today, options = {}) new(date - 28, date + 7, options[:label], options[:shortcut]) end + def previous_month(date = Time.zone.today) + date = date.to_date if date.is_a?(Time) + new(date.beginning_of_month - 1.month, date.beginning_of_month - 1.day) + end + def coming_month(date = Time.zone.today, options = {}) date = date.to_date if date.is_a?(Time) date -= (date.wday - 1) % 7 diff --git a/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml b/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml index a1e7e967..ff517271 100644 --- a/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml +++ b/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml @@ -4,9 +4,15 @@ %div{:style => "box-sizing: border-box;"} .lead Es hat wieder ein neuer Monat begonnen. - Bitte gib noch deine Zeiten frei, Danke! + %br + Bitte gib im + = link_to 'PuzzleTime', root_url + noch deine Zeiten frei, Danke! + %br + Falls du keine Erinnerungsmails wünschst, kannst du sie + = link_to 'hier', settings_employees_url + abschalten. %br - .text-center Liebe Grüsse %br Dein PuzzleTime diff --git a/app/views/employee_mailer/worktime_commit_reminder_mail.text.erb b/app/views/employee_mailer/worktime_commit_reminder_mail.text.erb index fc200685..0558067c 100644 --- a/app/views/employee_mailer/worktime_commit_reminder_mail.text.erb +++ b/app/views/employee_mailer/worktime_commit_reminder_mail.text.erb @@ -1,7 +1,9 @@ Hallo <%= @employee.firstname %> Es hat wieder ein neuer Monat begonnen. -Bitte gib noch deine Zeiten frei, Danke! +Bitte gib im PuzzleTime noch deine Zeiten frei, Danke! + +Falls du keine Erinnerungsmails wünschst, kannst du sie hier abschalten: <%= settings_employees_url %> Liebe Grüsse Dein PuzzleTime diff --git a/config/application.rb b/config/application.rb index f88aaece..3a2427cc 100644 --- a/config/application.rb +++ b/config/application.rb @@ -68,6 +68,11 @@ class Application < Rails::Application config.active_job.queue_adapter = :delayed_job + config.action_mailer.default_url_options = { + protocol: 'https', + host: ENV['RAILS_MAIL_URL_HOST'].presence || 'example.com' + } + config.to_prepare do |_| Crm.init Invoicing.init From cbf8615b7b851965ea5092dff4e861c0ff2fb081 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 14 Apr 2022 16:47:43 +0200 Subject: [PATCH 020/163] Render workplace in employee_master_data, refs #57579 --- app/views/employee_master_data/_attrs.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/employee_master_data/_attrs.html.haml b/app/views/employee_master_data/_attrs.html.haml index 23e94c41..2b4ba9b2 100644 --- a/app/views/employee_master_data/_attrs.html.haml +++ b/app/views/employee_master_data/_attrs.html.haml @@ -76,6 +76,7 @@ %h4 Anstellung = labeled_attr(@employee, :department_id) = labeled_attr(@employee, :current_percent) + = labeled_attr(@employee, :workplace_id) = labeled('Rollen', format_current_employment_roles(@employee, tag(:br))) - if @employee.additional_information.present? From b2b5be826c90cb40e0a3771b2a50e06d47269e90 Mon Sep 17 00:00:00 2001 From: Bruno Santschi Date: Sat, 23 Apr 2022 06:43:00 +0200 Subject: [PATCH 021/163] Update version, CHANGELOG.md --- CHANGELOG.md | 27 +++++++++++++++++---------- config/version.rb | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4810b59d..f9153b85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,18 @@ # master branch unreleased ### Improvements -* **Spesen:** Spesenbelege werden nun beim Hochladen herunterskaliert -* **Kundenauswertung:** Auftrag verlinkt, um schneller hin und her navigieren zu können -* **Mitarbeiter-Stammdaten:** - + Attribut "Telefon privat" umbenannt in "Mobiltelefon" - + Anstellungsprozente und Funktionsanteile können nun in 2.5% Schritten konfiguriert werden - + Neues Attribut "Arbeitsort", verfügbare Werte konfigurierbar unter "Verwalten" -* **Mitarbeiterliste:** Sortierbar gemacht nach Vorname, Nachname -* **Zeiterfassung:** Leerschläge vor und nach der Ticketnummer werden entfernt +* **Rechnungsstellung:** Umstellung auf SmallInvoice APIv2 (vorher v1) -# 2.7 +# 2.8 +### Features +* **Login:** Login wird auf SSO (Keycloak, Devise) umgestellt +* **Zeitfreigabe:** Neu wird eine Erinnerung per E-Mail versendet, wenn die Zeiten noch nicht freigegeben wurden. +### Improvements +* **Stammdaten:** In den Stammdaten der Members wird neu der vertragliche Arbeitsort geführt +* **Log:** Die Änderungen der Anstellungen (Pensen, Funktionen) wird neu im Members-Log protokolliert +# 2.7 ### Features * **Login:** Unterstützt nun Omniauth mit Keycloak und/oder SAML -* **Rechnungsstellung:** Umstellung auf SmallInvoice APIv2 (vorher v1) * **Business Intelligence:** Wir können jetzt Verbindung zu einer InfluxDB herstellen, die wichtige Kennzahlen als Timeseries speichert ### Improvements * **Update:** Update auf Ruby 2.7 @@ -22,6 +21,14 @@ * **Rechnungen:** Werden jetzt auf 5 Rappen gerundet * **Support** X-Sendfile-Header kann jetzt per Umgebungsvariable gesetzt werden * **Dokumentation:** Das Herokusetup ist jetzt dokumentiert +* **Spesen:** Spesenbelege werden nun beim Hochladen herunterskaliert +* **Kundenauswertung:** Auftrag verlinkt, um schneller hin und her navigieren zu können +* **Mitarbeiter-Stammdaten:** + + Attribut "Telefon privat" umbenannt in "Mobiltelefon" + + Anstellungsprozente und Funktionsanteile können nun in 2.5% Schritten konfiguriert werden + + Neues Attribut "Arbeitsort", verfügbare Werte konfigurierbar unter "Verwalten" +* **Mitarbeiterliste:** Sortierbar gemacht nach Vorname, Nachname +* **Zeiterfassung:** Leerschläge vor und nach der Ticketnummer werden entfernt ### Bug fixes * **Überzeitexport:** Header sind jetzt aussagekräftiger * **Verbleibende Arbeitszeit:** Berechnung korrigiert wenn Überstundenkompensationen in der Zukunft liegen diff --git a/config/version.rb b/config/version.rb index f50e153b..3bcf319b 100644 --- a/config/version.rb +++ b/config/version.rb @@ -1,3 +1,3 @@ module Puzzletime - VERSION = '2.7' + VERSION = '2.8' end From 963cd4a2bf50b50bf1d9a0ff4d4cc43c7d681375 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 28 Apr 2022 13:55:48 +0200 Subject: [PATCH 022/163] Beautify SSO login button text, refs #51134 --- app/views/devise/shared/_links.html.haml | 3 ++- config/settings.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/devise/shared/_links.html.haml b/app/views/devise/shared/_links.html.haml index e986cb90..6b9c859d 100644 --- a/app/views/devise/shared/_links.html.haml +++ b/app/views/devise/shared/_links.html.haml @@ -21,7 +21,8 @@ - if devise_mapping.omniauthable? - resource_class.omniauth_providers.each do |provider| - if Settings.auth&.omniauth[provider]&.active - = link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", + - provider_label = Settings.auth.omniauth.public_send(provider).label || OmniAuth::Utils.camelize(provider) + = link_to "Mit #{provider_label} anmelden", public_send("#{resource_name}_#{provider}_omniauth_authorize_path"), method: :post, class: 'btn btn-primary' diff --git a/config/settings.yml b/config/settings.yml index 480301a2..2eaae0f9 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -54,6 +54,7 @@ auth: active: <%= ENV['AUTH_DB_ACTIVE'] == 'true' %> omniauth: keycloakopenid: + label: 'Puzzle SSO' active: <%= ENV['AUTH_KEYCLOAK_ACTIVE'] == 'true' %> host: <%= ENV['AUTH_KEYCLOAK_HOST'] %> realm: <%= ENV['AUTH_KEYCLOAK_REALM'] %> From 8372c874c25426afba403de1d12383799a6fa2f2 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 28 Apr 2022 14:18:24 +0200 Subject: [PATCH 023/163] Beautify commit reminder email, refs #55511 --- .../worktime_commit_reminder_mail.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml b/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml index ff517271..2e9168b6 100644 --- a/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml +++ b/app/views/employee_mailer/worktime_commit_reminder_mail.html.haml @@ -9,10 +9,11 @@ = link_to 'PuzzleTime', root_url noch deine Zeiten frei, Danke! %br - Falls du keine Erinnerungsmails wünschst, kannst du sie - = link_to 'hier', settings_employees_url - abschalten. - %br + .lead Liebe Grüsse %br Dein PuzzleTime + %br + Falls du keine Erinnerungsmails wünschst, kannst du sie + = link_to 'hier', settings_employees_url + abschalten. From 3657666e0a5701166d7da0c8816b32b2140a6420 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 28 Apr 2022 17:34:05 +0200 Subject: [PATCH 024/163] Hotfix for config/initializers/nil_to_d.rb --- config/initializers/nil_to_d.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/nil_to_d.rb b/config/initializers/nil_to_d.rb index 3608ac5c..4c054b15 100644 --- a/config/initializers/nil_to_d.rb +++ b/config/initializers/nil_to_d.rb @@ -5,6 +5,6 @@ class NilClass def to_d - BigDecimal.new(0) + 0.to_d end end From 0dac30ed47d4f02f5713a030256b781c0affbefe Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 29 Apr 2022 08:55:41 +0200 Subject: [PATCH 025/163] Update translation for flash message when unauthenticated, refs #51134 --- config/locales/devise.de-CH.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/devise.de-CH.yml b/config/locales/devise.de-CH.yml index f580cf69..930cd861 100644 --- a/config/locales/devise.de-CH.yml +++ b/config/locales/devise.de-CH.yml @@ -14,7 +14,7 @@ de-CH: last_attempt: "Du hast noch einen weiteren Versuch, bevor dein Account gesperrt wird." not_found_in_database: "Ungültige %{authentication_keys} oder ungültiges Passwort." timeout: "Deine Session ist abgelaufen. Bitte melde dich erneut an." - unauthenticated: "Du musst dich anmelden oder registrieren bevor du fortfahren kannst." + unauthenticated: "Du musst dich anmelden bevor du fortfahren kannst." unconfirmed: "Du musst deine Email Adresse bestätigen, bevor du fortfahren kannst." mailer: confirmation_instructions: From 8fbe4d9a27975798a469e2d1babc2086bdd6a430 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 29 Apr 2022 10:35:09 +0200 Subject: [PATCH 026/163] HTML Emails: show logo inline, refs #55511 --- app/helpers/email_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb index 9df18482..9e3417ba 100644 --- a/app/helpers/email_helper.rb +++ b/app/helpers/email_helper.rb @@ -1,6 +1,6 @@ module EmailHelper def email_image_tag(image, **options) - attachments[image] = File.read(Rails.root.join("app/assets/images/#{image}")) + attachments.inline[image] = File.read(Rails.root.join("app/assets/images/#{image}")) image_tag attachments[image].url, **options end end From 2243481fe0deeb90082eef38907e2dc0f3dd62a8 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Mon, 2 May 2022 09:31:33 +0200 Subject: [PATCH 027/163] Change email from address to no-reply@puzzle.ch, schedule CommitReminderJob, refs #55511 --- app/mailers/employee_mailer.rb | 3 +-- config/application.rb | 1 + config/settings.yml | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/mailers/employee_mailer.rb b/app/mailers/employee_mailer.rb index dbf7d974..139db27f 100644 --- a/app/mailers/employee_mailer.rb +++ b/app/mailers/employee_mailer.rb @@ -3,8 +3,7 @@ def worktime_deleted_mail(worktime, deleted_by) @worktime = worktime @deleted_by = deleted_by - mail(from: Settings.mailer.employee.worktime_deleted.from, - to: worktime.employee.email, + mail(to: worktime.employee.email, subject: 'PuzzleTime-Eintrag wurde gelöscht') end diff --git a/config/application.rb b/config/application.rb index 3a2427cc..8c4dfc58 100644 --- a/config/application.rb +++ b/config/application.rb @@ -77,6 +77,7 @@ class Application < Rails::Application Crm.init Invoicing.init BI.init + CommitReminderJob.schedule end end diff --git a/config/settings.yml b/config/settings.yml index 2eaae0f9..26f636d8 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -173,10 +173,7 @@ reports: lower_limit: 0 mailer: - from: <%= ENV['RAILS_PTIME_MAILER_FROM'] || 'time@puzzle.ch' %> - employee: - worktime_deleted: - from: <%= ENV['RAILS_PTIME_MAILER_EMPLOYEE_WORKTIME_DELETED_FROM'] || 'time@puzzle.ch' %> + from: <%= ENV['RAILS_PTIME_MAILER_FROM'] || 'no-reply@puzzle.ch' %> influxdb: export: <%= ENV['RAILS_INFLUXDB_EXPORT'] == '1' %> From eb649ec904fd4a4013cdd971f24aae9796e803f4 Mon Sep 17 00:00:00 2001 From: Thomas Burkhalter Date: Tue, 3 May 2022 14:59:55 +0200 Subject: [PATCH 028/163] Fix ci build --- Gemfile | 1 + Gemfile.lock | 3 +++ images/s2i/Dockerfile | 1 + 3 files changed, 5 insertions(+) diff --git a/Gemfile b/Gemfile index 83818abc..fafc2ba4 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source 'https://rubygems.org' gem 'rails', '~> 5.2.x' gem 'pg', '~> 0.21.0' +gem 'activerecord-nulldb-adapter' gem 'nochmal', github: 'puzzle/nochmal' diff --git a/Gemfile.lock b/Gemfile.lock index 1a81acd6..b2b0ec12 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,6 +50,8 @@ GEM activemodel (= 5.2.7) activesupport (= 5.2.7) arel (>= 9.0) + activerecord-nulldb-adapter (0.8.0) + activerecord (>= 5.2.0, < 7.1) activeresource (5.1.1) activemodel (>= 5.0, < 7) activemodel-serializers-xml (~> 1.0) @@ -579,6 +581,7 @@ PLATFORMS ruby DEPENDENCIES + activerecord-nulldb-adapter acts_as_tree airbrake annotate diff --git a/images/s2i/Dockerfile b/images/s2i/Dockerfile index bb63d54d..9d343d5b 100644 --- a/images/s2i/Dockerfile +++ b/images/s2i/Dockerfile @@ -27,3 +27,4 @@ RUN bash -c 'gem install bundler:2.2.5 --no-document' USER 1001 ENV RAILS_ENV=production +ENV RAILS_DB_ADAPTER=nulldb From 584f3f5f6a5d8348f527c8d84dd41df436d77d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20R=C3=A4z?= Date: Wed, 6 Jul 2022 09:49:20 +0200 Subject: [PATCH 029/163] Remove unmaintained ability to export BI stats to influx --- Gemfile | 2 - Gemfile.lock | 10 -- app/domain/bi.rb | 30 ------ app/domain/bi/export.rb | 38 -------- app/domain/bi/influx_ensure_bucket.rb | 39 -------- app/domain/bi/influx_exporter.rb | 53 ----------- app/domain/crm/highrise_stats.rb | 105 --------------------- app/domain/order/report/bi.rb | 90 ------------------ app/domain/reports/bi_workload.rb | 70 -------------- app/domain/reports/revenue/bi.rb | 72 --------------- app/jobs/bi_export_job.rb | 12 --- config/application.rb | 1 - config/initializers/inflections.rb | 6 +- config/settings.yml | 8 -- config/settings/development.yml | 3 - doc/user/README.md | 1 - doc/user/influx_metrics.md | 91 ------------------- docker-compose.yml | 17 +--- test/domain/order/report/bi_test.rb | 50 ---------- test/domain/reports/bi_workload_test.rb | 108 ---------------------- test/domain/reports/revenue/bi_test.rb | 116 ------------------------ 21 files changed, 2 insertions(+), 920 deletions(-) delete mode 100644 app/domain/bi.rb delete mode 100644 app/domain/bi/export.rb delete mode 100644 app/domain/bi/influx_ensure_bucket.rb delete mode 100644 app/domain/bi/influx_exporter.rb delete mode 100644 app/domain/crm/highrise_stats.rb delete mode 100644 app/domain/order/report/bi.rb delete mode 100644 app/domain/reports/bi_workload.rb delete mode 100644 app/domain/reports/revenue/bi.rb delete mode 100644 app/jobs/bi_export_job.rb delete mode 100644 doc/user/influx_metrics.md delete mode 100644 test/domain/order/report/bi_test.rb delete mode 100644 test/domain/reports/bi_workload_test.rb delete mode 100644 test/domain/reports/revenue/bi_test.rb diff --git a/Gemfile b/Gemfile index fafc2ba4..35375953 100644 --- a/Gemfile +++ b/Gemfile @@ -24,8 +24,6 @@ gem 'fast_jsonapi' gem 'haml' gem 'highrise' gem 'image_processing' -gem 'influxdb-client' -gem 'influxdb-client-apis' gem 'jbuilder' gem 'kaminari' gem 'kaminari-bootstrap' diff --git a/Gemfile.lock b/Gemfile.lock index b2b0ec12..b6dfbbb9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -221,8 +221,6 @@ GEM email_address (0.2.2) simpleidn erubi (1.10.0) - ethon (0.14.0) - ffi (>= 1.15.0) execjs (2.8.1) fabrication (2.22.0) faker (2.18.0) @@ -273,10 +271,6 @@ GEM image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - influxdb-client (2.1.0) - influxdb-client-apis (2.1.0) - influxdb-client (= 2.1.0) - typhoeus (~> 1.0, >= 1.0.1) jbuilder (2.11.2) activesupport (>= 5.0.0) jmespath (1.4.0) @@ -538,8 +532,6 @@ GEM turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) - typhoeus (1.4.0) - ethon (>= 0.9.0) tzinfo (1.2.9) thread_safe (~> 0.1) uglifier (4.2.0) @@ -618,8 +610,6 @@ DEPENDENCIES headless highrise image_processing - influxdb-client - influxdb-client-apis jbuilder jquery-rails jquery-ui-rails diff --git a/app/domain/bi.rb b/app/domain/bi.rb deleted file mode 100644 index c558369f..00000000 --- a/app/domain/bi.rb +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -module BI - class ConfigurationError < StandardError; end - - def self.init - if Settings.influxdb&.export - check_config! - - BIExportJob.schedule if Delayed::Job.table_exists? - end - end - - private - - def self.check_config! - settings = Settings.influxdb - - missing = %i[host port org token use_ssl].select do |setting| - settings.send(setting).nil? - end - - return if missing.empty? - - raise ConfigurationError, "Settings influxdb.{#{missing.join(', ')}} missing" - end -end diff --git a/app/domain/bi/export.rb b/app/domain/bi/export.rb deleted file mode 100644 index 643baedb..00000000 --- a/app/domain/bi/export.rb +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -module BI - class Export - def run - stats = [ - [Reports::Revenue::BI.new.stats, 'Revenue'], - [Reports::BIWorkload.new.stats, 'Workload'], - [Order::Report::BI.new.stats, 'Orders'], - [role_distribution, 'RoleDistribution'] - ] - stats << [Crm::HighriseStats.new.stats, 'Highrise'] if highrise_enabled? - - export(stats) - end - - private - - def highrise_enabled? - Crm.instance.is_a?(Crm::Highrise) - end - - def role_distribution - # RoleDistributionReport ... - [] - end - - def export(stats) - exporter = InfluxExporter.new(Settings.influxdb) - - exporter.ensure_buckets(stats.map { |_s, bucket| bucket }) - stats.each { |data, bucket| exporter.export(data, bucket) } - end - end -end diff --git a/app/domain/bi/influx_ensure_bucket.rb b/app/domain/bi/influx_ensure_bucket.rb deleted file mode 100644 index fa5e55b8..00000000 --- a/app/domain/bi/influx_ensure_bucket.rb +++ /dev/null @@ -1,39 +0,0 @@ -module BI - class InfluxEnsureBucket - class OrgNotFound < StandardError; end - - def initialize(client, org) - @client = client - @api = InfluxDB2::API::Client.new(client) - @org = find_org(org, @api) - end - - def bucket(bucket) - return if bucket_exists?(bucket, @org, @api) - - create_bucket(bucket, @org, @api) - end - - private - - def find_org(name, api) - org = - api.create_organizations_api.get_orgs.orgs.select do |it| - it.name == name - end.first - - raise OrgNotFound, "No org with name #{name} present" if org.nil? - org - end - - def bucket_exists?(name, org, api) - api.create_buckets_api.get_buckets(name: name).buckets.any? - end - - def create_bucket(name, org, api) - request = - InfluxDB2::API::PostBucketRequest.new(org_id: org.id, name: name) - api.create_buckets_api.post_buckets(request) - end - end -end diff --git a/app/domain/bi/influx_exporter.rb b/app/domain/bi/influx_exporter.rb deleted file mode 100644 index 71883870..00000000 --- a/app/domain/bi/influx_exporter.rb +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -module BI - class InfluxExporter - # @param settings `Settings.influxdb` - def initialize(settings) - @client = make_client(settings) - @org = settings.org - @use_ssl = settings.use_ssl - end - - def ensure_buckets(buckets) - ensurer = InfluxEnsureBucket.new(@client, @org) - buckets.each { |bucket| ensurer.bucket(bucket) } - end - - def export(data, bucket) - return if data.empty? - - sanitize_fields!(data) - - api = @client.create_write_api - api.write(data: data, bucket: bucket) - end - - private - - def sanitize_fields!(data) - # If we write a field with type int first and then try to write a floating point - # value afterwards, we get - # `failure writing points to database: partial write: field type conflict [...]` - data.each do |e| - e[:fields].transform_values! { |v| v.is_a?(Numeric) ? v.to_f : v } - end - end - - def make_client(settings) - scheme = @use_ssl ? 'https' : 'http' - url = "#{scheme}://#{settings.host}:#{settings.port}" - InfluxDB2::Client.new( - url, - settings.token, - org: settings.org, - precision: InfluxDB2::WritePrecision::SECOND, - bucket: 'PtimeDefault', - use_ssl: settings.use_ssl - ) - end - end -end diff --git a/app/domain/crm/highrise_stats.rb b/app/domain/crm/highrise_stats.rb deleted file mode 100644 index 9704bcd3..00000000 --- a/app/domain/crm/highrise_stats.rb +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -module Crm - class HighriseStats < Base - NO_CATEGORY = '(no category)'.freeze - - def stats - # Job runs at 1 in the morning, collect data from yesterday - month_from = 1.day.ago.beginning_of_month.utc - month_deals = fetch_deals(modified_since: month_from) - pending_deals = fetch_deals(status: 'pending') - - month_stats(month_deals, month_from) + stale_stats(pending_deals) - end - - private - - def fetch_deals(modified_since: nil, status: nil) - # This ignores paging. If we ever get more than 500 deals we'll have to do multiple requests. - - params = {} - params[:since] = timestamp(modified_since) unless modified_since.nil? - params[:status] = status unless status.nil? - - ::Highrise::Deal.find(:all, params: params).yield_self do |deals| - modified_since.nil? ? deals : status_changed(deals, modified_since) - end - end - - def group(deals) - deals.group_by(&:status).transform_values do |deals| - deals.group_by { |deal| deal.try(:category).try(:name) || NO_CATEGORY } - end - end - - def timestamp(time) - # Highrise wants 'yyyymmddhhmmss' - # see https://github.com/basecamp/highrise-api/blob/master/sections/deals.md - time.strftime('%Y%m%d000000') - end - - def month_stats(deals, month) - build_stats( - group(deals).except('pending'), - month: month.strftime('%Y-%m') - ) - end - - def stale_stats(deals) - deals.partition { |deal| deal.updated_at <= 3.months.ago.utc }.zip( - [true, false] - ) - .flat_map { |deals, stale| build_stats(group(deals), stale: stale) } - end - - def build_stats(deals, tags = {}) - count_stats(deals, tags) + volume_stats(deals, tags) - end - - def count_stats(grouped_deals, tags) - map_deals(grouped_deals) do |status, category, deals| - { - name: 'highrise_deals', - fields: { count: deals.count }, - tags: { category: category, status: status }.merge(tags) - } - end - end - - def volume_stats(grouped_deals, tags) - map_deals(grouped_deals) do |status, category, deals| - value = deals.map { |deal| volume(deal) }.sum - - { - name: 'highrise_volume', - fields: { value: value }, - tags: { category: category, status: status }.merge(tags) - } - end - end - - def status_changed(deals, since) - deals.select do |deal| - next deal.created_at >= since if deal.status_changed_on.nil? - deal.status_changed_on >= since - end - end - - def map_deals(grouped_deals) - grouped_deals.flat_map do |status, deals_by_category| - deals_by_category.map do |category, deals| - yield(status, category, deals) - end - end.compact - end - - def volume(deal) - return 0 if deal.price.nil? - deal.price_type == 'fixed' ? deal.price : deal.price * deal.duration - end - end -end diff --git a/app/domain/order/report/bi.rb b/app/domain/order/report/bi.rb deleted file mode 100644 index 53f1ab9a..00000000 --- a/app/domain/order/report/bi.rb +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -class Order::Report::BI - attr_reader :report - - TAGS = %i[client category name status] - TARGET_TAGS = { - 'Kosten' => :target_budget, - 'Termin' => :target_schedule, - 'Qualität' => :target_quality - }.freeze - - METRICS = %i[ - offered_amount - supplied_amount - billable_amount - billed_amount - billability - offered_rate - billed_rate - average_rate - ] - - def initialize(departments = all_departments) - @departments = departments - end - - def stats - @departments.flat_map { |d| department_stats(d) } - end - - private - - def all_departments - Department.having_employees - end - - def department_stats(department) - period = Period.new(nil, nil) - status_ids = OrderStatus.open.pluck(:id) - report = - Order::Report.new( - period, - department_id: department.id, status_id: status_ids - ) - - targets = target_scopes - - report.entries - report.entries.flat_map { |e| entry_stats(e, department, targets) } - end - - def entry_stats(entry, department, targets) - fields = - METRICS.each_with_object({}) do |metric, memo| - memo[metric] = entry.send(metric) - end - - { - name: 'order_report', - fields: fields, - tags: tags(entry, department, targets) - } - end - - def tags(entry, department, targets) - { department: department.to_s }.merge(basic_tags(entry)).merge( - rating_tags(entry, targets) - ) - end - - def basic_tags(entry) - TAGS.each_with_object({}) { |tag, memo| memo[tag] = entry.send(tag).to_s } - end - - def rating_tags(entry, targets) - targets.each_with_object({}) do |target, memo| - rating = entry.target(target.id).try(:rating) || 'none' - tag = TARGET_TAGS.fetch(target.name) - memo[tag] = rating - end - end - - def target_scopes - TargetScope.list.to_a - end -end diff --git a/app/domain/reports/bi_workload.rb b/app/domain/reports/bi_workload.rb deleted file mode 100644 index 6846865b..00000000 --- a/app/domain/reports/bi_workload.rb +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -class Reports::BIWorkload - PROPERTIES = %i[ - employment_fte - must_hours - ordertime_hours - paid_absence_hours - worktime_balance - external_client_hours - billable_hours - workload - billability - absolute_billability - ] - - def initialize(today = DateTime.now) - @today = today - end - - def stats - periods.flat_map do |period, period_tags| - departments.flat_map do |department| - department_stats(department, period, period_tags) - end - end - end - - private - - def periods - last_week = @today - 1.week - last_month = @today - 1.month - - [ - [ - Period.new(last_week.beginning_of_week, last_week.end_of_week), - { week: last_week.strftime('CW %-V') } - ], - [ - Period.new(last_month.beginning_of_month, last_month.end_of_month), - { month: last_month.strftime('%Y-%m') } - ] - ] - end - - def departments - Department.having_employees - end - - def make_period; end - - def department_stats(department, period, tags) - _company, department = Reports::Workload.new(period, department).summary - - fields = - PROPERTIES.each_with_object({}) do |prop, memo| - memo[prop] = department.send(prop) - end - - { - name: 'workload', - fields: fields, - tags: { department: department.label.to_s }.merge(tags) - } - end -end diff --git a/app/domain/reports/revenue/bi.rb b/app/domain/reports/revenue/bi.rb deleted file mode 100644 index 5d968e58..00000000 --- a/app/domain/reports/revenue/bi.rb +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -module Reports::Revenue - class BI - def initialize(window = 3.months) - @window = window - end - - def stats - now = Time.now - - report = report(now, @window) - revenue(now, report) - end - - private - - def report(time, window) - period = - Period.new( - (time - window).beginning_of_month, - (time + window).end_of_month - ) - Reports::Revenue::Department.new(period, {}) - end - - def revenue(now, report) - report.entries.each_with_object([]) do |entry, memo| - report.step_past_months do |date| - memo << revenue_stats(report, entry, date, now, :ordertime) - end - report.step_future_months do |date| - memo << revenue_stats(report, entry, date, now, :planning) - end - end.compact - end - - def revenue_stats(report, entry, date, now, source) - volume = find_volume(report, entry, date, source) - return nil if volume.nil? - - { - name: "revenue_#{source}", - fields: { volume: volume }, - tags: tags(entry, date, now) - } - end - - def tags(entry, date, now) - delta = distance_in_months(now, date) - sign = delta < 0 ? '-' : '+' - - delta_tag = "#{sign} #{delta.abs} months" - month_tag = date.strftime('%Y-%m') - - { time_delta: delta_tag, month: month_tag, department: entry.to_s } - end - - def distance_in_months(from, to) - (to.year * 12 + to.month) - (from.year * 12 + from.month) - end - - def find_volume(report, entry, date, source) - data = - source == :ordertime ? report.ordertime_hours : report.planning_hours - data[[entry.try(:id), date]] - end - end -end diff --git a/app/jobs/bi_export_job.rb b/app/jobs/bi_export_job.rb deleted file mode 100644 index be660a1a..00000000 --- a/app/jobs/bi_export_job.rb +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -class BIExportJob < CronJob - self.cron_expression = '0 1 * * *' - - def perform - BI::Export.new.run - end -end diff --git a/config/application.rb b/config/application.rb index 8c4dfc58..1c725736 100644 --- a/config/application.rb +++ b/config/application.rb @@ -76,7 +76,6 @@ class Application < Rails::Application config.to_prepare do |_| Crm.init Invoicing.init - BI.init CommitReminderJob.schedule end end diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 6974e153..a8cc5225 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -13,8 +13,4 @@ # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym 'RESTful' -# end - -ActiveSupport::Inflector.inflections(:en) do |inflect| - inflect.acronym 'BI' -end +# end \ No newline at end of file diff --git a/config/settings.yml b/config/settings.yml index 26f636d8..cfeb5cd0 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -174,11 +174,3 @@ reports: mailer: from: <%= ENV['RAILS_PTIME_MAILER_FROM'] || 'no-reply@puzzle.ch' %> - -influxdb: - export: <%= ENV['RAILS_INFLUXDB_EXPORT'] == '1' %> - host: <%= ENV['RAILS_INFLUXDB_HOST'] || 'localhost' %> - port: <%= ENV['RAILS_INFLUXDB_PORT']&.to_i || 8086 %> - token: <%= ENV['RAILS_INFLUXDB_TOKEN'] || 'ptimetoken' %> - org: <%= ENV['RAILS_INFLUXDB_ORG'] || 'ptime' %> - use_ssl: false diff --git a/config/settings/development.yml b/config/settings/development.yml index 44c3ff89..9c669884 100644 --- a/config/settings/development.yml +++ b/config/settings/development.yml @@ -2,6 +2,3 @@ # PuzzleTime and licensed under the Affero General Public License version 3 # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. - -influxdb: - export: true diff --git a/doc/user/README.md b/doc/user/README.md index 887d6bb8..6c8e98e0 100644 --- a/doc/user/README.md +++ b/doc/user/README.md @@ -8,4 +8,3 @@ Diese Dokumente beschreiben PuzzleTime aus der Benutzersicht. * [Funktionen](02_features.md) * [Use Cases](usecases/README.md) * [API](api.md) -* [InfluxDB-Metriken](influx_metrics.md) diff --git a/doc/user/influx_metrics.md b/doc/user/influx_metrics.md deleted file mode 100644 index 3626ebe0..00000000 --- a/doc/user/influx_metrics.md +++ /dev/null @@ -1,91 +0,0 @@ -# InfluxDB-Metriken - -PuzzleTime kann folgende Metriken nach InfluxDB v2 exportieren: - -## Umsatz - -* InfluxDB-Bucket: `Revenue` - -Metriken / Felder - -* `revenue_ordertime` - Geleistet - * `volume [CHF]` - geleistete verrechenbare Aufwände -* `revenue_planning` - Forecast - * `volume [CHF]` - geplante verrechenbare Aufwände - -Tags - -* `time_delta` - Zeitraum der Auswertung, z.B "-2 months" für vorletzter Monat -* `month` - Monat, den die Auswertung betrifft, z.B. "2021-01" -* `department` - Bereich - -## Auslastung - -* InfluxDB-Bucket: `Workload` - -Metriken - -* `workload` - * `employment_fte [Vollzeitäkquivalente]` - Kumulierter Anstellungsgrad (1 = 100%) - * `must_hours [h]` - Soll-Zeit - * `must_hours [h]` - Ist-Zeit - * `paid_absence_hours [h]` - Absenzen - * `worktime_balance [h]` - Über-/Unterzeit - * `external_client_hours [h]` - Kundenprojekte - * `billable_hours [h]` - Verrechenbare Zeit - * `workload [%]` - Auslastung - * `billability [%]` - Verrechenbarkeit - * `absolute_billability [%]` - Absolute Verrechenbarkeit - -Tags - -* `department` (Bereich) -* Ausgewerteter Zeitraum - * a) `week` - Kalenderwoche, z.B. "CW 3". Es wird jeweils die vergangene Woche ausgewertet. Diese Metriken sind darum ungenau, weil noch nicht alle Zeitein eingetragen sind. - * b) `month` - Monat, z.B. "2021-01". Es wird jeweils der vergangene Monat ausgewertet. - -## Aufträge - -* InfluxDB-Bucket: `Orders` - -Metriken - -* `order_report` - Auftrags-Controlling - * `offered_amount [CHF/EUR]` - Budget - * `supplied_amount [CHF/EUR]` - Geleistet - * `billable_amount [CHF/EUR]` - Verrechenbar - * `billed_amount [CHF/EUR]` - Verrechnet - * `billabiltity [%]` - Verrechenbarkeit - * `offered_rate [CHF/h]` - Offerierter Stundensatz - * `billed_rate [CHF/h]` - Verrechneter Stundensatz - * `average_rate [CHF/h]` - Durchschnittlicher Stundensatz - * `target_budget ["green"|"orange"|"red"]` - Projekt-Ampel "Kosten" - * `target_schedule ["green"|"orange"|"red"]` - Projekt-Ampel "Termin" - * `target_quality ["green"|"orange"|"red"]` - Projekt-Ampel "Qualität" - -Tags - -* `client` - Kunde -* `name` - Auftragsname -* `status` - Bearbeitungsstatus -* `category` - Kategorie - -## CRM (Highrise) - -* InfluxDB-Bucket: `Highrise` - -Metriken - -* `highrise_deals` - Deals - * `count` - Anzahl der Deals -* `highrise_volume` - Deal-Volumen - * `value [CHF/EUR]` - Gesamtvolumen der Deals (Fixpreise + Stundensätze * Angebotene Stunden) - -Tags - -* `status` - Bearbeitungsstatus -* `category` - Deal-Kategorie -* Wenn `status` den Wert `"lost"` oder `"won"` hat: - * `month` - Monat, z.B. "2021-01" -* Wenn `status` den Wert `"pending"` hat: - * `stale [boolean]` - ob die letzte Änderung des Deals mehr als 3 Monate her ist diff --git a/docker-compose.yml b/docker-compose.yml index 41891524..b7377724 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,6 @@ version: '3' volumes: db-data: - influxdb-data: services: ptimedb: @@ -31,18 +30,4 @@ services: image: schickling/mailcatcher ports: - '1025:1025' - - '1080:1080' - - ptimeinfluxdb: - image: influxdb:2.0.4 - ports: - - '8086:8086' - volumes: - - influxdb-data:/var/lib/influxdb2 - environment: - - DOCKER_INFLUXDB_INIT_MODE=setup - - DOCKER_INFLUXDB_INIT_USERNAME=influxuser - - DOCKER_INFLUXDB_INIT_PASSWORD=influxpassword - - DOCKER_INFLUXDB_INIT_ORG=ptime - - DOCKER_INFLUXDB_INIT_BUCKET=PtimeDefault - - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=ptimetoken + - '1080:1080' \ No newline at end of file diff --git a/test/domain/order/report/bi_test.rb b/test/domain/order/report/bi_test.rb deleted file mode 100644 index 12e364ae..00000000 --- a/test/domain/order/report/bi_test.rb +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -require 'test_helper' - -class Order::Report::BITest < ActiveSupport::TestCase - test 'collects stats' do - stats = Order::Report::BI.new.stats - - ptime = stats.find { |stat| stat.dig(:tags, :name) == 'PuzzleTime' } - - assert_equal('order_report', ptime[:name]) - assert_equal( - { - client: 'Puzzle ITC', - category: '', - name: 'PuzzleTime', - status: 'In Bearbeitung', - department: 'devone', - target_schedule: 'green', - target_budget: 'orange', - target_quality: 'green' - }, - ptime[:tags] - ) - assert_equal(44, ptime.dig(:fields, :billability)) - assert_equal( - %i[ - offered_amount - supplied_amount - billable_amount - billed_amount - billability - offered_rate - billed_rate - average_rate - ], - ptime[:fields].keys - ) - end - - private - - def report(params = {}) - period = params.delete(:period) || Period.new(nil, nil) - @report ||= Order::Report.new(period, params) - end -end diff --git a/test/domain/reports/bi_workload_test.rb b/test/domain/reports/bi_workload_test.rb deleted file mode 100644 index b1abb318..00000000 --- a/test/domain/reports/bi_workload_test.rb +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -require 'test_helper' - -class BIWorkloadTest < ActiveSupport::TestCase - test 'initializes default period' do - # Make sure initialization does not break as we specify a period in the example below - report - end - - test 'collects stats' do - Fabricate( - :ordertime, - hours: 2, - work_item: work_items(:webauftritt), - employee: employees(:lucien), - work_date: worked_at - ) - Fabricate( - :ordertime, - hours: 3, - work_item: work_items(:hitobito_demo_app), - employee: employees(:lucien), - work_date: worked_at - ) - - Fabricate( - :ordertime, - hours: 3, - work_item: work_items(:hitobito_demo_app), - employee: employees(:lucien), - work_date: worked_at - 1.week - ) - - stats = report.stats - - assert_equal 4, stats.length - - assert_includes( - stats, - { - name: 'workload', - fields: { - employment_fte: 0.0, - must_hours: 0, - ordertime_hours: 5.0, - paid_absence_hours: 0, - worktime_balance: 5.0, - external_client_hours: 2.0, - billable_hours: 2.0, - workload: 40.0, - billability: 100.0, - absolute_billability: 40.0 - }, - tags: { department: 'devtwo', week: 'CW 26' } - } - ) - - assert_includes( - stats, - { - name: 'workload', - fields: { - employment_fte: 0.0, - must_hours: 0, - ordertime_hours: 8.0, - paid_absence_hours: 0, - worktime_balance: 8.0, - external_client_hours: 2.0, - billable_hours: 2.0, - workload: 25.0, - billability: 100.0, - absolute_billability: 25.0 - }, - tags: { department: 'devtwo', month: '2021-06' } - } - ) - end - - private - - def report - # The work items are the last week of June (CW 26), the report runs in the first week of July (CW 27) - @report = Reports::BIWorkload.new(worked_at + 1.week) - end - - def worked_at - @worked_at ||= Date.new(2_021, 6, 28) # A monday - end - - def create_employments - Fabricate( - :employment, - employee: employees(:pascal), - start_date: Date.parse('1.1.2006'), - percent: 80 - ) - Fabricate( - :employment, - employee: employees(:lucien), - start_date: Date.parse('1.1.2006'), - percent: 100 - ) - end -end diff --git a/test/domain/reports/revenue/bi_test.rb b/test/domain/reports/revenue/bi_test.rb deleted file mode 100644 index 7f45b514..00000000 --- a/test/domain/reports/revenue/bi_test.rb +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -require 'test_helper' - -class BIRevenueReportTest < ActiveSupport::TestCase - setup do - travel_to Date.new(2_000, 9, 5) - Worktime.destroy_all - end - - teardown { travel_back } - - test 'entries and values without any ordertimes and plannings' do - r = report - assert_equal [], r.stats - end - - test 'entries and values' do - Settings.clients.stubs(:company_id).returns(0) # TODO: do not use puzzle as example - - ordertime(Date.new(2_000, 1, 10), :puzzletime) # before period (ignored) - ordertime(Date.new(2_000, 7, 10), :puzzletime) - ordertime(Date.new(2_000, 7, 11), :puzzletime) - ordertime(Date.new(2_000, 8, 10), :puzzletime) - ordertime(Date.new(2_000, 7, 10), :hitobito_demo_app) - ordertime(Date.new(2_000, 7, 10), :allgemein) # offered rate = 0 - ordertime(Date.new(2_000, 7, 10), :puzzletime, false) # work time not billable (ignored) - ordertime(Date.new(2_000, 9, 10), :puzzletime) # in the future (ignored) - - planning(Date.new(2_000, 7, 10), :hitobito_demo_app) # in the past (ignored) - planning(Date.new(2_000, 9, 11), :hitobito_demo_app) - planning(Date.new(2_000, 11, 10), :hitobito_demo_app) - planning(Date.new(2_000, 11, 13), :hitobito_demo_app) - planning(Date.new(2_000, 11, 10), :webauftritt) - planning(Date.new(2_000, 11, 10), :allgemein) # offered rate = 0 - planning(Date.new(2_000, 11, 14), :hitobito_demo_app, false) # provisional (ignored) - planning(Date.new(2_000, 11, 10), :puzzletime) # accounting post not billable (ignored) - planning(Date.new(2_000, 12, 1), :hitobito_demo_app) # after period (ignored) - - r = report - # The test uses the same setup as DepartmentRevenueReportTest - - stats = r.stats - - assert_equal(9, stats.count) - assert_includes( - r.stats, - { - name: 'revenue_planning', - fields: { volume: 2176.0 }, - tags: { - time_delta: '+ 2 months', department: 'devtwo', month: '2000-11' - } - } - ) - assert_includes( - r.stats, - { - name: 'revenue_ordertime', - fields: { volume: 6.0 }, - tags: { - time_delta: '- 2 months', department: 'devone', month: '2000-07' - } - } - ) - end - - private - - def report - Reports::Revenue::BI.new - end - - def period( - start_date = Date.new(2_000, 7, 1), end_date = Date.new(2_000, 11, 30) - ) - Period.new(start_date, end_date) - end - - def ordertime(date, work_item_uuid, billable = true) - Fabricate( - :ordertime, - work_date: date, - work_item: work_items(work_item_uuid), - employee: employees(:pascal), - hours: 1, - billable: billable - ) - end - - def planning(date, work_item_uuid, definitive = true) - Fabricate( - :planning, - date: date, - work_item: work_items(work_item_uuid), - employee: employees(:pascal), - percent: 80, - definitive: definitive - ) - end - - def devone - departments(:devone) - end - - def devtwo - departments(:devtwo) - end - - def sys - departments(:sys) - end -end From 69d9dcc407cac7a77350e3287ae576ffad969998 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 18 Aug 2022 18:01:22 +0200 Subject: [PATCH 030/163] Auto redirect to authorize url on login page, fixes #59739 --- .tool-versions | 1 + .../employees/sessions_controller.rb | 34 ++++++++++ app/models/employee.rb | 7 -- config/routes.rb | 2 +- .../employees/sessions_controller_test.rb | 65 +++++++++++++++++++ 5 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 app/controllers/employees/sessions_controller.rb create mode 100644 test/controllers/employees/sessions_controller_test.rb diff --git a/.tool-versions b/.tool-versions index 9eb38ed7..70787422 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ ruby 2.7.2 +nodejs 14.4.0 diff --git a/app/controllers/employees/sessions_controller.rb b/app/controllers/employees/sessions_controller.rb new file mode 100644 index 00000000..65f0438e --- /dev/null +++ b/app/controllers/employees/sessions_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +class Employees::SessionsController < Devise::SessionsController + # GET /resource/sign_in + def new + return redirect_to auto_redirect_path if auto_redirect? + + super + end + + private + + def local_auth? + Settings.auth.db.active + end + + def single_omniauth_provider? + Settings.auth&.omniauth&.map(&:second)&.map(&:active)&.one? + end + + def auto_redirect? + !local_auth? && single_omniauth_provider? + end + + def auto_redirect_path + provider = Settings.auth&.omniauth.to_h.find { |_, options| options[:active] }&.first + public_send("employee_#{provider}_omniauth_authorize_path") + end +end diff --git a/app/models/employee.rb b/app/models/employee.rb index 30079be8..e687fdd8 100644 --- a/app/models/employee.rb +++ b/app/models/employee.rb @@ -140,13 +140,6 @@ def providers end class << self - # Tries to login a user with the passed data. - # Returns the logged-in Employee or nil if the login failed. - def login(username, pwd) - find_by(shortname: username.upcase, passwd: encode(pwd)) || - LdapAuthenticator.new.login(username, pwd) - end - def employed_ones(period, sort = true) result = joins('left join employments em on em.employee_id = employees.id'). where('(em.end_date IS null or em.end_date >= ?) AND em.start_date <= ?', diff --git a/config/routes.rb b/config/routes.rb index 24af575f..e728bca0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,7 @@ # https://github.com/puzzle/puzzletime. Rails.application.routes.draw do - devise_for :employees, controllers: { omniauth_callbacks: 'employees/omniauth_callbacks' }, skip: [:registrations] + devise_for :employees, controllers: { sessions: 'employees/sessions', omniauth_callbacks: 'employees/omniauth_callbacks' }, skip: [:registrations] as :employee do get 'employees/edit' => 'devise/registrations#edit', :as => 'edit_employee_registration' patch 'employees' => 'devise/registrations#update', :as => 'employee_registration' diff --git a/test/controllers/employees/sessions_controller_test.rb b/test/controllers/employees/sessions_controller_test.rb new file mode 100644 index 00000000..cf72b86b --- /dev/null +++ b/test/controllers/employees/sessions_controller_test.rb @@ -0,0 +1,65 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +require 'test_helper' + +class Employees::SessionsControllerTest < ActionController::TestCase + setup do + @request.env['devise.mapping'] = Devise.mappings[:employee] + end + + def test_only_omniauth_keycloakopenid_active + Settings.auth.db.active = false + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = false + + get :new + + assert_redirected_to employee_keycloakopenid_omniauth_authorize_path + end + + def test_only_omniauth_saml_active + Settings.auth.db.active = false + Settings.auth.omniauth.keycloakopenid.active = false + Settings.auth.omniauth.saml.active = true + + get :new + + assert_redirected_to employee_saml_omniauth_authorize_path + end + + def test_only_local_auth_active + Settings.auth.db.active = true + Settings.auth.omniauth.keycloakopenid.active = false + Settings.auth.omniauth.saml.active = false + + get :new + + assert_response :success + assert_template :new + end + + def test_multiple_omniauth_active + Settings.auth.db.active = false + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = true + + get :new + + assert_response :success + assert_template :new + end + + def test_local_auth_and_single_omniauth_active + Settings.auth.db.active = true + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = false + + get :new + + assert_response :success + assert_template :new + end +end From ad1630700d5397510bd2d794869a739c731eb5cd Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 26 Aug 2022 11:06:29 +0200 Subject: [PATCH 031/163] Revert "Fix assets precompile in s2i build by disabling `validates_by_schema` in initializer", fixes #59957 This reverts commit 1254eef2541c891492b25742fe97d9d1b9b1eda6. --- .s2i/environment | 1 - config/initializers/s2i_build.rb | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 .s2i/environment delete mode 100644 config/initializers/s2i_build.rb diff --git a/.s2i/environment b/.s2i/environment deleted file mode 100644 index fc5356ed..00000000 --- a/.s2i/environment +++ /dev/null @@ -1 +0,0 @@ -S2I_BUILD=true \ No newline at end of file diff --git a/config/initializers/s2i_build.rb b/config/initializers/s2i_build.rb deleted file mode 100644 index b9f72ff7..00000000 --- a/config/initializers/s2i_build.rb +++ /dev/null @@ -1,7 +0,0 @@ -if ENV['S2I_BUILD'] - module ValidatesBySchema::ClassMethods - def validates_by_schema(options = {}) - # do nothing on s2i build to prevent db initialisation as db does not exist during s2i build - end - end -end \ No newline at end of file From cb1648862ab9a966b86b83baa228ec28b87121b0 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Mon, 4 Apr 2022 18:07:45 +0200 Subject: [PATCH 032/163] Configure paper_trail for EmploymentRolesEmployment, extend presenter, fixes #57605 --- app/domain/presenters/log_presenter.rb | 9 ++++ app/models/employment_roles_employment.rb | 2 + config/locales/models.de-CH.yml | 1 + config/locales/views.de-CH.yml | 4 ++ lib/tasks/db.rake | 27 ++++++++++++ test/domain/presenters/log_presenter_test.rb | 43 ++++++++++++++++++++ 6 files changed, 86 insertions(+) create mode 100644 test/domain/presenters/log_presenter_test.rb diff --git a/app/domain/presenters/log_presenter.rb b/app/domain/presenters/log_presenter.rb index a7ca3d0a..16d1d258 100644 --- a/app/domain/presenters/log_presenter.rb +++ b/app/domain/presenters/log_presenter.rb @@ -38,10 +38,19 @@ def present_changes(versions) def title_for(version) model = version.item_type.parameterize + return send("title_for_#{model}", version) if respond_to?("title_for_#{model}") + event = version.event I18n.t("version.model.#{event}.#{model}", id: version.item_id) end + def title_for_employmentrolesemployment(version) + entry_class = version.item_type.constantize + entry = entry_class.find_by(id: version.item_id) || version.object && entry_class.new(version.object_deserialized) + role = entry&.employment_role || '(deleted)' + I18n.t("version.model.#{version.event}.employmentrolesemployment", role: role, employment_id: entry&.employment_id) + end + def changes_for(version) version.changeset.collect do |attr, changes| next if changes.all?(&:blank?) diff --git a/app/models/employment_roles_employment.rb b/app/models/employment_roles_employment.rb index 5fe04f05..b2cbe3aa 100644 --- a/app/models/employment_roles_employment.rb +++ b/app/models/employment_roles_employment.rb @@ -15,6 +15,8 @@ # class EmploymentRolesEmployment < ActiveRecord::Base + has_paper_trail(meta: { employee_id: ->(e){ e.employment.employee_id } }, skip: [:id]) + belongs_to :employment belongs_to :employment_role belongs_to :employment_role_level, optional: true diff --git a/config/locales/models.de-CH.yml b/config/locales/models.de-CH.yml index 781d8370..852b01c6 100644 --- a/config/locales/models.de-CH.yml +++ b/config/locales/models.de-CH.yml @@ -236,6 +236,7 @@ de-CH: name: Name employment_roles_employment: percent: Pensum + employment_role: Funktion expense: id: Spesennummer amount: Betrag diff --git a/config/locales/views.de-CH.yml b/config/locales/views.de-CH.yml index 70dd0c2e..c9a0e1c4 100644 --- a/config/locales/views.de-CH.yml +++ b/config/locales/views.de-CH.yml @@ -9,15 +9,19 @@ de-CH: create: employee: "Der Member #%{id} wurde erstellt." employment: "Die Anstellung #%{id} wurde erstellt." + employmentrolesemployment: "Der Funktionsanteil \"%{role}\" der Anstellung #%{employment_id} wurde erstellt." update: employee: "Der Member #%{id} wurde bearbeitet." employment: "Die Anstellung #%{id} wurde bearbeitet." + employmentrolesemployment: "Der Funktionsanteil \"%{role}\" der Anstellung #%{employment_id} wurde bearbeitet." destroy: employee: "Der Member #%{id} wurde gelöscht." employment: "Die Anstellung #%{id} wurde gelöscht." + employmentrolesemployment: "Der Funktionsanteil \"%{role}\" der Anstellung #%{employment_id} wurde gelöscht." model_reference: employee: "des Members" employment: "der Anstellung" + employmentrolesemployment: "des Funktionsanteils" attribute_change: from_to: "%{attr} %{model_ref} wurde von «%{from}» auf «%{to}» geändert." from: "%{attr} %{model_ref} «%{from}» wurde gelöscht." diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index e7a2180b..b4db5d32 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -83,5 +83,32 @@ namespace :db do percent: 80 } ) + + CreateTestuser.run( + shortname: 'MGT', + employee: { + firstname: 'Manager', + lastname: 'Management', + passwd: Employee.encode('member'), + password: 'member', + email: 'mgt@puzzle.ch', + management: true + }, + role: { + name: 'T2 System Engineer', + billable: true, + level: true + }, + level: { + name: 'S4' + }, + employment: { + percent: 80, + start_date: Date.new(2014, 9, 1) + }, + role_employment: { + percent: 80 + } + ) end end diff --git a/test/domain/presenters/log_presenter_test.rb b/test/domain/presenters/log_presenter_test.rb new file mode 100644 index 00000000..aef09257 --- /dev/null +++ b/test/domain/presenters/log_presenter_test.rb @@ -0,0 +1,43 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +require 'test_helper' + +class LogPresenterTest < ActiveSupport::TestCase + test 'title_for_version' do + employment = Employment.first + version = PaperTrail::Version.new( + item_type: "Employment", + item_id: employment.id, + event: "destroy" + ) + + assert_equal I18n.t("version.model.destroy.employment", id: employment.id), LogPresenter.new(Employee.new).title_for(version) + end + + test 'title_for_employmentrolesemployment_version if object record exists' do + entry = EmploymentRolesEmployment.first + role = entry.employment_role + version = PaperTrail::Version.new( + item_type: "EmploymentRolesEmployment", + item_id: entry.id, + event: "destroy" + ) + + assert_equal I18n.t("version.model.destroy.employmentrolesemployment", role: role, employment_id: entry.employment_id), LogPresenter.new(Employee.new).title_for(version) + end + + test 'title_for_employmentrolesemployment_version if object record does not exist' do + version = PaperTrail::Version.new( + item_type: "EmploymentRolesEmployment", + item_id: 999, + object: "---\nemployment_id: 999\nemployment_role_id: 999\nemployment_role_level_id: 1\npercent: !ruby/object:BigDecimal 18:0.1e3\n", + event: "destroy" + ) + + assert_equal I18n.t("version.model.destroy.employmentrolesemployment", role: '(deleted)', employment_id: 999), LogPresenter.new(Employee.new).title_for(version) + end + +end From 310a49eaa3478e37c24ce4d817385c683fb55ab2 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Mon, 5 Sep 2022 14:16:12 +0200 Subject: [PATCH 033/163] Allow selenium driver to be set by env --- test/test_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index d451246b..0c496716 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -44,6 +44,8 @@ Capybara.register_driver :selenium do |app| require 'selenium/webdriver' + next Capybara::Selenium::Driver.new(app, browser: ENV['SELENIUM_DRIVER'].to_sym) if ENV['SELENIUM_DRIVER'] + Selenium::WebDriver::Firefox::Binary.path = ENV['FIREFOX_PATH'] if ENV['FIREFOX_PATH'] capa = Selenium::WebDriver::Remote::Capabilities.firefox(marionette: true) Capybara::Selenium::Driver.new(app, browser: :firefox, desired_capabilities: capa) From a1502cfdfe755e9b939ad0693640065d1a5c19ca Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 2 Sep 2022 09:35:09 +0200 Subject: [PATCH 034/163] Implement absence type filter, fixes #60108 --- app/controllers/evaluator_controller.rb | 18 +++++++++++++++--- app/domain/evaluations/absences_eval.rb | 4 ++-- .../evaluations/employee_absences_eval.rb | 4 ++-- app/domain/evaluations/evaluation.rb | 7 +++++-- app/views/evaluator/_division.html.haml | 2 +- .../evaluator/_employee_absences.html.haml | 2 +- app/views/evaluator/_evallink.html.haml | 3 ++- app/views/evaluator/overview.html.haml | 7 +++++++ .../evaluations/employee_absences_eval_test.rb | 8 ++++++++ 9 files changed, 43 insertions(+), 12 deletions(-) diff --git a/app/controllers/evaluator_controller.rb b/app/controllers/evaluator_controller.rb index 75104e60..25dc26a5 100644 --- a/app/controllers/evaluator_controller.rb +++ b/app/controllers/evaluator_controller.rb @@ -11,6 +11,8 @@ class EvaluatorController < ApplicationController before_action :set_period + helper_method :search_conditions, :evaluation_type + def index overview end @@ -74,6 +76,10 @@ def set_evaluation @evaluation end + def evaluation_type + params[:evaluation] + end + def set_default_evaluation @evaluation = case params[:evaluation].downcase when 'managed' then ManagedOrdersEval.new(@user) @@ -81,7 +87,7 @@ def set_default_evaluation when "employeesubworkitems#{@user.id}", 'usersubworkitems' then params[:evaluation] = 'usersubworkitems' EmployeeSubWorkItemsEval.new(params[:category_id], @user.id) - when 'userabsences' then EmployeeAbsencesEval.new(@user.id) + when 'userabsences' then EmployeeAbsencesEval.new(@user.id, **search_conditions) when 'subworkitems' then SubWorkItemsEval.new(params[:category_id]) when 'workitememployees' then WorkItemEmployeesEval.new(params[:category_id]) end @@ -106,8 +112,8 @@ def set_management_evaluation when /employeesubworkitems(\d+)/ then EmployeeSubWorkItemsEval.new(params[:category_id], Regexp.last_match[1]) when 'departmentorders' then DepartmentOrdersEval.new(params[:category_id]) - when 'absences' then AbsencesEval.new - when 'employeeabsences' then EmployeeAbsencesEval.new(params[:category_id]) + when 'absences' then AbsencesEval.new(**search_conditions) + when 'employeeabsences' then EmployeeAbsencesEval.new(params[:category_id], **search_conditions) end end @@ -190,6 +196,12 @@ def init_periods end end + def search_conditions + return {} unless params[:absence_id].present? + + {absence_id: params[:absence_id]} + end + def authorize_action params[:evaluation] ||= params[:action].to_s evaluation diff --git a/app/domain/evaluations/absences_eval.rb b/app/domain/evaluations/absences_eval.rb index 691152ca..c1a23b11 100644 --- a/app/domain/evaluations/absences_eval.rb +++ b/app/domain/evaluations/absences_eval.rb @@ -13,8 +13,8 @@ class AbsencesEval < Evaluation self.detail_columns = detail_columns.reject { |i| i == :billable } self.detail_labels = detail_labels.merge(account: 'Absenz') - def initialize - super(Employee) + def initialize(**search_conditions) + super(Employee, **search_conditions) end def divisions(period = nil) diff --git a/app/domain/evaluations/employee_absences_eval.rb b/app/domain/evaluations/employee_absences_eval.rb index 52ed2e04..60426b2a 100644 --- a/app/domain/evaluations/employee_absences_eval.rb +++ b/app/domain/evaluations/employee_absences_eval.rb @@ -12,8 +12,8 @@ class EmployeeAbsencesEval < Evaluation self.detail_columns = detail_columns.reject { |i| i == :billable } self.detail_labels = detail_labels.merge(account: 'Absenz') - def initialize(employee_id) - super(Employee.find(employee_id)) + def initialize(employee_id, **search_conditions) + super(Employee.find(employee_id), **search_conditions) end def for?(user) diff --git a/app/domain/evaluations/evaluation.rb b/app/domain/evaluations/evaluation.rb index 442d26ea..9042e0cb 100644 --- a/app/domain/evaluations/evaluation.rb +++ b/app/domain/evaluations/evaluation.rb @@ -57,7 +57,8 @@ class Evaluation description: 'Bemerkungen' } attr_reader :category, # category - :division # selected division for detail Evaluations, nil otherwise + :division, # selected division for detail Evaluations, nil otherwise + :search_conditions ############### Time Evaluation Functions ############### @@ -107,6 +108,7 @@ def times(period) def worktime_query(receiver, period = nil, division = nil) query = receiver.worktimes.where(type: worktime_type).in_period(period) query = query.where("? = #{category_ref}", category_id) if division && category_ref + query = query.where(search_conditions) if search_conditions.present? query end @@ -229,8 +231,9 @@ def account_id private # Initializes a new Evaluation with the given category. - def initialize(category) + def initialize(category, **search_conditions) @category = category + @search_conditions = search_conditions end def query_time_sums(query, group_by_column = nil) diff --git a/app/views/evaluator/_division.html.haml b/app/views/evaluator/_division.html.haml index 4e56560a..d7fcea1f 100644 --- a/app/views/evaluator/_division.html.haml +++ b/app/views/evaluator/_division.html.haml @@ -9,7 +9,7 @@ %td - if sub = evaluation.sub_evaluation = link_to(division.label_verbose, - { action: 'overview', evaluation: sub, category_id: division.id }, + { action: 'overview', evaluation: sub, category_id: division.id, **search_conditions }, title: division.tooltip) - else = content_tag :span, division.label_verbose, title: division.tooltip diff --git a/app/views/evaluator/_employee_absences.html.haml b/app/views/evaluator/_employee_absences.html.haml index 8f3d11b8..f81832c7 100644 --- a/app/views/evaluator/_employee_absences.html.haml +++ b/app/views/evaluator/_employee_absences.html.haml @@ -7,7 +7,7 @@ %section.employee-absences %h4 Absenzen - - evaluation = EmployeeAbsencesEval.new(params[:category_id] || @user.id) + - evaluation = EmployeeAbsencesEval.new(params[:category_id] || @user.id, search_conditions) = render('category', evaluation: evaluation, times: @periods.collect { |p| evaluation.sum_times_grouped(p) }, diff --git a/app/views/evaluator/_evallink.html.haml b/app/views/evaluator/_evallink.html.haml index 40704dc5..6e9d4423 100644 --- a/app/views/evaluator/_evallink.html.haml +++ b/app/views/evaluator/_evallink.html.haml @@ -9,4 +9,5 @@ evallink[2], action: 'overview', evaluation: evallink[0], - category_id: evallink[1] + category_id: evallink[1], + **search_conditions diff --git a/app/views/evaluator/overview.html.haml b/app/views/evaluator/overview.html.haml index 404fc1a3..d9ed7aa6 100644 --- a/app/views/evaluator/overview.html.haml +++ b/app/views/evaluator/overview.html.haml @@ -9,6 +9,13 @@ - if @order = "#{@order.model_name.human}: #{link_to(@order.label_with_workitem_path, @order)}".html_safe +- if evaluation_type =~ /absence/ + = form_tag(nil, { method: :get, class: 'form-inline', role: 'search' }) do + = hidden_field_tag :returning, true + = hidden_field_tag :page, 1 + = hidden_field_tag :category_id, params[:category_id] + = direct_filter_select(:absence_id, 'Absenztyp', Absence.all) + = render 'period_link' - if @evaluation.category.respond_to?(:tooltip) && @evaluation.category.tooltip.present? diff --git a/test/domain/evaluations/employee_absences_eval_test.rb b/test/domain/evaluations/employee_absences_eval_test.rb index b2a7c271..6ae5e7a3 100644 --- a/test/domain/evaluations/employee_absences_eval_test.rb +++ b/test/domain/evaluations/employee_absences_eval_test.rb @@ -107,4 +107,12 @@ def test_employee_absences_lucien_detail assert_sum_times 0, 0, 12, 12 assert_count_times 0, 0, 1, 1 end + + def test_sum_times_search_conditions + period = Period.new(Date.parse('2006-01-01'), Date.parse('2006-12-31')) + assert_equal(worktimes(:wt_pz_vacation, :wt_pz_doctor), EmployeeAbsencesEval.new(employees(:pascal).id).times(period)) + assert_equal([worktimes(:wt_pz_vacation)], EmployeeAbsencesEval.new(employees(:pascal).id, absence_id: absences(:vacation).id).times(period)) + assert_equal([worktimes(:wt_pz_doctor)], EmployeeAbsencesEval.new(employees(:pascal).id, absence_id: absences(:doctor).id).times(period)) + assert_equal([], EmployeeAbsencesEval.new(employees(:pascal).id, absence_id: absences(:civil_service).id).times(period)) + end end From ffd8142e1476bdbc556344707aabee0c3324b5bf Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 2 Sep 2022 10:03:05 +0200 Subject: [PATCH 035/163] Fix period parameter for employee capacity report, fixes #60084 --- app/controllers/workload_report_controller.rb | 9 ++++++--- app/views/workload_report/_filters.html.haml | 3 --- app/views/workload_report/index.html.haml | 1 + test/controllers/workload_report_controller_test.rb | 10 ++-------- test/integration/workload_report_test.rb | 8 +++----- test/support/integration_helper.rb | 7 +++++++ test/test_helper.rb | 6 +++++- 7 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/controllers/workload_report_controller.rb b/app/controllers/workload_report_controller.rb index a25330f0..72629c11 100644 --- a/app/controllers/workload_report_controller.rb +++ b/app/controllers/workload_report_controller.rb @@ -5,9 +5,7 @@ class WorkloadReportController < ApplicationController include DryCrud::Rememberable - include WithPeriod - - self.remember_params = %w(start_date end_date department_id) + self.remember_params = %w(department_id) before_action :authorize_class @@ -34,6 +32,11 @@ def set_department @department = Department.where(id: params[:department_id]).first end + def set_period + super + @period ||= default_period + end + def default_period month = Time.zone.today.last_month Period.new(month.beginning_of_month, month.end_of_month) diff --git a/app/views/workload_report/_filters.html.haml b/app/views/workload_report/_filters.html.haml index addfa8db..bf405a68 100644 --- a/app/views/workload_report/_filters.html.haml +++ b/app/views/workload_report/_filters.html.haml @@ -5,9 +5,6 @@ = form_tag(nil, method: :get, class: 'form-inline', role: 'filter', remote: true, data: { spin: true }) do - = direct_filter_date(:start_date, 'Von', @period.start_date) - = direct_filter_date(:end_date, 'Bis', @period.end_date) - = direct_filter_select(:department_id, 'OE', @departments) .form-group diff --git a/app/views/workload_report/index.html.haml b/app/views/workload_report/index.html.haml index 2491b1ea..7ec1e202 100644 --- a/app/views/workload_report/index.html.haml +++ b/app/views/workload_report/index.html.haml @@ -7,6 +7,7 @@ - @title ||= 'Auslastung' %p= render 'filters' += render 'evaluator/period_link', entities_name: 'Absenzen' .results= render 'results' diff --git a/test/controllers/workload_report_controller_test.rb b/test/controllers/workload_report_controller_test.rb index be727a9e..41a97f55 100644 --- a/test/controllers/workload_report_controller_test.rb +++ b/test/controllers/workload_report_controller_test.rb @@ -23,17 +23,11 @@ class WorkloadReportControllerTest < ActionController::TestCase end test 'GET index with department and period filter params sets correct report attributes' do - get :index, params: { start_date: '1.1.2006', end_date: '31.12.2006', department_id: departments(:devtwo).id } + set_period start_date: '1.1.2006', end_date: '31.12.2006' + get :index, params: { department_id: departments(:devtwo).id } assert_equal true, assigns(:report).filters_defined? assert_equal Date.parse('1.1.2006'), assigns(:report).period.start_date assert_equal Date.parse('31.12.2006'), assigns(:report).period.end_date assert_equal departments(:devtwo), assigns(:report).department end - - test 'GET index with invalid period filter shows error' do - get :index, params: { start_date: '31.12.2006', end_date: '1.12.2006' } - assert_nil assigns(:report).filters_defined? - assert flash[:alert].present? - assert_equal Period.new(nil, nil), assigns(:period) - end end diff --git a/test/integration/workload_report_test.rb b/test/integration/workload_report_test.rb index 588d806c..b10d4b3e 100644 --- a/test/integration/workload_report_test.rb +++ b/test/integration/workload_report_test.rb @@ -14,8 +14,7 @@ class WorkloadReportTest < ActionDispatch::IntegrationTest end test 'member detail links are set-up after changing filter' do - find('input#start_date').click - all('td[data-handler=selectDay]').last.click + set_period assert_no_selector 'tbody#employee-6-ordertimes' find('a[data-toggle="employee-6-ordertimes"]').click @@ -26,8 +25,7 @@ class WorkloadReportTest < ActionDispatch::IntegrationTest def login login_as(:mark) - visit reports_workload_path(start_date: '1.1.2006', - end_date: '31.12.2006', - department_id: departments(:devtwo).id) + set_period(start_date: '1.1.2006', end_date: '31.12.2006', back_url: '/') + visit reports_workload_path(department_id: departments(:devtwo).id) end end diff --git a/test/support/integration_helper.rb b/test/support/integration_helper.rb index c220b4e9..53694543 100644 --- a/test/support/integration_helper.rb +++ b/test/support/integration_helper.rb @@ -12,6 +12,13 @@ def login_as(user) super(employee) end + def set_period(start_date: '1.1.2006', end_date: '31.12.2006', back_url: current_url) + visit periods_path(back_url: back_url) + fill_in 'period_start_date', with: start_date, fill_options: { clear: :backspace } + fill_in 'period_end_date', with: end_date, fill_options: { clear: :backspace } + find('input[name=commit]').click + end + # catch some errors occuring now and then in capybara tests def timeout_safe yield diff --git a/test/test_helper.rb b/test/test_helper.rb index 0c496716..76210c2d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -48,7 +48,7 @@ Selenium::WebDriver::Firefox::Binary.path = ENV['FIREFOX_PATH'] if ENV['FIREFOX_PATH'] capa = Selenium::WebDriver::Remote::Capabilities.firefox(marionette: true) - Capybara::Selenium::Driver.new(app, browser: :firefox, desired_capabilities: capa) + Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capa) end Capybara.server = :puma, { Silent: true } @@ -100,6 +100,10 @@ def setup_regular_holidays(years) end end end + + def set_period(start_date: '1.1.2006', end_date: '31.12.2006') + @controller.session[:period] = [start_date, end_date] + end end class ActionDispatch::IntegrationTest From c98bb4d1d304a7c06d76623db07138c4bd9ee533 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 2 Sep 2022 11:37:19 +0200 Subject: [PATCH 036/163] Test for musttime calculation --- app/models/util/period.rb | 2 ++ test/models/util/employee_statistics_test.rb | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/app/models/util/period.rb b/app/models/util/period.rb index 0af14a64..711ef406 100644 --- a/app/models/util/period.rb +++ b/app/models/util/period.rb @@ -222,6 +222,8 @@ def &(other) end def step(size = 1) + return @start_date.step(@end_date, size) unless block_given? + @start_date.step(@end_date, size) do |date| yield date end diff --git a/test/models/util/employee_statistics_test.rb b/test/models/util/employee_statistics_test.rb index 26066c33..d452cdd8 100644 --- a/test/models/util/employee_statistics_test.rb +++ b/test/models/util/employee_statistics_test.rb @@ -42,6 +42,23 @@ class EmployeeStatisticsTest < ActiveSupport::TestCase end end + test 'musttime calculates correctly' do + period = Period.new('01.11.2006', '30.11.2006') + + employee = Fabricate(:employee) + employment = Fabricate(:employment, employee: employee, percent: 100, start_date: '01.01.2006', end_date: '31.12.2006') + assert_equal 176.0, employee.statistics.musttime(period) + + employment.update(end_date: '15.11.2006') + assert_equal 88.0, employee.statistics.musttime(period) + + employment2 = Fabricate(:employment, employee: employee, percent: 100, start_date: '16.11.2006', end_date: '31.12.2006') + assert_equal 176.0, employee.statistics.musttime(period) + + employment2.update(percent: 50) + assert_equal 132.0, employee.statistics.musttime(period) + end + test 'remaining worktime is affected by' do period = Period.new('01.12.2006', '11.12.2006') method = 'statistics.pending_worktime(period).to_f' From 3c7808fab676f09d98d3a8c594d47cc28698ca5c Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Mon, 5 Sep 2022 16:25:28 +0200 Subject: [PATCH 037/163] Fix calculation of employee average percent, fixes #60085 --- .../reports/extended_capacity_report.rb | 6 +----- app/models/util/employee_statistics.rb | 6 ++++++ test/models/util/employee_statistics_test.rb | 20 +++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/app/domain/reports/extended_capacity_report.rb b/app/domain/reports/extended_capacity_report.rb index 3e08e1cc..dd9ef445 100644 --- a/app/domain/reports/extended_capacity_report.rb +++ b/app/domain/reports/extended_capacity_report.rb @@ -73,7 +73,7 @@ def add_employees(csv) def employee_summary_row(employee, rows) [employee.shortname, '', - employee_average_percents(employee), + employee.statistics.average_percents(@period), employee.statistics.musttime(@period), employee.statistics.overtime(@period), employee.statistics.current_overtime(@period.end_date), @@ -182,10 +182,6 @@ def internal?(work_item) Array.wrap(work_item.path_ids).include?(Company.work_item_id) end - def employee_average_percents(employee) - employee.statistics.employments_during(@period).sum(&:percent) - end - def employee_remaining_vacations(employee) employee.statistics.remaining_vacations(Date.new(@period.end_date.year, 12, 31)) end diff --git a/app/models/util/employee_statistics.rb b/app/models/util/employee_statistics.rb index fab35f85..3e3a81da 100644 --- a/app/models/util/employee_statistics.rb +++ b/app/models/util/employee_statistics.rb @@ -10,6 +10,12 @@ def initialize(employee) @employee = employee end + def average_percents(period) + employments_during(period).map do |employment| + employment.period.length.to_f / period.length * employment.percent + end.sum + end + ######### vacation information ############ # Returns the unused days of vacation remaining until the end of the current year. diff --git a/test/models/util/employee_statistics_test.rb b/test/models/util/employee_statistics_test.rb index d452cdd8..e9ae4528 100644 --- a/test/models/util/employee_statistics_test.rb +++ b/test/models/util/employee_statistics_test.rb @@ -72,6 +72,26 @@ class EmployeeStatisticsTest < ActiveSupport::TestCase end # rubocop:enable Lint/UselessAssignment + test 'average_percents calculates correctly' do + period = Period.new('01.11.2006', '30.11.2006') + + employee = Fabricate(:employee) + employment = Fabricate(:employment, employee: employee, percent: 100, start_date: '01.01.2006', end_date: '31.12.2006') + assert_equal 100.0, employee.statistics.average_percents(period) + + employment.update(end_date: '15.11.2006') + assert_equal 50.0, employee.statistics.average_percents(period) + + employment2 = Fabricate(:employment, employee: employee, percent: 100, start_date: '16.11.2006', end_date: '31.12.2006') + assert_equal 100.0, employee.statistics.average_percents(period) + + employment2.update(percent: 50) + assert_equal 75.0, employee.statistics.average_percents(period) + + employment2.update(percent: 100, start_date: '23.11.2006', end_date: '31.11.2006') + assert_equal 75.0, employee.statistics.average_percents(period) + end + private def create_employments From 807517d431ba9624360720dc6d8d8fb45d026ca7 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Mon, 5 Sep 2022 17:55:04 +0200 Subject: [PATCH 038/163] Add 'cleaned' project hours column to capacity report, fixes #60086 --- .../reports/extended_capacity_report.rb | 18 ++++-- app/models/util/employee_statistics.rb | 10 ++++ .../reports/extended_capacity_report_test.rb | 59 ++++++++++--------- test/models/util/employee_statistics_test.rb | 39 +++++++++++- 4 files changed, 92 insertions(+), 34 deletions(-) diff --git a/app/domain/reports/extended_capacity_report.rb b/app/domain/reports/extended_capacity_report.rb index dd9ef445..cc370cd8 100644 --- a/app/domain/reports/extended_capacity_report.rb +++ b/app/domain/reports/extended_capacity_report.rb @@ -37,6 +37,7 @@ def add_header(csv) 'Subprojektname', 'Projekte Total (h)', 'Projekte Total - Detail (h)', + 'Ist bereinigt ver. Rollenanteile (h)', 'Stundensatz', 'Kunden-Projekte Total (h)', 'Kunden-Projekte Total - Detail (h)', @@ -71,6 +72,11 @@ def add_employees(csv) end def employee_summary_row(employee, rows) + total_percents_at_start = employee.statistics.percents_at(@period.start_date) + billable_percents_at_start = employee.statistics.billable_percents_at(@period.start_date) + projekte_total_h = rows.map { |r| r[12].to_f }.sum + projekte_total_h_bereinigt = projekte_total_h * billable_percents_at_start / total_percents_at_start + [employee.shortname, '', employee.statistics.average_percents(@period), @@ -82,16 +88,17 @@ def employee_summary_row(employee, rows) '', '', '', - rows.map { |r| r[12].to_f }.sum, # Projekte Total (h) + projekte_total_h, # Projekte Total (h) '', + projekte_total_h_bereinigt, # Ist bereinigt ver. Rollenanteile (h) '', - rows.map { |r| r[15].to_f }.sum, # Kunden-Projekte Total (h) + rows.map { |r| r[16].to_f }.sum, # Kunden-Projekte Total (h) '', - rows.map { |r| r[17].to_f }.sum, # Kunden-Projekte Total verrechenbar (h) + rows.map { |r| r[18].to_f }.sum, # Kunden-Projekte Total verrechenbar (h) '', - rows.map { |r| r[19].to_f }.sum, # Kunden-Projekte Total nicht verrechenbar (h) + rows.map { |r| r[20].to_f }.sum, # Kunden-Projekte Total nicht verrechenbar (h) '', - rows.map { |r| r[21].to_f }.sum, # Interne Projekte Total (h) + rows.map { |r| r[22].to_f }.sum, # Interne Projekte Total (h) ''] end @@ -145,6 +152,7 @@ def build_employee_row(employee, work_item, data = {}) subwork_item_label(parent, child), '', employee_overall_total(data), + '', offered_rate(work_item), '', employee_customer_total(data), diff --git a/app/models/util/employee_statistics.rb b/app/models/util/employee_statistics.rb index 3e3a81da..e9175f94 100644 --- a/app/models/util/employee_statistics.rb +++ b/app/models/util/employee_statistics.rb @@ -10,6 +10,16 @@ def initialize(employee) @employee = employee end + ######### employment percent information ############ + + def percents_at(date) + employee.employment_at(date)&.percent || 0 + end + + def billable_percents_at(date) + employee.employment_at(date)&.employment_roles_employments&.select {|e| e.employment_role.billable }&.map(&:percent)&.sum || 0 + end + def average_percents(period) employments_during(period).map do |employment| employment.period.length.to_f / period.length * employment.percent diff --git a/test/domain/reports/extended_capacity_report_test.rb b/test/domain/reports/extended_capacity_report_test.rb index 58bcbed5..ebf697bb 100644 --- a/test/domain/reports/extended_capacity_report_test.rb +++ b/test/domain/reports/extended_capacity_report_test.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# Copyright (c) 2106-2117, Puzzle ITC GmbH. This file is part of # PuzzleTime and licensed under the Affero General Public License version 3 # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. @@ -26,17 +26,20 @@ class ExtendedCapacityReportTest < ActiveSupport::TestCase assert_equal 'Projekte Total (h)', header[11] assert_equal '21.0', summary[11] - assert_equal 'Kunden-Projekte Total (h)', header[14] - assert_equal '13.0', summary[14] + assert_equal 'Ist bereinigt ver. Rollenanteile (h)', header[13] + assert_equal '21.0', summary[11] + + assert_equal 'Kunden-Projekte Total (h)', header[15] + assert_equal '13.0', summary[15] - assert_equal 'Kunden-Projekte Total verrechenbar (h)', header[16] - assert_equal '6.0', summary[16] + assert_equal 'Kunden-Projekte Total verrechenbar (h)', header[17] + assert_equal '6.0', summary[17] - assert_equal 'Kunden-Projekte Total nicht verrechenbar (h)', header[18] - assert_equal '7.0', summary[18] + assert_equal 'Kunden-Projekte Total nicht verrechenbar (h)', header[19] + assert_equal '7.0', summary[19] - assert_equal 'Interne Projekte Total (h)', header[20] - assert_equal '8.0', summary[20] + assert_equal 'Interne Projekte Total (h)', header[21] + assert_equal '8.0', summary[21] webauftritt = rows.second shop = rows.third @@ -57,25 +60,25 @@ class ExtendedCapacityReportTest < ActiveSupport::TestCase assert_equal '10.0', shop[12] assert_equal '8.0', ptime[12] - assert_equal 'Kunden-Projekte Total - Detail (h)', header[15] - assert_equal '3.0', webauftritt[15] - assert_equal '10.0', shop[15] - assert_equal '', ptime[15] - - assert_equal 'Kunden-Projekte Total verrechenbar - Detail (h)', header[17] - assert_equal '2.0', webauftritt[17] - assert_equal '4.0', shop[17] - assert_equal '', ptime[17] - - assert_equal 'Kunden-Projekte Total nicht verrechenbar - Detail (h)', header[19] - assert_equal '1.0', webauftritt[19] - assert_equal '6.0', shop[19] - assert_equal '', ptime[19] - - assert_equal 'Interne Projekte Total - Detail (h)', header[21] - assert_equal '', webauftritt[21] - assert_equal '', shop[21] - assert_equal '8.0', ptime[21] + assert_equal 'Kunden-Projekte Total - Detail (h)', header[16] + assert_equal '3.0', webauftritt[16] + assert_equal '10.0', shop[16] + assert_equal '', ptime[16] + + assert_equal 'Kunden-Projekte Total verrechenbar - Detail (h)', header[18] + assert_equal '2.0', webauftritt[18] + assert_equal '4.0', shop[18] + assert_equal '', ptime[18] + + assert_equal 'Kunden-Projekte Total nicht verrechenbar - Detail (h)', header[20] + assert_equal '1.0', webauftritt[20] + assert_equal '6.0', shop[20] + assert_equal '', ptime[20] + + assert_equal 'Interne Projekte Total - Detail (h)', header[22] + assert_equal '', webauftritt[22] + assert_equal '', shop[22] + assert_equal '8.0', ptime[22] end private diff --git a/test/models/util/employee_statistics_test.rb b/test/models/util/employee_statistics_test.rb index e9ae4528..f7a709c5 100644 --- a/test/models/util/employee_statistics_test.rb +++ b/test/models/util/employee_statistics_test.rb @@ -72,7 +72,7 @@ class EmployeeStatisticsTest < ActiveSupport::TestCase end # rubocop:enable Lint/UselessAssignment - test 'average_percents calculates correctly' do + test '#average_percents calculates correctly' do period = Period.new('01.11.2006', '30.11.2006') employee = Fabricate(:employee) @@ -92,6 +92,43 @@ class EmployeeStatisticsTest < ActiveSupport::TestCase assert_equal 75.0, employee.statistics.average_percents(period) end + test '#percents_at' do + employee = Fabricate(:employee) + assert_equal 0, employee.statistics.percents_at('16.11.2006') + + employment = Fabricate(:employment, employee: employee, percent: 80, start_date: '01.01.2006', end_date: '31.12.2006') + assert_equal 80, employee.statistics.percents_at('16.11.2006') + + employment.update(percent: 65, start_date: '16.11.2006', end_date: '16.11.2006') + assert_equal 65, employee.statistics.percents_at('16.11.2006') + end + + test '#billable_percents_at' do + employee = Fabricate(:employee) + assert_equal 0, employee.statistics.billable_percents_at('16.11.2006') + + employment = Fabricate(:employment, employee: employee, percent: 80, start_date: '01.01.2006', end_date: '31.12.2006') + assert_equal 0, employee.statistics.billable_percents_at('16.11.2006') + + billable = employment.employment_roles_employments.create!( + percent: 80, + employment_role_level: employment_role_levels(:junior), + employment_role: employment_roles(:system_technician) + ) + assert_equal 80, employee.statistics.billable_percents_at('16.11.2006') + + billable.update(percent: 55) + non_billable = employment.employment_roles_employments.create!( + percent: 25, + employment_role: employment_roles(:technical_board) + ) + assert_equal 55, employee.statistics.billable_percents_at('16.11.2006') + + billable.destroy + non_billable.update!(percent: 80) + assert_equal 0, employee.statistics.billable_percents_at('16.11.2006') + end + private def create_employments From 65c41e0f5a32ff877ae1a22844a004cb21605f8f Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Wed, 7 Sep 2022 10:54:00 +0200 Subject: [PATCH 039/163] Respect absence type filter on link to details, fixes #60108 --- app/controllers/evaluator_controller.rb | 1 + app/views/evaluator/_timecell.html.haml | 3 ++- app/views/evaluator/details.html.haml | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/evaluator_controller.rb b/app/controllers/evaluator_controller.rb index 25dc26a5..601dc058 100644 --- a/app/controllers/evaluator_controller.rb +++ b/app/controllers/evaluator_controller.rb @@ -30,6 +30,7 @@ def overview end def details + @absence = Absence.find_by(id: params[:absence_id]) if params[:absence_id] set_navigation_levels set_evaluation_details paginate_times diff --git a/app/views/evaluator/_timecell.html.haml b/app/views/evaluator/_timecell.html.haml index c552693a..f0236f48 100644 --- a/app/views/evaluator/_timecell.html.haml +++ b/app/views/evaluator/_timecell.html.haml @@ -37,7 +37,8 @@ action: action, evaluation: evaluation_name, category_id: evaluation.category_id, - division_id: division_id.presence) + division_id: division_id.presence, + absence_id: params[:absence_id]) - link += '&' + ( p ? p.url_query_s : 'start_date=0' ) -# use html link instead of link_to for performance reasons %a{ href: link.html_safe }= image_tag('lupe.gif', size: '19x11', alt: 'Details') diff --git a/app/views/evaluator/details.html.haml b/app/views/evaluator/details.html.haml index bf87c12d..bbf81449 100644 --- a/app/views/evaluator/details.html.haml +++ b/app/views/evaluator/details.html.haml @@ -9,6 +9,11 @@ %h3= @evaluation.category_label %h3= @evaluation.division_label +- if @absence + %h3 + Absenztyp: + = @absence + = render 'detail_times' %p From d35255a2ead8e5e5e09f005baf20f5f7fbf645f2 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 9 Sep 2022 11:16:36 +0200 Subject: [PATCH 040/163] Update CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9153b85..64ff8d17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ### Improvements * **Rechnungsstellung:** Umstellung auf SmallInvoice APIv2 (vorher v1) +# 2.9 +### Improvements +* **Log:** Änderungen an den Funktionsanteilen der Anstellungen werden neu im Members-Log protokolliert +* **Absenzen:** In der Auswertung kann nach Absenztyp gefiltert werden +* **Auslastung:** Verwendet nun die Standard Zeitbereich Auswahl. +* **CSV Detaillierte Auslastung:** + + Berücksichtigt nun den eingestellten Zeitbereich + + Berechnung des durchschnittlichen Arbeitspensums korrigiert + + Spalte hinzugefügt für "bereinigte Projektzeit" + # 2.8 ### Features * **Login:** Login wird auf SSO (Keycloak, Devise) umgestellt From 373f118e10ef7c72ab760fcdd50da411c1dca441 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 29 Apr 2022 08:20:06 +0200 Subject: [PATCH 041/163] Downgrade nokogiri to fix prod build --- .tool-versions | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.tool-versions b/.tool-versions index 70787422..16cf04a6 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -ruby 2.7.2 +ruby 2.5.5 nodejs 14.4.0 diff --git a/Gemfile.lock b/Gemfile.lock index b6dfbbb9..22d68882 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -309,7 +309,7 @@ GEM method_source (1.0.0) mini_magick (4.11.0) mini_mime (1.1.2) - mini_portile2 (2.8.0) + mini_portile2 (2.6.1) minitest (5.15.0) minitest-reporters (1.4.3) ansi @@ -327,8 +327,8 @@ GEM rails (>= 3.2.0) net-ldap (0.17.0) nio4r (2.5.8) - nokogiri (1.13.2) - mini_portile2 (~> 2.8.0) + nokogiri (1.12.5) + mini_portile2 (~> 2.6.1) racc (~> 1.4) oauth2 (1.4.7) faraday (>= 0.8, < 2.0) From 0a297406f137d136ca04a516b4e4f8b14911e1c3 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 9 Sep 2022 11:26:24 +0200 Subject: [PATCH 042/163] Update config/version.rb --- config/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/version.rb b/config/version.rb index 3bcf319b..e570c5bc 100644 --- a/config/version.rb +++ b/config/version.rb @@ -1,3 +1,3 @@ module Puzzletime - VERSION = '2.8' + VERSION = '2.9' end From 7e53a8485c2dabba73bd27826d0c6b94e6da32f1 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 2 Sep 2022 09:30:04 +0200 Subject: [PATCH 043/163] Fix environment startup in case the db does not exist yet --- config/application.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/config/application.rb b/config/application.rb index 1c725736..50466d84 100644 --- a/config/application.rb +++ b/config/application.rb @@ -74,9 +74,14 @@ class Application < Rails::Application } config.to_prepare do |_| - Crm.init - Invoicing.init - CommitReminderJob.schedule + begin + Crm.init + Invoicing.init + CommitReminderJob.schedule + rescue ActiveRecord::StatementInvalid => e + # the db might not exist yet, lets ignore the error in this case + raise e unless e.message =~ /PG::UndefinedTable/ || e.message =~ /does not exist/ + end end end From 7db14b314c36502bf64a30fc331c066f4fef1500 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Fri, 9 Sep 2022 16:19:38 +0200 Subject: [PATCH 044/163] Revert "Auto redirect to authorize url on login page, fixes #59739" This reverts commit 69d9dcc407cac7a77350e3287ae576ffad969998. --- .../employees/sessions_controller.rb | 34 ---------- app/models/employee.rb | 7 ++ config/routes.rb | 2 +- .../employees/sessions_controller_test.rb | 65 ------------------- 4 files changed, 8 insertions(+), 100 deletions(-) delete mode 100644 app/controllers/employees/sessions_controller.rb delete mode 100644 test/controllers/employees/sessions_controller_test.rb diff --git a/app/controllers/employees/sessions_controller.rb b/app/controllers/employees/sessions_controller.rb deleted file mode 100644 index 65f0438e..00000000 --- a/app/controllers/employees/sessions_controller.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -class Employees::SessionsController < Devise::SessionsController - # GET /resource/sign_in - def new - return redirect_to auto_redirect_path if auto_redirect? - - super - end - - private - - def local_auth? - Settings.auth.db.active - end - - def single_omniauth_provider? - Settings.auth&.omniauth&.map(&:second)&.map(&:active)&.one? - end - - def auto_redirect? - !local_auth? && single_omniauth_provider? - end - - def auto_redirect_path - provider = Settings.auth&.omniauth.to_h.find { |_, options| options[:active] }&.first - public_send("employee_#{provider}_omniauth_authorize_path") - end -end diff --git a/app/models/employee.rb b/app/models/employee.rb index e687fdd8..30079be8 100644 --- a/app/models/employee.rb +++ b/app/models/employee.rb @@ -140,6 +140,13 @@ def providers end class << self + # Tries to login a user with the passed data. + # Returns the logged-in Employee or nil if the login failed. + def login(username, pwd) + find_by(shortname: username.upcase, passwd: encode(pwd)) || + LdapAuthenticator.new.login(username, pwd) + end + def employed_ones(period, sort = true) result = joins('left join employments em on em.employee_id = employees.id'). where('(em.end_date IS null or em.end_date >= ?) AND em.start_date <= ?', diff --git a/config/routes.rb b/config/routes.rb index e728bca0..24af575f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,7 @@ # https://github.com/puzzle/puzzletime. Rails.application.routes.draw do - devise_for :employees, controllers: { sessions: 'employees/sessions', omniauth_callbacks: 'employees/omniauth_callbacks' }, skip: [:registrations] + devise_for :employees, controllers: { omniauth_callbacks: 'employees/omniauth_callbacks' }, skip: [:registrations] as :employee do get 'employees/edit' => 'devise/registrations#edit', :as => 'edit_employee_registration' patch 'employees' => 'devise/registrations#update', :as => 'employee_registration' diff --git a/test/controllers/employees/sessions_controller_test.rb b/test/controllers/employees/sessions_controller_test.rb deleted file mode 100644 index cf72b86b..00000000 --- a/test/controllers/employees/sessions_controller_test.rb +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of -# PuzzleTime and licensed under the Affero General Public License version 3 -# or later. See the COPYING file at the top-level directory or at -# https://github.com/puzzle/puzzletime. - -require 'test_helper' - -class Employees::SessionsControllerTest < ActionController::TestCase - setup do - @request.env['devise.mapping'] = Devise.mappings[:employee] - end - - def test_only_omniauth_keycloakopenid_active - Settings.auth.db.active = false - Settings.auth.omniauth.keycloakopenid.active = true - Settings.auth.omniauth.saml.active = false - - get :new - - assert_redirected_to employee_keycloakopenid_omniauth_authorize_path - end - - def test_only_omniauth_saml_active - Settings.auth.db.active = false - Settings.auth.omniauth.keycloakopenid.active = false - Settings.auth.omniauth.saml.active = true - - get :new - - assert_redirected_to employee_saml_omniauth_authorize_path - end - - def test_only_local_auth_active - Settings.auth.db.active = true - Settings.auth.omniauth.keycloakopenid.active = false - Settings.auth.omniauth.saml.active = false - - get :new - - assert_response :success - assert_template :new - end - - def test_multiple_omniauth_active - Settings.auth.db.active = false - Settings.auth.omniauth.keycloakopenid.active = true - Settings.auth.omniauth.saml.active = true - - get :new - - assert_response :success - assert_template :new - end - - def test_local_auth_and_single_omniauth_active - Settings.auth.db.active = true - Settings.auth.omniauth.keycloakopenid.active = true - Settings.auth.omniauth.saml.active = false - - get :new - - assert_response :success - assert_template :new - end -end From 22e0d9ea189b2c080c3ad022a133a64e4f55796a Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Wed, 21 Sep 2022 10:44:03 +0200 Subject: [PATCH 045/163] Respect absence type filter in absence evaluation csv export, refs #60108 --- app/helpers/evaluator_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/evaluator_helper.rb b/app/helpers/evaluator_helper.rb index 62d71d00..7c7f0641 100644 --- a/app/helpers/evaluator_helper.rb +++ b/app/helpers/evaluator_helper.rb @@ -5,7 +5,7 @@ module EvaluatorHelper def evaluation_detail_params - params.permit(:evaluation, :category_id, :division_id, :start_date, :end_date, :page) + params.permit(:evaluation, :category_id, :division_id, :absence_id, :start_date, :end_date, :page) end def evaluation_path(evaluation, options = {}) From 397868b7fb573bbcdb9d793ef7477886818c826a Mon Sep 17 00:00:00 2001 From: Thomas Burkhalter Date: Wed, 2 Nov 2022 19:10:36 +0100 Subject: [PATCH 046/163] Secure cookies and hopefully fix login issues --- config/initializers/session_store.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index f55f077b..4fff2610 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -14,7 +14,12 @@ def memcache_configured? end end -Rails.application.config.session_store ActionDispatch::Session::CacheStore, expire_after: 12.hours +secure_cookies = !(Rails.env.development? || Rails.env.test?) + +Rails.application.config.session_store ActionDispatch::Session::CacheStore, + expire_after: 12.hours, + same_site: :lax, + secure: secure_cookies # We expect memcache to work in production. Prevents an error with the rails console on OpenShift if memcache_configured? && !Rails.env.production? && !dalli_reachable? From 50ff026a06669850f3b1ca95290821e1284cf8ee Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 18 Aug 2022 18:01:22 +0200 Subject: [PATCH 047/163] Auto redirect on login page when single auth method configured, fixes #59739 --- .../omniauth_callbacks_controller.rb | 4 ++ .../employees/sessions_controller.rb | 34 +++++++++++ app/models/employee.rb | 7 --- app/views/devise/shared/_links.html.haml | 8 ++- config/routes.rb | 2 +- .../employees/sessions_controller_test.rb | 59 +++++++++++++++++++ .../integration/employees/new_session_test.rb | 57 ++++++++++++++++++ 7 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 app/controllers/employees/sessions_controller.rb create mode 100644 test/controllers/employees/sessions_controller_test.rb create mode 100644 test/integration/employees/new_session_test.rb diff --git a/app/controllers/employees/omniauth_callbacks_controller.rb b/app/controllers/employees/omniauth_callbacks_controller.rb index d1aae90b..d73d6460 100644 --- a/app/controllers/employees/omniauth_callbacks_controller.rb +++ b/app/controllers/employees/omniauth_callbacks_controller.rb @@ -16,4 +16,8 @@ def default # TODO: Username wegspeichern alias keycloakopenid default alias saml default + + def after_omniauth_failure_path_for(scope) + new_session_path(scope, prevent_auto_login: true) + end end diff --git a/app/controllers/employees/sessions_controller.rb b/app/controllers/employees/sessions_controller.rb new file mode 100644 index 00000000..28a1fdfc --- /dev/null +++ b/app/controllers/employees/sessions_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +class Employees::SessionsController < Devise::SessionsController + helper_method :auto_redirect? + + private + + def no_local_auth? + !Settings.auth.db.active + end + + def omniauth_providers_active + Settings.auth&.omniauth&.map(&:second)&.map(&:active) + end + + def single_omniauth_provider? + omniauth_providers_active&.one? + end + + def auto_login_allowed? + return true unless prevent = params[:prevent_auto_login] + + !ActiveRecord::Type::Boolean.new.deserialize(prevent) + end + + def auto_redirect? + auto_login_allowed? && no_local_auth? && single_omniauth_provider? + end +end diff --git a/app/models/employee.rb b/app/models/employee.rb index 30079be8..e687fdd8 100644 --- a/app/models/employee.rb +++ b/app/models/employee.rb @@ -140,13 +140,6 @@ def providers end class << self - # Tries to login a user with the passed data. - # Returns the logged-in Employee or nil if the login failed. - def login(username, pwd) - find_by(shortname: username.upcase, passwd: encode(pwd)) || - LdapAuthenticator.new.login(username, pwd) - end - def employed_ones(period, sort = true) result = joins('left join employments em on em.employee_id = employees.id'). where('(em.end_date IS null or em.end_date >= ?) AND em.start_date <= ?', diff --git a/app/views/devise/shared/_links.html.haml b/app/views/devise/shared/_links.html.haml index 6b9c859d..f8f1a53b 100644 --- a/app/views/devise/shared/_links.html.haml +++ b/app/views/devise/shared/_links.html.haml @@ -25,6 +25,12 @@ = link_to "Mit #{provider_label} anmelden", public_send("#{resource_name}_#{provider}_omniauth_authorize_path"), method: :post, - class: 'btn btn-primary' + class: try(:auto_redirect?) ? 'btn btn-primary auto-login' : 'btn btn-primary' %br %br + +- if try(:auto_redirect?) + :coffeescript + $(document).on('turbolinks:load', -> + $('.auto-login').click(); + ) \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 24af575f..e728bca0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,7 @@ # https://github.com/puzzle/puzzletime. Rails.application.routes.draw do - devise_for :employees, controllers: { omniauth_callbacks: 'employees/omniauth_callbacks' }, skip: [:registrations] + devise_for :employees, controllers: { sessions: 'employees/sessions', omniauth_callbacks: 'employees/omniauth_callbacks' }, skip: [:registrations] as :employee do get 'employees/edit' => 'devise/registrations#edit', :as => 'edit_employee_registration' patch 'employees' => 'devise/registrations#update', :as => 'employee_registration' diff --git a/test/controllers/employees/sessions_controller_test.rb b/test/controllers/employees/sessions_controller_test.rb new file mode 100644 index 00000000..a6431c1f --- /dev/null +++ b/test/controllers/employees/sessions_controller_test.rb @@ -0,0 +1,59 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +require 'test_helper' + +class Employees::SessionsControllerTest < ActionController::TestCase + setup do + @request.env['devise.mapping'] = Devise.mappings[:employee] + end + + test "helper auto_redirect? with only omniauth keycloadopenid active" do + Settings.auth.db.active = false + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = false + assert @controller.view_context.auto_redirect? + end + + test "helper auto_redirect? with only omniauth saml active" do + Settings.auth.db.active = false + Settings.auth.omniauth.keycloakopenid.active = false + Settings.auth.omniauth.saml.active = true + assert @controller.view_context.auto_redirect? + end + + test "helper auto_redirect? with only local auth active" do + Settings.auth.db.active = true + Settings.auth.omniauth.keycloakopenid.active = false + Settings.auth.omniauth.saml.active = false + refute @controller.view_context.auto_redirect? + end + + test "helper auto_redirect? with multiple omniauth active" do + Settings.auth.db.active = false + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = true + refute @controller.view_context.auto_redirect? + end + + test "helper auto_redirect? with local auth and single omniauth active" do + Settings.auth.db.active = true + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = false + refute @controller.view_context.auto_redirect? + end + + test "helper auto_redirect? depending on param prevent_auto_login" do + Settings.auth.db.active = false + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = false + + get :new + assert @controller.view_context.auto_redirect? + + get :new, params: {prevent_auto_login: true} + refute @controller.view_context.auto_redirect? + end +end diff --git a/test/integration/employees/new_session_test.rb b/test/integration/employees/new_session_test.rb new file mode 100644 index 00000000..ce97ac5c --- /dev/null +++ b/test/integration/employees/new_session_test.rb @@ -0,0 +1,57 @@ +# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +require 'test_helper' + +class Employees::NewSessionTest < ActionDispatch::IntegrationTest + def setup + # We use the rack_test driver as this one does not evaluate javascript. + # This is required as we want to test if the page contains the necessary class attribute and javascript snippet + # to execute the auto login. For this the auto login redirect can't actually happen. + Capybara.current_driver = :rack_test + end + + def teardown + # Let's restore the original driver. + Capybara.use_default_driver + end + + test 'login button has auto-login class if eligible' do + Settings.auth.db.active = false + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = false + + visit new_employee_session_path + assert_selector 'a.auto-login', text: 'Mit Puzzle SSO anmelden' + end + + test 'login button does not have auto-login class if uneligible' do + Settings.auth.db.active = true + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = false + + visit new_employee_session_path + assert_selector 'a', text: 'Mit Puzzle SSO anmelden' + assert_no_selector 'a.auto-login', text: 'Mit Puzzle SSO anmelden' + end + + test 'page includes auto-login javascript if eligible' do + Settings.auth.db.active = false + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = false + + visit new_employee_session_path + assert page.text(:all).include? "$('.auto-login').click()" + end + + test 'page excludes auto-login javascript if uneligible' do + Settings.auth.db.active = true + Settings.auth.omniauth.keycloakopenid.active = true + Settings.auth.omniauth.saml.active = false + + visit new_employee_session_path + assert page.text(:all).exclude? "$('.auto-login').click()" + end +end From d00604e00911a7ffb5b1c368d041ba5a61ce4ac1 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Mon, 14 Nov 2022 15:07:03 +0100 Subject: [PATCH 048/163] Update config/version.rb, CHANGELOG.md --- CHANGELOG.md | 6 ++++++ config/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ff8d17..0e60eb7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ### Improvements * **Rechnungsstellung:** Umstellung auf SmallInvoice APIv2 (vorher v1) +# 2.10 +### Improvements +* Auto-redirect zum SSO login sofern genau 1 SSO Provider konfiguriert ist und localauth deaktiviert ist +* Secure flag auf session cookie gesetzt +* Absenztyp Filter wird nun auch für Absenzen Export respektiert + # 2.9 ### Improvements * **Log:** Änderungen an den Funktionsanteilen der Anstellungen werden neu im Members-Log protokolliert diff --git a/config/version.rb b/config/version.rb index e570c5bc..5dc8ec76 100644 --- a/config/version.rb +++ b/config/version.rb @@ -1,3 +1,3 @@ module Puzzletime - VERSION = '2.9' + VERSION = '2.10' end From 755b26588dc12039bba78e68edad7ba06b2309e0 Mon Sep 17 00:00:00 2001 From: Thomas Burkhalter Date: Tue, 7 Feb 2023 23:49:40 +0100 Subject: [PATCH 049/163] remove editor specific configs --- .vscode/settings.json | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6300bbbd..e9344f39 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,39 +1,5 @@ { - "rubyTestExplorer.logpanel": true, - "rubyTestExplorer.testFramework": "minitest", - "saveEditorLayout.list": [ - { - "name": "ptime: api tests", - "documents": [ - { - "fsPath": "/code/pz/puzzletime/test/small_invoice_test_helper.rb", - "column": 1 - }, - { - "fsPath": "/code/pz/puzzletime/app/domain/invoicing/small_invoice/contact_sync.rb", - "column": 1 - }, - { - "fsPath": "/code/pz/puzzletime/test/domain/invoicing/small_invoice/contact_sync_test.rb", - "column": 1 - }, - { - "fsPath": "/code/pz/puzzletime/test/domain/invoicing/small_invoice/api_test.rb", - "column": 2 - }, - { - "fsPath": "/code/pz/puzzletime/test/domain/invoicing/small_invoice/address_sync_test.rb", - "column": 2 - }, - { - "fsPath": "/code/pz/puzzletime/test/domain/invoicing/small_invoice/interface_test.rb", - "column": 2 - }, - { - "fsPath": "/code/pz/puzzletime/test/domain/invoicing/small_invoice/invoice_store_test.rb", - "column": 2 - } - ] - } - ] + "i18n-ally.localesPaths": [ + "config/locales" + ] } \ No newline at end of file From 867fb8df12024d75ac714ed0bcfda67cd3f3438c Mon Sep 17 00:00:00 2001 From: Thomas Burkhalter Date: Mon, 6 Feb 2023 10:59:49 +0100 Subject: [PATCH 050/163] WIP: Upgrade to 6.0.6.1 --- .tool-versions | 2 +- Gemfile | 9 +- Gemfile.lock | 601 ++++++++++--------- app/assets/javascripts/application.js.coffee | 2 +- config/application.rb | 2 +- config/environments/development.rb | 40 +- config/initializers/session_store.rb | 35 +- 7 files changed, 373 insertions(+), 318 deletions(-) diff --git a/.tool-versions b/.tool-versions index 16cf04a6..0003ae98 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -ruby 2.5.5 +ruby 2.7.7 nodejs 14.4.0 diff --git a/Gemfile b/Gemfile index 35375953..376f1957 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,10 @@ source 'https://rubygems.org' -gem 'rails', '~> 5.2.x' +git_source(:github) { |name| "https://github.com/#{name}.git" } -gem 'pg', '~> 0.21.0' + gem 'rails', '~> 6.0.6.1' + +gem 'pg' #, '~> 0.21.0' gem 'activerecord-nulldb-adapter' gem 'nochmal', github: 'puzzle/nochmal' @@ -37,6 +39,7 @@ gem 'omniauth-saml' gem 'prawn' gem 'prometheus_exporter' gem 'protective' +gem 'psych', '~> 3.0' gem 'puma' gem 'rails_autolink' gem 'rails-i18n' @@ -116,3 +119,5 @@ group :metrics do gem 'sdoc' gem 'simplecov-rcov', git: 'https://github.com/puzzle/simplecov-rcov' end + +gem "listen", "~> 3.8" diff --git a/Gemfile.lock b/Gemfile.lock index 22d68882..306baf29 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,10 @@ GIT remote: https://github.com/puzzle/nochmal.git - revision: d66a5e9a99e550084d29763d1395e1529fa17c2a + revision: 55a9cfbade16e78396ce69dbbb6267864d35fc84 specs: - nochmal (0.1.0) + nochmal (0.2.1) + pastel + rails (>= 6.0) GIT remote: https://github.com/puzzle/simplecov-rcov @@ -14,126 +16,139 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (5.2.7) - actionpack (= 5.2.7) + actioncable (6.0.6.1) + actionpack (= 6.0.6.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.7) - actionpack (= 5.2.7) - actionview (= 5.2.7) - activejob (= 5.2.7) + actionmailbox (6.0.6.1) + actionpack (= 6.0.6.1) + activejob (= 6.0.6.1) + activerecord (= 6.0.6.1) + activestorage (= 6.0.6.1) + activesupport (= 6.0.6.1) + mail (>= 2.7.1) + actionmailer (6.0.6.1) + actionpack (= 6.0.6.1) + actionview (= 6.0.6.1) + activejob (= 6.0.6.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.7) - actionview (= 5.2.7) - activesupport (= 5.2.7) + actionpack (6.0.6.1) + actionview (= 6.0.6.1) + activesupport (= 6.0.6.1) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.7) - activesupport (= 5.2.7) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (6.0.6.1) + actionpack (= 6.0.6.1) + activerecord (= 6.0.6.1) + activestorage (= 6.0.6.1) + activesupport (= 6.0.6.1) + nokogiri (>= 1.8.5) + actionview (6.0.6.1) + activesupport (= 6.0.6.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.7) - activesupport (= 5.2.7) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (6.0.6.1) + activesupport (= 6.0.6.1) globalid (>= 0.3.6) - activemodel (5.2.7) - activesupport (= 5.2.7) + activemodel (6.0.6.1) + activesupport (= 6.0.6.1) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (5.2.7) - activemodel (= 5.2.7) - activesupport (= 5.2.7) - arel (>= 9.0) + activerecord (6.0.6.1) + activemodel (= 6.0.6.1) + activesupport (= 6.0.6.1) activerecord-nulldb-adapter (0.8.0) activerecord (>= 5.2.0, < 7.1) - activeresource (5.1.1) - activemodel (>= 5.0, < 7) + activeresource (6.0.0) + activemodel (>= 6.0) activemodel-serializers-xml (~> 1.0) - activesupport (>= 5.0, < 7) - activestorage (5.2.7) - actionpack (= 5.2.7) - activerecord (= 5.2.7) - marcel (~> 1.0.0) - activesupport (5.2.7) + activesupport (>= 6.0) + activestorage (6.0.6.1) + actionpack (= 6.0.6.1) + activejob (= 6.0.6.1) + activerecord (= 6.0.6.1) + marcel (~> 1.0) + activesupport (6.0.6.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) acts_as_tree (2.9.1) activerecord (>= 3.0.0) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) - airbrake (11.0.3) - airbrake-ruby (~> 5.1) - airbrake-ruby (5.2.1) + airbrake (13.0.3) + airbrake-ruby (~> 6.0) + airbrake-ruby (6.2.0) rbtree3 (~> 0.5) - annotate (3.1.1) - activerecord (>= 3.2, < 7.0) + annotate (3.2.0) + activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) ansi (1.5.0) - arel (9.0.0) ast (2.4.2) - autoprefixer-rails (10.2.5.1) - execjs (> 0) + autoprefixer-rails (10.4.7.0) + execjs (~> 2) aws-eventstream (1.2.0) - aws-partitions (1.496.0) - aws-sdk-core (3.121.0) + aws-partitions (1.705.0) + aws-sdk-core (3.170.0) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.5) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.62.0) + aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.48.0) - aws-sdk-core (~> 3, >= 3.120.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.102.0) - aws-sdk-core (~> 3, >= 3.120.0) + aws-sdk-s3 (1.119.0) + aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.4.0) + aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) - bcrypt (3.1.16) + bcrypt (3.1.18) better_errors (2.9.1) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) - bindata (2.4.10) + bindata (2.4.14) bindex (0.8.1) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) - bleib (0.0.11) - bootsnap (1.7.5) - msgpack (~> 1.0) + bleib (0.0.12) + bootsnap (1.16.0) + msgpack (~> 1.2) bootstrap-sass (3.4.1) autoprefixer-rails (>= 5.2.1) sassc (>= 2.0.0) - brakeman (5.0.4) + brakeman (5.4.0) builder (3.2.4) - bullet (6.1.4) + bullet (7.0.7) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) - bundler-audit (0.8.0) + bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) byebug (11.1.3) - cancancan (3.3.0) - capybara (3.35.3) + cancancan (3.4.0) + capybara (3.38.0) addressable + matrix mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - chartjs-ror (3.6.4) + chartjs-ror (3.7.0) rails (>= 3.1) - childprocess (3.0.0) choice (0.2.0) chunky_png (1.4.0) coderay (1.1.3) @@ -147,260 +162,274 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.10) - config (3.1.0) + concurrent-ruby (1.2.0) + config (4.1.0) deep_merge (~> 1.2, >= 1.2.1) dry-validation (~> 1.0, >= 1.0.0) - countries (4.0.0) - i18n_data (~> 0.13.0) - sixarm_ruby_unaccent (~> 1.1) - country_select (6.0.0) - countries (~> 4.0) - sort_alphabetical (~> 1.1) + countries (5.3.1) + unaccent (~> 0.3) + country_select (8.0.1) + countries (~> 5.0) crack (0.4.5) rexml crass (1.0.6) - daemons (1.4.0) - dalli (2.7.11) + daemons (1.4.1) + dalli (3.2.3) database_cleaner (2.0.1) database_cleaner-active_record (~> 2.0.0) database_cleaner-active_record (2.0.1) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) + date (3.3.3) debug_inspector (1.1.0) - deep_merge (1.2.1) - delayed_cron_job (0.7.4) - delayed_job (>= 4.1) - delayed_job (4.1.9) - activesupport (>= 3.0, < 6.2) - delayed_job_active_record (4.1.6) - activerecord (>= 3.0, < 6.2) + deep_merge (1.2.2) + delayed_cron_job (0.9.0) + fugit (>= 1.5) + delayed_job (4.1.11) + activesupport (>= 3.0, < 8.0) + delayed_job_active_record (4.1.7) + activerecord (>= 3.0, < 8.0) delayed_job (>= 3.0, < 5) - devise (4.8.0) + devise (4.8.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) docile (1.4.0) - dry-configurable (0.12.1) - concurrent-ruby (~> 1.0) - dry-core (~> 0.5, >= 0.5.0) - dry-container (0.7.2) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (0.6.0) + dry-configurable (1.0.1) + dry-core (~> 1.0, < 2) + zeitwerk (~> 2.6) + dry-core (1.0.0) concurrent-ruby (~> 1.0) - dry-equalizer (0.3.0) - dry-inflector (0.2.0) - dry-initializer (3.0.4) - dry-logic (1.2.0) + zeitwerk (~> 2.6) + dry-inflector (1.0.0) + dry-initializer (3.1.1) + dry-logic (1.5.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.5, >= 0.5) - dry-schema (1.6.2) + dry-core (~> 1.0, < 2) + zeitwerk (~> 2.6) + dry-schema (1.13.0) concurrent-ruby (~> 1.0) - dry-configurable (~> 0.8, >= 0.8.3) - dry-core (~> 0.5, >= 0.5) + dry-configurable (~> 1.0, >= 1.0.1) + dry-core (~> 1.0, < 2) dry-initializer (~> 3.0) - dry-logic (~> 1.0) - dry-types (~> 1.5) - dry-types (1.5.1) + dry-logic (>= 1.5, < 2) + dry-types (>= 1.7, < 2) + zeitwerk (~> 2.6) + dry-types (1.7.0) concurrent-ruby (~> 1.0) - dry-container (~> 0.3) - dry-core (~> 0.5, >= 0.5) - dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 1.0, >= 1.0.2) - dry-validation (1.6.0) + dry-core (~> 1.0, < 2) + dry-inflector (~> 1.0, < 2) + dry-logic (>= 1.4, < 2) + zeitwerk (~> 2.6) + dry-validation (1.10.0) concurrent-ruby (~> 1.0) - dry-container (~> 0.7, >= 0.7.1) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) + dry-core (~> 1.0, < 2) dry-initializer (~> 3.0) - dry-schema (~> 1.5, >= 1.5.2) - email_address (0.2.2) + dry-schema (>= 1.12, < 2) + zeitwerk (~> 2.6) + email_address (0.2.4) simpleidn - erubi (1.10.0) + erubi (1.12.0) + et-orbi (1.2.7) + tzinfo execjs (2.8.1) - fabrication (2.22.0) - faker (2.18.0) - i18n (>= 1.6, < 2) - faraday (1.5.1) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0.1) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.1) - faraday-patron (~> 1.0) - multipart-post (>= 1.2, < 3) + fabrication (2.30.0) + faker (3.1.1) + i18n (>= 1.8.11, < 2) + faraday (2.7.4) + faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) + faraday-net_http (3.0.2) fast_jsonapi (1.5) activesupport (>= 4.2) ffi (1.15.5) - globalid (1.0.0) + fugit (1.8.1) + et-orbi (~> 1, >= 1.2.7) + raabro (~> 1.4) + globalid (1.1.0) activesupport (>= 5.0) - haml (5.2.1) - temple (>= 0.8.0) + haml (6.1.1) + temple (>= 0.8.2) + thor tilt haml-lint (0.999.999) haml_lint - haml_lint (0.37.1) - haml (>= 4.0, < 5.3) + haml_lint (0.45.0) + haml (>= 4.0, < 6.2) parallel (~> 1.10) rainbow rubocop (>= 0.50.0) sysexits (~> 1.1) hashdiff (1.0.1) - hashie (4.1.0) + hashie (5.0.0) headless (2.3.1) highrise (3.2.3) activeresource (>= 3.2.13) hpricot (0.8.6) htmlentities (4.3.4) - i18n (1.10.0) + i18n (1.12.0) concurrent-ruby (~> 1.0) - i18n_data (0.13.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - jbuilder (2.11.2) + jbuilder (2.11.5) + actionview (>= 5.0.0) activesupport (>= 5.0.0) - jmespath (1.4.0) - jquery-rails (4.4.0) + jmespath (1.6.2) + jquery-rails (4.5.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) jquery-ui-rails (6.0.1) railties (>= 3.2.16) + json (2.6.3) json-jwt (1.13.0) activesupport (>= 4.2) aes_key_wrap bindata - jwt (2.2.3) - kaminari (1.2.1) + jwt (2.7.0) + kaminari (1.2.2) activesupport (>= 4.1.0) - kaminari-actionview (= 1.2.1) - kaminari-activerecord (= 1.2.1) - kaminari-core (= 1.2.1) - kaminari-actionview (1.2.1) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) actionview - kaminari-core (= 1.2.1) - kaminari-activerecord (1.2.1) + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) activerecord - kaminari-core (= 1.2.1) + kaminari-core (= 1.2.2) kaminari-bootstrap (3.0.1) kaminari (>= 0.13.0) rails - kaminari-core (1.2.1) - loofah (2.16.0) + kaminari-core (1.2.2) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + loofah (2.19.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.1) + mail (2.8.1) mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp marcel (1.0.2) + matrix (0.4.2) method_source (1.0.0) - mini_magick (4.11.0) + mini_magick (4.12.0) mini_mime (1.1.2) - mini_portile2 (2.6.1) - minitest (5.15.0) - minitest-reporters (1.4.3) + mini_portile2 (2.8.1) + minitest (5.17.0) + minitest-reporters (1.5.0) ansi builder minitest (>= 5.0) ruby-progressbar - mocha (1.13.0) - msgpack (1.4.2) - multi_json (1.15.0) + mocha (2.0.2) + ruby2_keywords (>= 0.0.5) + msgpack (1.6.0) multi_xml (0.6.0) - multipart-post (2.1.1) nested_form_fields (0.8.4) coffee-rails (>= 3.2.1) jquery-rails rails (>= 3.2.0) - net-ldap (0.17.0) + net-imap (0.3.4) + date + net-protocol + net-ldap (0.17.1) + net-pop (0.1.2) + net-protocol + net-protocol (0.2.1) + timeout + net-smtp (0.3.3) + net-protocol nio4r (2.5.8) - nokogiri (1.12.5) - mini_portile2 (~> 2.6.1) + nokogiri (1.14.1) + mini_portile2 (~> 2.8.0) racc (~> 1.4) - oauth2 (1.4.7) - faraday (>= 0.8, < 2.0) + oauth2 (2.0.9) + faraday (>= 0.17.3, < 3.0) jwt (>= 1.0, < 3.0) - multi_json (~> 1.3) multi_xml (~> 0.5) - rack (>= 1.2, < 3) - omniauth (2.0.4) + rack (>= 1.2, < 4) + snaky_hash (~> 2.0) + version_gem (~> 1.1) + omniauth (2.1.1) hashie (>= 3.4.6) - rack (>= 1.6.2, < 3) + rack (>= 2.2.3) rack-protection - omniauth-keycloak (1.3.0) + omniauth-keycloak (1.4.4) + faraday json-jwt (~> 1.13.0) - omniauth (~> 2.0.4) + omniauth (>= 2.0) omniauth-oauth2 (~> 1.7.1) - omniauth-oauth2 (1.7.1) - oauth2 (~> 1.4) + omniauth-oauth2 (1.7.3) + oauth2 (>= 1.4, < 3) omniauth (>= 1.9, < 3) - omniauth-rails_csrf_protection (1.0.0) + omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) omniauth (~> 2.0) - omniauth-saml (2.0.0) + omniauth-saml (2.1.0) omniauth (~> 2.0) - ruby-saml (~> 1.9) + ruby-saml (~> 1.12) orm_adapter (0.5.0) - paper_trail (12.0.0) - activerecord (>= 5.2) - request_store (~> 1.1) - parallel (1.20.1) - parser (3.0.2.0) + paper_trail (14.0.0) + activerecord (>= 6.0) + request_store (~> 1.4) + parallel (1.22.1) + parser (3.2.0.0) ast (~> 2.4.1) + pastel (0.8.0) + tty-color (~> 0.5) pdf-core (0.9.0) - pg (0.21.0) + pg (1.4.5) prawn (2.4.0) pdf-core (~> 0.9.0) ttfunk (~> 1.7) - prometheus_exporter (0.7.0) + prometheus_exporter (2.0.8) webrick protective (0.2.0) activerecord - pry (0.13.1) + pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) - pry-byebug (3.9.0) + pry-byebug (3.10.1) byebug (~> 11.0) - pry (~> 0.13.0) - pry-doc (1.1.0) + pry (>= 0.13, < 0.15) + pry-doc (1.4.0) pry (~> 0.11) yard (~> 0.9.11) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (4.0.6) - puma (5.6.4) + psych (3.3.4) + public_suffix (5.0.1) + puma (6.0.2) nio4r (~> 2.0) - racc (1.6.0) - rack (2.2.3) - rack-protection (2.1.0) + raabro (1.4.0) + racc (1.6.2) + rack (2.2.6.2) + rack-protection (3.0.5) rack - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (5.2.7) - actioncable (= 5.2.7) - actionmailer (= 5.2.7) - actionpack (= 5.2.7) - actionview (= 5.2.7) - activejob (= 5.2.7) - activemodel (= 5.2.7) - activerecord (= 5.2.7) - activestorage (= 5.2.7) - activesupport (= 5.2.7) + rack-test (2.0.2) + rack (>= 1.3) + rails (6.0.6.1) + actioncable (= 6.0.6.1) + actionmailbox (= 6.0.6.1) + actionmailer (= 6.0.6.1) + actionpack (= 6.0.6.1) + actiontext (= 6.0.6.1) + actionview (= 6.0.6.1) + activejob (= 6.0.6.1) + activemodel (= 6.0.6.1) + activerecord (= 6.0.6.1) + activestorage (= 6.0.6.1) + activesupport (= 6.0.6.1) bundler (>= 1.3.0) - railties (= 5.2.7) + railties (= 6.0.6.1) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -409,77 +438,81 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-erd (1.6.1) + rails-erd (1.7.2) activerecord (>= 4.2) activesupport (>= 4.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) - rails-html-sanitizer (1.4.2) - loofah (~> 2.3) - rails-i18n (5.1.3) + rails-html-sanitizer (1.5.0) + loofah (~> 2.19, >= 2.19.1) + rails-i18n (7.0.6) i18n (>= 0.7, < 2) - railties (>= 5.0, < 6) - rails_autolink (1.1.6) + railties (>= 6.0.0, < 8) + rails_autolink (1.1.7) rails (> 3.1) - railties (5.2.7) - actionpack (= 5.2.7) - activesupport (= 5.2.7) + railties (6.0.6.1) + actionpack (= 6.0.6.1) + activesupport (= 6.0.6.1) method_source rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rainbow (3.0.0) + thor (>= 0.20.3, < 2.0) + rainbow (3.1.1) rake (13.0.6) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) rb-readline (0.5.5) - rbtree3 (0.6.0) - rdoc (6.3.2) - regexp_parser (2.1.1) + rbtree3 (0.7.0) + rdoc (6.3.3) + regexp_parser (2.6.2) request_profiler (0.0.4) ruby-prof - request_store (1.5.0) + request_store (1.5.1) rack (>= 1.4) - responders (3.0.1) - actionpack (>= 5.0) - railties (>= 5.0) + responders (3.1.0) + actionpack (>= 5.2) + railties (>= 5.2) rexml (3.2.5) - rqrcode (2.0.0) + rqrcode (2.1.2) chunky_png (~> 1.0) rqrcode_core (~> 1.0) - rqrcode_core (1.1.0) - rswag-ui (2.4.0) - actionpack (>= 3.1, < 7.0) - railties (>= 3.1, < 7.0) - rubocop (1.18.3) + rqrcode_core (1.2.0) + rswag-ui (2.8.0) + actionpack (>= 3.1, < 7.1) + railties (>= 3.1, < 7.1) + rubocop (1.44.1) + json (~> 2.3) parallel (~> 1.10) - parser (>= 3.0.0.0) + parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.7.0, < 2.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.24.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.7.0) - parser (>= 3.0.1.1) - rubocop-checkstyle_formatter (0.4.0) - rubocop (>= 0.35.1) - rubocop-minitest (0.14.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.24.1) + parser (>= 3.1.1.0) + rubocop-checkstyle_formatter (0.6.0) + rubocop (>= 1.14.0) + rubocop-minitest (0.27.0) rubocop (>= 0.90, < 2.0) - rubocop-performance (1.11.4) + rubocop-performance (1.16.0) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - rubocop-rails (2.11.3) + rubocop-rails (2.17.4) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 1.7.0, < 2.0) + rubocop (>= 1.33.0, < 2.0) ruby-graphviz (1.2.5) rexml - ruby-prof (1.4.3) + ruby-prof (1.4.5) ruby-progressbar (1.11.0) - ruby-saml (1.12.2) - nokogiri (>= 1.10.5) + ruby-saml (1.15.0) + nokogiri (>= 1.13.10) rexml ruby-vips (2.1.4) ffi (~> 1.12) - ruby2_keywords (0.0.4) + ruby2_keywords (0.0.5) rubyzip (2.3.2) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -491,83 +524,91 @@ GEM sprockets (> 3.0) sprockets-rails tilt - sdoc (2.2.0) + sdoc (2.6.0) rdoc (>= 5.0) seed-fu (2.3.9) activerecord (>= 3.1) activesupport (>= 3.1) selectize-rails (0.12.6) - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) - rubyzip (>= 1.2.2) + selenium-webdriver (4.8.0) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) sentry-raven (3.1.2) faraday (>= 1.0) - simplecov (0.21.2) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) - simplecov_json_formatter (0.1.3) + simplecov_json_formatter (0.1.4) simpleidn (0.2.1) unf (~> 0.1.4) - sixarm_ruby_unaccent (1.2.0) - sort_alphabetical (1.1.0) - unicode_utils (>= 1.2.2) - spring (2.1.1) - sprockets (4.0.3) + snaky_hash (2.0.1) + hashie + version_gem (~> 1.1, >= 1.1.1) + spring (4.1.1) + sprockets (4.2.0) concurrent-ruby (~> 1.0) - rack (> 1, < 3) + rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) swagger-blocks (3.0.0) sysexits (1.2.0) - temple (0.8.2) + temple (0.10.0) thor (1.2.1) thread_safe (0.3.6) - tilt (2.0.10) - timeliness (0.4.4) + tilt (2.0.11) + timeliness (0.4.5) + timeout (0.3.1) ttfunk (1.7.0) + tty-color (0.6.0) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) - tzinfo (1.2.9) + tzinfo (1.2.11) thread_safe (~> 0.1) uglifier (4.2.0) execjs (>= 0.3.0, < 3) + unaccent (0.4.0) unf (0.1.4) unf_ext - unf_ext (0.0.8.1) - unicode-display_width (2.0.0) - unicode_utils (1.4.0) - uniform_notifier (1.14.2) - validates_by_schema (0.4.0) + unf_ext (0.0.8.2) + unicode-display_width (2.4.2) + uniform_notifier (1.16.0) + validates_by_schema (0.5.0) activerecord (>= 5.0.0) - validates_timeliness (5.0.0) + validates_timeliness (6.0.1) + activemodel (>= 6.0.0, < 7) timeliness (>= 0.3.10, < 1) + version_gem (1.1.1) warden (1.2.9) rack (>= 2.0.9) - web-console (3.7.0) - actionview (>= 5.0) - activemodel (>= 5.0) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) bindex (>= 0.4.0) - railties (>= 5.0) - webdrivers (4.6.0) + railties (>= 6.0.0) + webdrivers (5.2.0) nokogiri (~> 1.6) rubyzip (>= 1.3.0) - selenium-webdriver (>= 3.0, < 4.0) - webmock (3.13.0) - addressable (>= 2.3.6) + selenium-webdriver (~> 4.0) + webmock (3.18.1) + addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) webrick (1.7.0) + websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.26) + yard (0.9.28) + webrick (~> 1.7.0) + zeitwerk (2.6.6) PLATFORMS ruby @@ -615,6 +656,7 @@ DEPENDENCIES jquery-ui-rails kaminari kaminari-bootstrap + listen (~> 3.8) minitest-reporters mocha nested_form_fields @@ -626,15 +668,16 @@ DEPENDENCIES omniauth-rails_csrf_protection omniauth-saml paper_trail - pg (~> 0.21.0) + pg prawn prometheus_exporter protective pry-byebug pry-doc pry-rails + psych (~> 3.0) puma - rails (~> 5.2.x) + rails (~> 6.0.6.1) rails-controller-testing rails-erd rails-i18n @@ -667,4 +710,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.3.9 + 2.4.6 diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index de2ce2ae..25afb2d5 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -23,7 +23,7 @@ #= require waypoints/shortcuts/sticky #= require waypoints/shortcuts/inview #= require modernizr-custom -#= require Chart.bundle.min +#= require Chart.min #= require chartjs-plugin-annotation.min #= require_self #= require_tree ./modules diff --git a/config/application.rb b/config/application.rb index 50466d84..70d9a0a4 100644 --- a/config/application.rb +++ b/config/application.rb @@ -60,7 +60,7 @@ class Application < Rails::Application memcached_host = ENV['RAILS_MEMCACHED_HOST'] || 'localhost' memcached_port = ENV['RAILS_MEMCACHED_PORT'] || '11211' - config.cache_store = :dalli_store, "#{memcached_host}:#{memcached_port}" + config.cache_store = :mem_cache_store, "#{memcached_host}:#{memcached_port}" config.middleware.insert_before Rack::ETag, Rack::Deflater diff --git a/config/environments/development.rb b/config/environments/development.rb index d60823a3..ca2e3b21 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -97,25 +97,25 @@ Bullet.add_footer = false Bullet.stacktrace_includes = [] - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Ordertime", association: :employee - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Ordertime", association: :absence - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Absencetime", association: :work_item - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Planning", association: :work_item - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Planning", association: :employee - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Order", association: :contacts - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Order", association: :work_item - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Order", association: :kind - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Order", association: :department - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Order", association: :status - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Order", association: :responsible - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Order", association: :targets - Bullet.add_whitelist type: :unused_eager_loading, class_name: "Order", association: :order_uncertainties - - Bullet.add_whitelist type: :n_plus_one_query, class_name: "Order", association: :order_team_members - Bullet.add_whitelist type: :n_plus_one_query, class_name: "Order", association: :team_members - Bullet.add_whitelist type: :n_plus_one_query, class_name: "Order", association: :order_contacts - Bullet.add_whitelist type: :n_plus_one_query, class_name: "WorkItem", association: :parent - Bullet.add_whitelist type: :n_plus_one_query, class_name: "BillingAddress", association: :client - Bullet.add_whitelist type: :n_plus_one_query, class_name: "BillingAddress", association: :contact + Bullet.add_safelist type: :unused_eager_loading, class_name: "Ordertime", association: :employee + Bullet.add_safelist type: :unused_eager_loading, class_name: "Ordertime", association: :absence + Bullet.add_safelist type: :unused_eager_loading, class_name: "Absencetime", association: :work_item + Bullet.add_safelist type: :unused_eager_loading, class_name: "Planning", association: :work_item + Bullet.add_safelist type: :unused_eager_loading, class_name: "Planning", association: :employee + Bullet.add_safelist type: :unused_eager_loading, class_name: "Order", association: :contacts + Bullet.add_safelist type: :unused_eager_loading, class_name: "Order", association: :work_item + Bullet.add_safelist type: :unused_eager_loading, class_name: "Order", association: :kind + Bullet.add_safelist type: :unused_eager_loading, class_name: "Order", association: :department + Bullet.add_safelist type: :unused_eager_loading, class_name: "Order", association: :status + Bullet.add_safelist type: :unused_eager_loading, class_name: "Order", association: :responsible + Bullet.add_safelist type: :unused_eager_loading, class_name: "Order", association: :targets + Bullet.add_safelist type: :unused_eager_loading, class_name: "Order", association: :order_uncertainties + + Bullet.add_safelist type: :n_plus_one_query, class_name: "Order", association: :order_team_members + Bullet.add_safelist type: :n_plus_one_query, class_name: "Order", association: :team_members + Bullet.add_safelist type: :n_plus_one_query, class_name: "Order", association: :order_contacts + Bullet.add_safelist type: :n_plus_one_query, class_name: "WorkItem", association: :parent + Bullet.add_safelist type: :n_plus_one_query, class_name: "BillingAddress", association: :client + Bullet.add_safelist type: :n_plus_one_query, class_name: "BillingAddress", association: :contact end end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 4fff2610..c4ea7068 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,27 +1,34 @@ # Be sure to restart your server when you modify this file. -def dalli_reachable? - Rails.cache.dalli.stats.values.any? +secure_cookies = !(Rails.env.development? || Rails.env.test?) +Rails.application.config.session_store( + ActionDispatch::Session::CacheStore, + expire_after: 12.hours, + same_site: :lax, + secure: secure_cookies +) + +def cache_reachable? + Rails.cache.stats.values.any? end def memcache_configured? if Rails.env.production? ENV['RAILS_MEMCACHED_HOST'].present? - elsif Rails.env.development? - true else - false + Rails.env.development? end end -secure_cookies = !(Rails.env.development? || Rails.env.test?) - -Rails.application.config.session_store ActionDispatch::Session::CacheStore, - expire_after: 12.hours, - same_site: :lax, - secure: secure_cookies +def skip_memcache_check + ENV['SKIP_MEMCACHE_CHECK'].present? +end -# We expect memcache to work in production. Prevents an error with the rails console on OpenShift -if memcache_configured? && !Rails.env.production? && !dalli_reachable? - fail "As CSRF tokens are read from cache, we require a memcache instance to start" +# We expect memcache to work in production. +# Prevents an error with the rails console on OpenShift +if !skip_memcache_check && + memcache_configured? && + !Rails.env.production? && + !cache_reachable? + raise 'As CSRF tokens are read from cache, we require a memcache instance to start' end From 2906895435910eb29ff5a1683fa23bce7d2626b3 Mon Sep 17 00:00:00 2001 From: Thomas Burkhalter Date: Mon, 6 Feb 2023 23:45:36 +0100 Subject: [PATCH 051/163] WIP: Fix javascript --- Gemfile | 3 +- app/assets/config/manifest.js | 20 + app/assets/javascripts/application.js.coffee | 23 +- .../modules/checkbox/toggler.js.coffee | 3 - app/controllers/api/jsonapi_controller.rb | 2 +- app/controllers/dry_crud/sortable.rb | 2 +- app/controllers/employees_controller.rb | 2 +- app/domain/evaluations/clients_eval.rb | 2 +- app/domain/evaluations/evaluation.rb | 14 +- app/domain/evaluations/sub_work_items_eval.rb | 2 +- app/domain/evaluations/work_items_eval.rb | 2 +- app/domain/order/controlling.rb | 4 +- app/domain/presenters/log_presenter.rb | 2 +- app/domain/reports/revenue/csv.rb | 4 +- .../reports/role_distribution_report.rb | 2 +- app/helpers/expenses_helper.rb | 2 +- app/helpers/i18n_helper.rb | 6 +- app/models/employee.rb | 2 +- app/models/order_chance.rb | 2 +- app/models/order_risk.rb | 2 +- app/models/order_uncertainty.rb | 2 +- app/models/working_condition.rb | 4 +- .../evaluator/_employee_absences.html.haml | 2 +- app/views/plannings/orders/update.js.haml | 2 +- bin/setup | 15 +- config/application.rb | 4 +- config/cable.yml | 2 +- config/environment.rb | 3 - config/environments/development.rb | 48 +- config/environments/production.rb | 86 +- config/environments/test.rb | 20 +- config/initializers/assets.rb | 7 +- .../initializers/content_security_policy.rb | 5 + .../new_framework_defaults_6_0.rb | 45 + config/locales/en.yml | 5 +- config/puma.rb | 12 +- config/routes.rb | 6 +- ..._attachments_for_blob_id.active_storage.rb | 10 + db/schema.rb | 12 +- node_modules/.yarn-integrity | 12 + ...fest-389d4ce5d8509d6d04d0b775519d90f1.json | 1 + ...1069ec1dff319afc0f8c0ba8ba84406e28d3ba.gif | Bin 0 -> 673 bytes ...289128fac47df4ce07ddc3bcc37059e5909dc34.js | 2452 ++ ...128fac47df4ce07ddc3bcc37059e5909dc34.js.gz | Bin 0 -> 15689 bytes ...36d0909a5ea9d39952bb0c8422c6a1fac09a204.js | 25673 ++++++++++++++++ ...0909a5ea9d39952bb0c8422c6a1fac09a204.js.gz | Bin 0 -> 268654 bytes ...e31830cd84b6fe6115a594ba8d06b2866c3d19.css | 16052 ++++++++++ ...830cd84b6fe6115a594ba8d06b2866c3d19.css.gz | Bin 0 -> 54822 bytes ...def6d553c805ce3784542f477ed63499287.coffee | 127 + ...6d553c805ce3784542f477ed63499287.coffee.gz | Bin 0 -> 1471 bytes ...7a8a20270c336948a8668df5cb89a8827299b.woff | Bin 0 -> 23424 bytes ...4990e4bfb6d2cfb00ca563ad4b553c240ddc33.eot | Bin 0 -> 20127 bytes ...0e4bfb6d2cfb00ca563ad4b553c240ddc33.eot.gz | Bin 0 -> 20056 bytes ...601061a32a46a9b9afd2dc7f457436f5f15f6e.svg | 288 + ...061a32a46a9b9afd2dc7f457436f5f15f6e.svg.gz | Bin 0 -> 26508 bytes ...2cea53642644b3a73f91c5a4ab46859af772.woff2 | Bin 0 -> 18028 bytes ...84e85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf | Bin 0 -> 45404 bytes ...85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf.gz | Bin 0 -> 23360 bytes ...15b3d2cc4a936b1182b1ba102ea9992d9c561c.gif | Bin 0 -> 127 bytes ...c0475cd5e7503242cd6adb1c34f5957538b202.png | Bin 0 -> 150 bytes ...ef5f8fc77693ae7c8c169788eda089c84d4441.svg | 12 + ...f8fc77693ae7c8c169788eda089c84d4441.svg.gz | Bin 0 -> 473 bytes ...b6c56ec5510e3b48a36d08e8814ba1693e2ddb.svg | 12 + ...56ec5510e3b48a36d08e8814ba1693e2ddb.svg.gz | Bin 0 -> 473 bytes ...461939b5962d6545f8760011508e5e498aec20.svg | 12 + ...939b5962d6545f8760011508e5e498aec20.svg.gz | Bin 0 -> 471 bytes ...f38ad9cffe3b61fe1c0a035a8d113073327c96.png | Bin 0 -> 490 bytes ...4f3c8c3f2ae8eb94c59d260ab4f107fc8f3f31.png | Bin 0 -> 1121 bytes ...9952c90b361a3f685804be08a5fa2ee6d6198b.png | Bin 0 -> 3756 bytes ...231f7e6edd10de024da40c190f9e0706c70276.png | Bin 0 -> 3756 bytes ...b638d08b96a568bfb173f0dce2c9eee4a441b3.png | Bin 0 -> 3756 bytes ...57884b418c8b3361019647559c835996ddaedb.png | Bin 0 -> 3756 bytes ...1c0b3e09d13a2f4ba4ebd638595bbad9d21c2d.png | Bin 0 -> 3756 bytes ...9b5af8d8dc60b91bfb414b4ca82779f7f9ffb6.png | Bin 0 -> 3756 bytes ...c1a8939ccc8db5f92ec6a00351b5d89e92c426.svg | 15 + ...8939ccc8db5f92ec6a00351b5d89e92c426.svg.gz | Bin 0 -> 497 bytes ...dd2b323ec7bb823c37fa5249cd5b6004bdfce6.gif | Bin 0 -> 94 bytes ...92ec813ad3abc1bb688bf3daa9eb134b0186f80.js | 23244 ++++++++++++++ ...c813ad3abc1bb688bf3daa9eb134b0186f80.js.gz | Bin 0 -> 253067 bytes ...b3c1361c0583026cdf35d6a2921bccaea835331.js | 3 + ...1361c0583026cdf35d6a2921bccaea835331.js.gz | Bin 0 -> 23 bytes ...0a2609abbae2654ab85b1f65fdfb55a76fe173.css | 111 + ...609abbae2654ab85b1f65fdfb55a76fe173.css.gz | Bin 0 -> 583 bytes ...3abc3dbea21463a0a0a1ee40bdf9a7fe99e34f.css | 9040 ++++++ ...c3dbea21463a0a0a1ee40bdf9a7fe99e34f.css.gz | Bin 0 -> 28064 bytes ...bc0b7dd5f348a427b3c12120fe15f61b7903cc.eot | Bin 0 -> 6364 bytes ...b7dd5f348a427b3c12120fe15f61b7903cc.eot.gz | Bin 0 -> 3969 bytes ...1b19283766200e3997f7a474d8d9692c1dddd.woff | Bin 0 -> 4504 bytes ...ba7c0de75acda38363a42c5831513a9a2b3d86.svg | 38 + ...c0de75acda38363a42c5831513a9a2b3d86.svg.gz | Bin 0 -> 4885 bytes ...ff59463333c45a93e17f5dda18854cc44076f2.ttf | Bin 0 -> 6188 bytes ...9463333c45a93e17f5dda18854cc44076f2.ttf.gz | Bin 0 -> 3923 bytes ...ad2df1ffbabb6c44489acc8b54f984d88d1854.gif | Bin 0 -> 43 bytes ...290de0a228ff1f074e64fda276e18c53b6885a.png | Bin 0 -> 180 bytes ...64ac59dc3e8258d1dc7d0d7fda47239669cad9.png | Bin 0 -> 107 bytes ...6c72dc7277107d3df71dc3baf4f77312473ae9.png | Bin 0 -> 4369 bytes ...916428fcad59d785b5f505af8cd3c8fbb43cb2.png | Bin 0 -> 4369 bytes ...133795033ae7d4e2b929b2daac08112ac1c3bd.png | Bin 0 -> 4369 bytes ...6a1abfecc8201d7adf92c594e386f8f10cf987.png | Bin 0 -> 4369 bytes ...d848bf023da59a7985bd51a451d47ee2cc434e.png | Bin 0 -> 8884 bytes ...3bcbe8d6d6bd4fe6011087a0c1e035bda32148.gif | Bin 0 -> 110 bytes .../absencetimes_controller_test.rb | 2 +- .../api/v1/employees_controller_test.rb | 8 +- .../employees/log_controller_test.rb | 10 +- .../controllers/ordertimes_controller_test.rb | 4 +- test/integration/invoice_form_test.rb | 2 + test/models/work_item_test.rb | 4 +- test/support/small_invoice_test_helper.rb | 4 +- test/tarantula/tarantula_test.rb_ | 2 +- test/test_helper.rb | 42 +- .../stylesheets/jquery-ui-1.10.0.custom.scss | 1696 +- 111 files changed, 78675 insertions(+), 582 deletions(-) create mode 100644 config/initializers/new_framework_defaults_6_0.rb create mode 100644 db/migrate/20230206101600_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb create mode 100644 node_modules/.yarn-integrity create mode 100644 public/assets/.sprockets-manifest-389d4ce5d8509d6d04d0b775519d90f1.json create mode 100644 public/assets/ajax-loader-0e899a4e4cea34f16947494fb21069ec1dff319afc0f8c0ba8ba84406e28d3ba.gif create mode 100644 public/assets/application-795779a25521261357be45bad289128fac47df4ce07ddc3bcc37059e5909dc34.js create mode 100644 public/assets/application-795779a25521261357be45bad289128fac47df4ce07ddc3bcc37059e5909dc34.js.gz create mode 100644 public/assets/application-b13e430fa55310b01e7be687036d0909a5ea9d39952bb0c8422c6a1fac09a204.js create mode 100644 public/assets/application-b13e430fa55310b01e7be687036d0909a5ea9d39952bb0c8422c6a1fac09a204.js.gz create mode 100644 public/assets/application-bd0c129a2e26937339c4fd5440e31830cd84b6fe6115a594ba8d06b2866c3d19.css create mode 100644 public/assets/application-bd0c129a2e26937339c4fd5440e31830cd84b6fe6115a594ba8d06b2866c3d19.css.gz create mode 100644 public/assets/application-d8f81a2184b7e0302aba9ec8b71a8def6d553c805ce3784542f477ed63499287.coffee create mode 100644 public/assets/application-d8f81a2184b7e0302aba9ec8b71a8def6d553c805ce3784542f477ed63499287.coffee.gz create mode 100644 public/assets/bootstrap/glyphicons-halflings-regular-0703369a358a012c0011843ae337a8a20270c336948a8668df5cb89a8827299b.woff create mode 100644 public/assets/bootstrap/glyphicons-halflings-regular-0805fb1fe24235f70a639f67514990e4bfb6d2cfb00ca563ad4b553c240ddc33.eot create mode 100644 public/assets/bootstrap/glyphicons-halflings-regular-0805fb1fe24235f70a639f67514990e4bfb6d2cfb00ca563ad4b553c240ddc33.eot.gz create mode 100644 public/assets/bootstrap/glyphicons-halflings-regular-22d0c88a49d7d0ebe45627143a601061a32a46a9b9afd2dc7f457436f5f15f6e.svg create mode 100644 public/assets/bootstrap/glyphicons-halflings-regular-22d0c88a49d7d0ebe45627143a601061a32a46a9b9afd2dc7f457436f5f15f6e.svg.gz create mode 100644 public/assets/bootstrap/glyphicons-halflings-regular-403acfcf0cbaebd1c28b404eec442cea53642644b3a73f91c5a4ab46859af772.woff2 create mode 100644 public/assets/bootstrap/glyphicons-halflings-regular-7c9caa5f4e16169b0129fdf93c84e85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf create mode 100644 public/assets/bootstrap/glyphicons-halflings-regular-7c9caa5f4e16169b0129fdf93c84e85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf.gz create mode 100644 public/assets/calendar-48d011f3b73eb282dc94666ed015b3d2cc4a936b1182b1ba102ea9992d9c561c.gif create mode 100644 public/assets/calendar-61bcc4c23aa409db2f353d136ec0475cd5e7503242cd6adb1c34f5957538b202.png create mode 100644 public/assets/day-marker-active-d99b32b60616df674b040de1ceef5f8fc77693ae7c8c169788eda089c84d4441.svg create mode 100644 public/assets/day-marker-active-d99b32b60616df674b040de1ceef5f8fc77693ae7c8c169788eda089c84d4441.svg.gz create mode 100644 public/assets/day-marker-holiday-8a281e6680508108fe59cf1c01b6c56ec5510e3b48a36d08e8814ba1693e2ddb.svg create mode 100644 public/assets/day-marker-holiday-8a281e6680508108fe59cf1c01b6c56ec5510e3b48a36d08e8814ba1693e2ddb.svg.gz create mode 100644 public/assets/day-marker-missing-44a616885a5627c4c6af6ba793461939b5962d6545f8760011508e5e498aec20.svg create mode 100644 public/assets/day-marker-missing-44a616885a5627c4c6af6ba793461939b5962d6545f8760011508e5e498aec20.svg.gz create mode 100644 public/assets/favicon-e7bb1f6b17d7f9dcab65221e7df38ad9cffe3b61fe1c0a035a8d113073327c96.png create mode 100644 public/assets/highrise-cdc00f1faa88687f254449a5b54f3c8c3f2ae8eb94c59d260ab4f107fc8f3f31.png create mode 100644 public/assets/jquery-ui/ui-icons_444444_256x240-f6d8c091e81dfb911fc0815e7c9952c90b361a3f685804be08a5fa2ee6d6198b.png create mode 100644 public/assets/jquery-ui/ui-icons_555555_256x240-c23f23416a84a359723ffe28c6231f7e6edd10de024da40c190f9e0706c70276.png create mode 100644 public/assets/jquery-ui/ui-icons_777620_256x240-0a3bb6e0c499ea585572cbda26b638d08b96a568bfb173f0dce2c9eee4a441b3.png create mode 100644 public/assets/jquery-ui/ui-icons_777777_256x240-ea4f5d5317b31eaaaf01e3db3357884b418c8b3361019647559c835996ddaedb.png create mode 100644 public/assets/jquery-ui/ui-icons_cc0000_256x240-c7cf3a680d1476856186d49a361c0b3e09d13a2f4ba4ebd638595bbad9d21c2d.png create mode 100644 public/assets/jquery-ui/ui-icons_ffffff_256x240-24221a00d39ccfd2da654907e99b5af8d8dc60b91bfb414b4ca82779f7f9ffb6.png create mode 100644 public/assets/logo-c9792e60e4d11c6511c7d6549ec1a8939ccc8db5f92ec6a00351b5d89e92c426.svg create mode 100644 public/assets/logo-c9792e60e4d11c6511c7d6549ec1a8939ccc8db5f92ec6a00351b5d89e92c426.svg.gz create mode 100644 public/assets/lupe-c774b159265725d0245e0cb4f5dd2b323ec7bb823c37fa5249cd5b6004bdfce6.gif create mode 100644 public/assets/manifest-6ad05901a908774949f62ed6b92ec813ad3abc1bb688bf3daa9eb134b0186f80.js create mode 100644 public/assets/manifest-6ad05901a908774949f62ed6b92ec813ad3abc1bb688bf3daa9eb134b0186f80.js.gz create mode 100644 public/assets/manifest-dad05bf766af0fe3d79dd746db3c1361c0583026cdf35d6a2921bccaea835331.js create mode 100644 public/assets/manifest-dad05bf766af0fe3d79dd746db3c1361c0583026cdf35d6a2921bccaea835331.js.gz create mode 100644 public/assets/phone-559c5ed1cdc3ac647348a0dd390a2609abbae2654ab85b1f65fdfb55a76fe173.css create mode 100644 public/assets/phone-559c5ed1cdc3ac647348a0dd390a2609abbae2654ab85b1f65fdfb55a76fe173.css.gz create mode 100644 public/assets/print-23f9a8c22460910734dee3bd913abc3dbea21463a0a0a1ee40bdf9a7fe99e34f.css create mode 100644 public/assets/print-23f9a8c22460910734dee3bd913abc3dbea21463a0a0a1ee40bdf9a7fe99e34f.css.gz create mode 100644 public/assets/puzzletime-2de41030a7af76acbd1f6ccb10bc0b7dd5f348a427b3c12120fe15f61b7903cc.eot create mode 100644 public/assets/puzzletime-2de41030a7af76acbd1f6ccb10bc0b7dd5f348a427b3c12120fe15f61b7903cc.eot.gz create mode 100644 public/assets/puzzletime-61a62a54f3b770abbbdc71254ea1b19283766200e3997f7a474d8d9692c1dddd.woff create mode 100644 public/assets/puzzletime-d547a1d48c348caf9d81ddc08eba7c0de75acda38363a42c5831513a9a2b3d86.svg create mode 100644 public/assets/puzzletime-d547a1d48c348caf9d81ddc08eba7c0de75acda38363a42c5831513a9a2b3d86.svg.gz create mode 100644 public/assets/puzzletime-e2ba8ea4540e897e42ea9182e7ff59463333c45a93e17f5dda18854cc44076f2.ttf create mode 100644 public/assets/puzzletime-e2ba8ea4540e897e42ea9182e7ff59463333c45a93e17f5dda18854cc44076f2.ttf.gz create mode 100644 public/assets/space-9a89381c23d3b40e675948d825ad2df1ffbabb6c44489acc8b54f984d88d1854.gif create mode 100644 public/assets/ui-bg_flat_0_aaaaaa_40x100-aa167a04c6c4e82e1fbe80ec8a290de0a228ff1f074e64fda276e18c53b6885a.png create mode 100644 public/assets/ui-bg_glass_75_ffffff_1x400-e6dec50d602c5c8829427ecd8164ac59dc3e8258d1dc7d0d7fda47239669cad9.png create mode 100644 public/assets/ui-icons_222222_256x240-3305696b28a8757b9b73e678cc6c72dc7277107d3df71dc3baf4f77312473ae9.png create mode 100644 public/assets/ui-icons_2e83ff_256x240-71ec8b5da0ab851bbef886db2d916428fcad59d785b5f505af8cd3c8fbb43cb2.png create mode 100644 public/assets/ui-icons_454545_256x240-989d471b55541d188455465047133795033ae7d4e2b929b2daac08112ac1c3bd.png create mode 100644 public/assets/ui-icons_888888_256x240-42393eae298d8afe2c78cd1caf6a1abfecc8201d7adf92c594e386f8f10cf987.png create mode 100644 public/assets/ui-icons_f6cf3b_256x240-fa649f3fa8d7d4f9343a1d0714d848bf023da59a7985bd51a451d47ee2cc434e.png create mode 100644 public/assets/whitebox-aa4dc20192f264f3b20f45ef8c3bcbe8d6d6bd4fe6011087a0c1e035bda32148.gif diff --git a/Gemfile b/Gemfile index 376f1957..cb42625a 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' git_source(:github) { |name| "https://github.com/#{name}.git" } - gem 'rails', '~> 6.0.6.1' +gem 'rails', '~> 6.0.6.1' gem 'pg' #, '~> 0.21.0' gem 'activerecord-nulldb-adapter' @@ -79,6 +79,7 @@ group :development, :test do gem 'pry-rails' gem 'rb-readline' gem 'request_profiler' + gem 'pry-byebug' end group :development do diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index b625b75e..02374e86 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,3 +1,23 @@ //= link_tree ../images +//= require jquery3 +//= require jquery_ujs +//= require jquery-ui/widgets/datepicker +//= require jquery-ui-datepicker-i18n +//= require jquery-ui/widgets/autocomplete +//= require jquery-ui/widgets/selectable +//= require selectize +//= require bootstrap/modal +//= require bootstrap/tooltip +//= require bootstrap/button +//= require bootstrap/alert +//= require bootstrap/collapse +//= require waypoints/jquery.waypoints +//= require waypoints/shortcuts/sticky +//= require waypoints/shortcuts/inview +//= require modernizr-custom +//= require Chart.min +//= require chartjs-plugin-annotation.min +//= link application.js.coffee +//= require turbolinks //= link application.js //= link application.css diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 25afb2d5..08db30aa 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -7,27 +7,9 @@ # Place your application-specific JavaScript functions and classes here # This file is automatically included by javascript_include_tag :defaults # -#= require jquery3 -#= require jquery_ujs -#= require jquery-ui/widgets/datepicker -#= require jquery-ui-datepicker-i18n -#= require jquery-ui/widgets/autocomplete -#= require jquery-ui/widgets/selectable -#= require selectize -#= require bootstrap/modal -#= require bootstrap/tooltip -#= require bootstrap/button -#= require bootstrap/alert -#= require bootstrap/collapse -#= require waypoints/jquery.waypoints -#= require waypoints/shortcuts/sticky -#= require waypoints/shortcuts/inview -#= require modernizr-custom -#= require Chart.min -#= require chartjs-plugin-annotation.min #= require_self #= require_tree ./modules -# after self to disable links +#after self to disable links #= require nested_form_fields #= require modal_create #= require datepicker @@ -45,9 +27,8 @@ #= require expenses #= require expense_reviews #= require meal_compensations -#= require turbolinks - +debugger app = window.App ||= {} if typeof String.prototype.endsWith isnt 'function' diff --git a/app/assets/javascripts/modules/checkbox/toggler.js.coffee b/app/assets/javascripts/modules/checkbox/toggler.js.coffee index 80722be0..d0608372 100644 --- a/app/assets/javascripts/modules/checkbox/toggler.js.coffee +++ b/app/assets/javascripts/modules/checkbox/toggler.js.coffee @@ -24,6 +24,3 @@ class app.checkbox.Toggler selector = '[data-' + @data + ']' $(document).on('click', selector, (event) -> self.toggleChecked(this)) $(document).on('turbolinks:load', -> $(selector).each((i, e) -> self.toggleChecked(e))) - - - diff --git a/app/controllers/api/jsonapi_controller.rb b/app/controllers/api/jsonapi_controller.rb index 170b314f..a0909553 100644 --- a/app/controllers/api/jsonapi_controller.rb +++ b/app/controllers/api/jsonapi_controller.rb @@ -82,7 +82,7 @@ def render_error(title, detail: nil, code: :error, status: 422, **opts) def set_pagination_headers response.headers.merge!( 'Pagination-Total-Count' => list_entries.total_count, - 'Pagination-Per-Page' => list_entries.current_per_page, + 'Pagination-Per-Page' => list_entries.limit_value, 'Pagination-Current-Page' => list_entries.current_page, 'Pagination-Total-Pages' => list_entries.total_pages ) diff --git a/app/controllers/dry_crud/sortable.rb b/app/controllers/dry_crud/sortable.rb index 71908744..6a3438bd 100644 --- a/app/controllers/dry_crud/sortable.rb +++ b/app/controllers/dry_crud/sortable.rb @@ -42,7 +42,7 @@ def list_entries sortable = sortable?(params[:sort]) if sortable || default_sort clause = [sortable ? sort_expression : nil, default_sort] - super.reorder(clause.compact.join(', ')) + super.reorder(Arel.sql(clause.compact.join(', '))) else super end diff --git a/app/controllers/employees_controller.rb b/app/controllers/employees_controller.rb index 07e946f7..ffca577e 100644 --- a/app/controllers/employees_controller.rb +++ b/app/controllers/employees_controller.rb @@ -40,7 +40,7 @@ def update_settings @employee = @user attrs = params.require(:employee).permit(:worktimes_commit_reminder, eval_periods: []) attrs[:eval_periods] = [] if attrs[:eval_periods].blank? - if @employee.update_attributes(attrs) + if @employee.update(attrs) flash[:notice] = 'Die Benutzereinstellungen wurden aktualisiert' redirect_to root_path else diff --git a/app/domain/evaluations/clients_eval.rb b/app/domain/evaluations/clients_eval.rb index 85789020..61dc0438 100644 --- a/app/domain/evaluations/clients_eval.rb +++ b/app/domain/evaluations/clients_eval.rb @@ -4,7 +4,7 @@ # https://github.com/puzzle/puzzletime. class ClientsEval < Evaluation - self.division_column = 'work_items.path_ids[1]' + self.division_column = Arel.sql('work_items.path_ids[1]') self.division_join = :work_item self.sub_evaluation = 'clientworkitems' self.label = 'Kunden' diff --git a/app/domain/evaluations/evaluation.rb b/app/domain/evaluations/evaluation.rb index 9042e0cb..12943c53 100644 --- a/app/domain/evaluations/evaluation.rb +++ b/app/domain/evaluations/evaluation.rb @@ -130,7 +130,7 @@ def sum_total_plannings(period = nil) def planning_query(receiver, division = nil) query = receiver.plannings. joins(:work_item). - joins('INNER JOIN accounting_posts ON accounting_posts.work_item_id = ANY (work_items.path_ids)'). + joins(Arel.sql('INNER JOIN accounting_posts ON accounting_posts.work_item_id = ANY (work_items.path_ids)')). definitive query = query.where("? = #{category_ref}", category_id) if division && category_ref query @@ -258,11 +258,11 @@ def query_grouped_time_sums(query, group_by_column) end def hours_and_billable_hours_columns - ['SUM(worktimes.hours) AS sum_hours', - 'SUM(CASE WHEN worktimes.billable = TRUE ' \ + [Arel.sql('SUM(worktimes.hours) AS sum_hours'), + Arel.sql('SUM(CASE WHEN worktimes.billable = TRUE ' \ 'THEN worktimes.hours ' \ 'ELSE 0 END) ' \ - 'AS sum_billable_hours'] + 'AS sum_billable_hours')] end def query_planning_sums(query, period) @@ -289,11 +289,11 @@ def query_grouped_planning_sums(query, period, group_by_column) end def plannings_and_billable_plannings_columns(must_hours) - ["SUM(plannings.percent / 100.0 * #{must_hours.to_f}) AS sum_hours", - 'SUM(CASE WHEN accounting_posts.billable = TRUE ' \ + [Arel.sql("SUM(plannings.percent / 100.0 * #{must_hours.to_f}) AS sum_hours"), + Arel.sql('SUM(CASE WHEN accounting_posts.billable = TRUE ' \ "THEN plannings.percent / 100.0 * #{must_hours.to_f} " \ 'ELSE 0 END) ' \ - 'AS sum_billable_hours'] + 'AS sum_billable_hours')] end def worktime_type diff --git a/app/domain/evaluations/sub_work_items_eval.rb b/app/domain/evaluations/sub_work_items_eval.rb index 4d5aa452..1c0915ea 100644 --- a/app/domain/evaluations/sub_work_items_eval.rb +++ b/app/domain/evaluations/sub_work_items_eval.rb @@ -28,6 +28,6 @@ def division_label end def division_column - "work_items.path_ids[#{category.path_ids.size + 1}]" + Arel.sql("work_items.path_ids[#{category.path_ids.size + 1}]") end end diff --git a/app/domain/evaluations/work_items_eval.rb b/app/domain/evaluations/work_items_eval.rb index ae2e889f..2dc906d4 100644 --- a/app/domain/evaluations/work_items_eval.rb +++ b/app/domain/evaluations/work_items_eval.rb @@ -6,7 +6,7 @@ # abstract class for evaluation with work item divisions class WorkItemsEval < Evaluation self.division_method = :work_items - self.division_column = 'work_items.path_ids[1]' + self.division_column = Arel.sql('work_items.path_ids[1]') self.division_join = :work_item self.label = 'Positionen' self.sub_evaluation = 'workitememployees' diff --git a/app/domain/order/controlling.rb b/app/domain/order/controlling.rb index 4c761e2c..79e791b8 100644 --- a/app/domain/order/controlling.rb +++ b/app/domain/order/controlling.rb @@ -37,7 +37,7 @@ def grouped_worktimes load_worktimes .group('week, worktimes.billable') .order('week') - .pluck('DATE_TRUNC(\'week\', work_date) week, worktimes.billable, SUM(hours * offered_rate)') + .pluck(Arel.sql('DATE_TRUNC(\'week\', work_date) week, worktimes.billable, SUM(hours * offered_rate)')) end def grouped_plannings @@ -45,7 +45,7 @@ def grouped_plannings .in_period(Period.with(date, nil)) .group('week, offered_rate, definitive') .order('week') - .pluck('DATE_TRUNC(\'week\', date) week, offered_rate, definitive, SUM(percent)') + .pluck(Arel.sql('DATE_TRUNC(\'week\', date) week, offered_rate, definitive, SUM(percent)')) end def load_worktimes diff --git a/app/domain/presenters/log_presenter.rb b/app/domain/presenters/log_presenter.rb index 16d1d258..15b46c43 100644 --- a/app/domain/presenters/log_presenter.rb +++ b/app/domain/presenters/log_presenter.rb @@ -68,7 +68,7 @@ def attribute_change(item_type, attr, changes) to = I18n.l(to, format: :month) if to end - I18n.t("version.attribute_change.#{key}", attribute_args(item_type, attr, from, to)) + I18n.t("version.attribute_change.#{key}", **attribute_args(item_type, attr, from, to)) end private diff --git a/app/domain/reports/revenue/csv.rb b/app/domain/reports/revenue/csv.rb index 1cd72bca..cd248be9 100644 --- a/app/domain/reports/revenue/csv.rb +++ b/app/domain/reports/revenue/csv.rb @@ -132,8 +132,8 @@ def footer_future_months out end - def l(*args) - I18n.l(*args) + def l(...) + I18n.l(...) end def format_number(number, precision = nil) diff --git a/app/domain/reports/role_distribution_report.rb b/app/domain/reports/role_distribution_report.rb index a7985e0a..98fc2877 100644 --- a/app/domain/reports/role_distribution_report.rb +++ b/app/domain/reports/role_distribution_report.rb @@ -140,7 +140,7 @@ def category_percents .joins('INNER JOIN employment_roles er ON er.id = ere.employment_role_id') .joins('INNER JOIN employment_role_categories erc ON erc.id = er.employment_role_category_id') .group('employees.id', 'erc.id') - .pluck('employees.id, erc.id, SUM(COALESCE(ere.percent, 0)) AS percent') + .pluck(Arel.sql('employees.id, erc.id, SUM(COALESCE(ere.percent, 0)) AS percent')) .each_with_object({}) do |(employee_id, category_id, percent), o| o[employee_id] ||= {} o[employee_id][category_id] = percent diff --git a/app/helpers/expenses_helper.rb b/app/helpers/expenses_helper.rb index 4c9bb77e..bab3c978 100644 --- a/app/helpers/expenses_helper.rb +++ b/app/helpers/expenses_helper.rb @@ -121,7 +121,7 @@ def expenses_order_field(form, **options) def expenses_file_field(form, **options) safe_join( [ - file_field_with_warning(form, options), + file_field_with_warning(form, **options), form.labeled(' ', options) { t('expenses.attachment.hint') } ] ) diff --git a/app/helpers/i18n_helper.rb b/app/helpers/i18n_helper.rb index 47d88413..3301b1f0 100644 --- a/app/helpers/i18n_helper.rb +++ b/app/helpers/i18n_helper.rb @@ -20,7 +20,7 @@ def translate_inheritable(key, variables = {}) partial = defined?(@virtual_path) ? @virtual_path.gsub(%r(.*\/_?), '') : nil defaults = inheritable_translation_defaults(key, partial) variables[:default] ||= defaults - t(defaults.shift, variables) + t(defaults.shift, **variables) end alias ti translate_inheritable @@ -36,9 +36,9 @@ def translate_association(key, assoc = nil, variables = {}) if assoc && assoc.options[:polymorphic].nil? variables[:default] ||= [association_klass_key(assoc, key).to_sym, :"global.associations.#{key}"] - t(association_owner_key(assoc, key), variables) + t(association_owner_key(assoc, key), **variables) else - t("global.associations.#{key}", variables) + t("global.associations.#{key}", **variables) end end diff --git a/app/models/employee.rb b/app/models/employee.rb index e687fdd8..6423f62b 100644 --- a/app/models/employee.rb +++ b/app/models/employee.rb @@ -193,7 +193,7 @@ def check_passwd(pwd) end def update_passwd!(pwd) - update_attributes!(passwd: Employee.encode(pwd)) + update!(passwd: Employee.encode(pwd)) end # main work items this employee ever worked on diff --git a/app/models/order_chance.rb b/app/models/order_chance.rb index a64dc02c..ac66829c 100644 --- a/app/models/order_chance.rb +++ b/app/models/order_chance.rb @@ -28,7 +28,7 @@ def update_major_order_value def major_order_value order.order_chances .where(type: OrderChance.sti_name) - .pluck('MAX(probability * impact)') + .pluck(Arel.sql('MAX(probability * impact)')) .first end end diff --git a/app/models/order_risk.rb b/app/models/order_risk.rb index c59698a2..9bd73694 100644 --- a/app/models/order_risk.rb +++ b/app/models/order_risk.rb @@ -27,7 +27,7 @@ def update_major_order_value def major_order_value order.order_risks - .pluck('MAX(probability * impact)') + .pluck(Arel.sql('MAX(probability * impact)')) .first end end diff --git a/app/models/order_uncertainty.rb b/app/models/order_uncertainty.rb index fd4af9ea..f1a4936f 100644 --- a/app/models/order_uncertainty.rb +++ b/app/models/order_uncertainty.rb @@ -43,7 +43,7 @@ class OrderUncertainty < ActiveRecord::Base after_save :update_major_order_value after_destroy :update_major_order_value - scope :list, -> { order('probability * impact DESC') } + scope :list, -> { order(Arel.sql('probability * impact DESC')) } class << self def risk(value) diff --git a/app/models/working_condition.rb b/app/models/working_condition.rb index 54355673..dfcbd953 100644 --- a/app/models/working_condition.rb +++ b/app/models/working_condition.rb @@ -28,7 +28,7 @@ class WorkingCondition < ActiveRecord::Base delegate :clear_cache, to: :class - scope :list, -> { order('(CASE WHEN valid_from IS NULL THEN 0 ELSE 1 END) DESC, valid_from DESC') } + scope :list, -> { order(Arel.sql('(CASE WHEN valid_from IS NULL THEN 0 ELSE 1 END) DESC, valid_from DESC')) } class << self def todays_value(attr) @@ -78,7 +78,7 @@ def cached # double cache for best performance RequestStore.store[model_name.route_key] ||= Rails.cache.fetch(model_name.route_key) do - order('(CASE WHEN valid_from IS NULL THEN 0 ELSE 1 END), valid_from').collect(&:attributes) + order(Arel.sql('(CASE WHEN valid_from IS NULL THEN 0 ELSE 1 END), valid_from')).collect(&:attributes) end end diff --git a/app/views/evaluator/_employee_absences.html.haml b/app/views/evaluator/_employee_absences.html.haml index f81832c7..b5221e9c 100644 --- a/app/views/evaluator/_employee_absences.html.haml +++ b/app/views/evaluator/_employee_absences.html.haml @@ -7,7 +7,7 @@ %section.employee-absences %h4 Absenzen - - evaluation = EmployeeAbsencesEval.new(params[:category_id] || @user.id, search_conditions) + - evaluation = EmployeeAbsencesEval.new(params[:category_id] || @user.id, **search_conditions) = render('category', evaluation: evaluation, times: @periods.collect { |p| evaluation.sum_times_grouped(p) }, diff --git a/app/views/plannings/orders/update.js.haml b/app/views/plannings/orders/update.js.haml index 11262ccf..f22ddbee 100644 --- a/app/views/plannings/orders/update.js.haml +++ b/app/views/plannings/orders/update.js.haml @@ -4,7 +4,7 @@ -# https://github.com/puzzle/puzzletime. -= render file: 'plannings/base/update' += render 'plannings/base/update' - @board.included_accounting_posts.each do |post| $('#group_header_times_#{dom_id(post)}').html('#{j(render('accounting_post_total', post: post))}'); \ No newline at end of file diff --git a/bin/setup b/bin/setup index b2293a35..5853b5ea 100755 --- a/bin/setup +++ b/bin/setup @@ -1,33 +1,32 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' -include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('..', __dir__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end -chdir APP_ROOT do - # This script is a starting point to setup your application. +FileUtils.chdir APP_ROOT do + # This script is a way to setup or update your development environment automatically. + # This script is idempotent, so that you can run it at anytime and get an expectable outcome. # Add necessary setup steps to this file. puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') - # Install JavaScript dependencies if using Yarn + # Install JavaScript dependencies # system('bin/yarn') # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') - # cp 'config/database.yml.sample', 'config/database.yml' + # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' # end puts "\n== Preparing database ==" - system! 'bin/rails db:setup' + system! 'bin/rails db:prepare' puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' diff --git a/config/application.rb b/config/application.rb index 70d9a0a4..cd6b7ebe 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,9 +1,9 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# Copyright (c) 2006-2023, Puzzle ITC GmbH. This file is part of # PuzzleTime and licensed under the Affero General Public License version 3 # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. -require File.expand_path('../boot', __FILE__) +require_relative 'boot' require 'rails/all' diff --git a/config/cable.yml b/config/cable.yml index 4400e072..60726b3c 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -2,7 +2,7 @@ development: adapter: async test: - adapter: async + adapter: test production: adapter: redis diff --git a/config/environment.rb b/config/environment.rb index e76ccaf8..426333bb 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,8 +1,5 @@ # Load the Rails application. require_relative 'application' -# FIXME: ignoring deprecation warnings for now. -::ActiveSupport::Deprecation.silenced = true if Rails.env.test? - # Initialize the Rails application. Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index ca2e3b21..68ae3acb 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# Copyright (c) 2006-2023, Puzzle ITC GmbH. This file is part of # PuzzleTime and licensed under the Affero General Public License version 3 # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. @@ -14,13 +14,14 @@ # Do not eager load code on boot. config.eager_load = false - # Show full error reports and disable caching. + # Show full error reports. config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. # if Rails.root.join('tmp', 'caching-dev.txt').exist? # config.action_controller.perform_caching = true + # config.action_controller.enable_fragment_cache_logging = true # # config.cache_store = :memory_store # config.public_file_server.headers = { @@ -32,7 +33,7 @@ # config.cache_store = :null_store # end - # Store uploaded files on the local file system (see config/storage.yml for options) + # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = ENV.fetch('RAILS_STORAGE_SERVICE', 'local').to_sym # Perform caching as the session is stored there @@ -43,22 +44,6 @@ config.action_mailer.perform_caching = false - # Mail sender - config.action_mailer.delivery_method = (ENV['RAILS_MAIL_DELIVERY_METHOD'].presence || :smtp).to_sym - - if ENV['RAILS_MAIL_DELIVERY_CONFIG'].present? - case config.action_mailer.delivery_method.to_s - when 'smtp' - config.action_mailer.smtp_settings = - YAML.load("{ #{ENV['RAILS_MAIL_DELIVERY_CONFIG']} }").symbolize_keys - when 'sendmail' - config.action_mailer.sendmail_settings = - YAML.load("{ #{ENV['RAILS_MAIL_DELIVERY_CONFIG']} }").symbolize_keys - end - else - config.action_mailer.smtp_settings = { :address => '127.0.0.1', :port => 1025 } - end - # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log @@ -73,17 +58,34 @@ # number of complex assets. config.assets.debug = true - config.middleware.insert_before ActionDispatch::Cookies, Rack::RequestProfiler, printer: RubyProf::CallStackPrinter - # Suppress logger output for asset requests. config.assets.quiet = true - # Raises error for missing translations + # Raises error for missing translations. # config.action_view.raise_on_missing_translations = true # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. - # config.file_watcher = ActiveSupport::EventedFileUpdateChecker + config.file_watcher = ActiveSupport::EventedFileUpdateChecker + + config.middleware.insert_before ActionDispatch::Cookies, Rack::RequestProfiler, printer: RubyProf::CallStackPrinter + + # Mail sender + config.action_mailer.delivery_method = (ENV['RAILS_MAIL_DELIVERY_METHOD'].presence || :smtp).to_sym + + if ENV['RAILS_MAIL_DELIVERY_CONFIG'].present? + case config.action_mailer.delivery_method.to_s + when 'smtp' + config.action_mailer.smtp_settings = + YAML.load("{ #{ENV['RAILS_MAIL_DELIVERY_CONFIG']} }").symbolize_keys + when 'sendmail' + config.action_mailer.sendmail_settings = + YAML.load("{ #{ENV['RAILS_MAIL_DELIVERY_CONFIG']} }").symbolize_keys + end + else + config.action_mailer.smtp_settings = { :address => '127.0.0.1', :port => 1025 } + end + # Raises error for missing translations # config.action_view.raise_on_missing_translations = true diff --git a/config/environments/production.rb b/config/environments/production.rb index 7b1ef82c..2c6e6042 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# Copyright (c) 2006-2023, Puzzle ITC GmbH. This file is part of # PuzzleTime and licensed under the Affero General Public License version 3 # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. @@ -28,7 +28,7 @@ # Apache or NGINX already handles this. config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? - # Compress JavaScripts and CSS. + # Compress CSS using a preprocessor. config.assets.js_compressor = :uglifier # config.assets.css_compressor = :sass @@ -45,66 +45,45 @@ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX config.action_dispatch.x_sendfile_header = ENV['RAILS_X_SENDFILE_HEADER'] || 'X-Sendfile' - # Store uploaded files on the local file system (see config/storage.yml for options) + # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = ENV.fetch('RAILS_STORAGE_SERVICE', 'ocp4_s3').to_sym - # Mount Action Cable outside main process or domain + # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil # config.action_cable.url = 'wss://example.com/cable' # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true ssl = %w[true yes 1].include?(ENV['RAILS_HOST_SSL']) config.force_ssl = ssl config.ssl_options = { redirect: { exclude: ->(request) { request.path =~ /health|readiness/ } } } - # Set to :debug to see everything in the log. - config.log_level = :info + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug # Prepend all log lines with the following tags. - config.log_tags = [:request_id] - - # Use a different logger for distributed setups. - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + config.log_tags = [ :request_id ] # Use a different cache store in production. # config.cache_store = :mem_cache_store - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = "http://assets.example.com" - - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) - - # Use a real queuing backend for Active Job (and separate queues per environment) + # Use a real queuing backend for Active Job (and separate queues per environment). # config.active_job.queue_adapter = :resque - # config.active_job.queue_name_prefix = "puzzletime_#{Rails.env}" + # config.active_job.queue_name_prefix = "puzzletime_production" + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. config.action_mailer.raise_delivery_errors = false - # Mail sender - config.action_mailer.delivery_method = (ENV['RAILS_MAIL_DELIVERY_METHOD'].presence || :smtp).to_sym - - if ENV['RAILS_MAIL_DELIVERY_CONFIG'].present? - case config.action_mailer.delivery_method - when :smtp - config.action_mailer.smtp_settings = - YAML.load("{ #{ENV['RAILS_MAIL_DELIVERY_CONFIG']} }").symbolize_keys - when :sendmail - config.action_mailer.sendmail_settings = - YAML.load("{ #{ENV['RAILS_MAIL_DELIVERY_CONFIG']} }").symbolize_keys - end - end - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = [I18n.default_locale] + config.i18n.fallbacks = true # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify @@ -116,12 +95,47 @@ # require 'syslog/logger' # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') - if ENV['RAILS_LOG_TO_STDOUT'].present? - logger = ActiveSupport::Logger.new($stdout) + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false + + # Inserts middleware to perform automatic connection switching. + # The `database_selector` hash is used to pass options to the DatabaseSelector + # middleware. The `delay` is used to determine how long to wait after a write + # to send a subsequent read to the primary. + # + # The `database_resolver` class is used by the middleware to determine which + # database is appropriate to use based on the time delay. + # + # The `database_resolver_context` class is used by the middleware to set + # timestamps for the last write to the primary. The resolver uses the context + # class timestamps to determine how long to wait before reading from the + # replica. + # + # By default Rails will store a last write timestamp in the session. The + # DatabaseSelector middleware is designed as such you can define your own + # strategy for connection switching and pass that into the middleware through + # these configuration options. + # config.active_record.database_selector = { delay: 2.seconds } + # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver + # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session + + # Mail sender + config.action_mailer.delivery_method = (ENV['RAILS_MAIL_DELIVERY_METHOD'].presence || :smtp).to_sym + + if ENV['RAILS_MAIL_DELIVERY_CONFIG'].present? + case config.action_mailer.delivery_method + when :smtp + config.action_mailer.smtp_settings = + YAML.load("{ #{ENV['RAILS_MAIL_DELIVERY_CONFIG']} }").symbolize_keys + when :sendmail + config.action_mailer.sendmail_settings = + YAML.load("{ #{ENV['RAILS_MAIL_DELIVERY_CONFIG']} }").symbolize_keys + end + end end diff --git a/config/environments/test.rb b/config/environments/test.rb index f54f986d..397bcb0c 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,15 +1,16 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# Copyright (c) 2006-2023, Puzzle ITC GmbH. This file is part of # PuzzleTime and licensed under the Affero General Public License version 3 # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - # The test environment is used exclusively to run your application's - # test suite. You never need to work with it otherwise. Remember that - # your test database is "scratch space" for the test suite and is wiped - # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Do not eager load code on boot. This avoids loading your whole application @@ -20,12 +21,13 @@ # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false + config.cache_store = :memory_store # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false @@ -33,7 +35,7 @@ # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false - # Store uploaded files on the local file system in a temporary directory + # Store uploaded files on the local file system in a temporary directory. config.active_storage.service = ENV.fetch('RAILS_STORAGE_SERVICE', 'test').to_sym config.action_mailer.perform_caching = false @@ -46,8 +48,6 @@ # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr - # Raises error for missing translations + # Raises error for missing translations. # config.action_view.raise_on_missing_translations = true - - config.cache_store = :memory_store end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 94db4b81..f4dc1ce0 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -6,12 +6,13 @@ # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path # Add Yarn node_modules folder to the asset load path. -Rails.application.config.assets.paths << - Rails.root.join('app', 'assets', 'fonts') << +Rails.application.config.assets.paths += [ + Rails.root.join('app', 'assets', 'fonts'), Rails.root.join('node_modules') +] # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. -# Rails.application.config.assets.precompile += %w( search.js ) +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) Rails.application.config.assets.precompile += %w(print.css phone.css *.png *.gif *.jpg *.svg) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index d3bcaa5e..35d0f26f 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -11,6 +11,8 @@ # policy.object_src :none # policy.script_src :self, :https # policy.style_src :self, :https +# # If you are using webpack-dev-server then specify webpack-dev-server host +# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? # # Specify URI for violation reports # # policy.report_uri "/csp-violation-report-endpoint" @@ -19,6 +21,9 @@ # If you are using UJS then enable automatic nonce generation # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } +# Set the nonce only to specific directives +# Rails.application.config.content_security_policy_nonce_directives = %w(script-src) + # Report CSP violations to a specified URI # For further information see the following documentation: # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only diff --git a/config/initializers/new_framework_defaults_6_0.rb b/config/initializers/new_framework_defaults_6_0.rb new file mode 100644 index 00000000..38c83656 --- /dev/null +++ b/config/initializers/new_framework_defaults_6_0.rb @@ -0,0 +1,45 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 6.0 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. + +# Don't force requests from old versions of IE to be UTF-8 encoded. +Rails.application.config.action_view.default_enforce_utf8 = false + +# Embed purpose and expiry metadata inside signed and encrypted +# cookies for increased security. +# +# This option is not backwards compatible with earlier Rails versions. +# It's best enabled when your entire app is migrated and stable on 6.0. +Rails.application.config.action_dispatch.use_cookies_with_metadata = true + +# Change the return value of `ActionDispatch::Response#content_type` to Content-Type header without modification. +Rails.application.config.action_dispatch.return_only_media_type_on_content_type = false + +# Return false instead of self when enqueuing is aborted from a callback. +# Rails.application.config.active_job.return_false_on_aborted_enqueue = true + +# Send Active Storage analysis and purge jobs to dedicated queues. +# Rails.application.config.active_storage.queues.analysis = :active_storage_analysis +# Rails.application.config.active_storage.queues.purge = :active_storage_purge + +# When assigning to a collection of attachments declared via `has_many_attached`, replace existing +# attachments instead of appending. Use #attach to add new attachments without replacing existing ones. +# Rails.application.config.active_storage.replace_on_assign_to_many = true + +# Use ActionMailer::MailDeliveryJob for sending parameterized and normal mail. +# +# The default delivery jobs (ActionMailer::Parameterized::DeliveryJob, ActionMailer::DeliveryJob), +# will be removed in Rails 6.1. This setting is not backwards compatible with earlier Rails versions. +# If you send mail in the background, job workers need to have a copy of +# MailDeliveryJob to ensure all delivery jobs are processed properly. +# Make sure your entire app is migrated and stable on 6.0 before using this setting. +# Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob" + +# Enable the same cache key to be reused when the object being cached of type +# `ActiveRecord::Relation` changes by moving the volatile information (max updated at and count) +# of the relation's cache key into the cache version to support recycling cache key. +# Rails.application.config.active_record.collection_cache_versioning = true diff --git a/config/locales/en.yml b/config/locales/en.yml index 287d3a1d..2c94fa98 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,9 +1,8 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# Copyright (c) 2006-2023, Puzzle ITC GmbH. This file is part of # PuzzleTime and licensed under the Affero General Public License version 3 # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. - # Files in the config/locales directory are used for internationalization # and are automatically loaded by Rails. If you want to use locales other # than English, add the necessary files in this directory. @@ -33,7 +32,7 @@ # 'true': 'foo' # # To learn more, please read the Rails Internationalization guide -# available at http://guides.rubyonrails.org/i18n.html. +# available at https://guides.rubyonrails.org/i18n.html. en: hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb index 9888b0fe..9a9d420e 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# Copyright (c) 2006-2023, Puzzle ITC GmbH. This file is part of # PuzzleTime and licensed under the Affero General Public License version 3 # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. @@ -9,8 +9,9 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 20 } -threads threads_count, threads_count +max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 20 } +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # @@ -20,8 +21,11 @@ # environment ENV.fetch("RAILS_ENV") { "development" } +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + # Specifies the number of `workers` to boot in clustered mode. -# Workers are forked webserver processes. If using threads and workers together +# Workers are forked web server processes. If using threads and workers together # the concurrency of the application would be max `threads` * `workers`. # Workers do not work on JRuby or Windows (both of which do not support # processes). diff --git a/config/routes.rb b/config/routes.rb index e728bca0..c1ba66ab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,17 +1,17 @@ -# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# Copyright (c) 2006-2023, Puzzle ITC GmbH. This file is part of # PuzzleTime and licensed under the Affero General Public License version 3 # or later. See the COPYING file at the top-level directory or at # https://github.com/puzzle/puzzletime. Rails.application.routes.draw do + # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html + devise_for :employees, controllers: { sessions: 'employees/sessions', omniauth_callbacks: 'employees/omniauth_callbacks' }, skip: [:registrations] as :employee do get 'employees/edit' => 'devise/registrations#edit', :as => 'edit_employee_registration' patch 'employees' => 'devise/registrations#update', :as => 'employee_registration' end - # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html - root to: 'worktimes#index' # mount DryCrudJsonapiSwagger::Engine => '/apidocs' diff --git a/db/migrate/20230206101600_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb b/db/migrate/20230206101600_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb new file mode 100644 index 00000000..ff5d72c7 --- /dev/null +++ b/db/migrate/20230206101600_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb @@ -0,0 +1,10 @@ +# This migration comes from active_storage (originally 20180723000244) +class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0] + def up + return if foreign_key_exists?(:active_storage_attachments, column: :blob_id) + + if table_exists?(:active_storage_blobs) + add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index b75f9618..8b84e87e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2,15 +2,15 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `rails +# db:schema:load`. When creating a new database, `rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_03_24_143608) do +ActiveRecord::Schema.define(version: 2023_02_06_101600) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity new file mode 100644 index 00000000..74953049 --- /dev/null +++ b/node_modules/.yarn-integrity @@ -0,0 +1,12 @@ +{ + "systemParams": "linux-x64-83", + "modulesFolders": [ + "node_modules" + ], + "flags": [], + "linkedModules": [], + "topLevelPatterns": [], + "lockfileEntries": {}, + "files": [], + "artifacts": {} +} \ No newline at end of file diff --git a/public/assets/.sprockets-manifest-389d4ce5d8509d6d04d0b775519d90f1.json b/public/assets/.sprockets-manifest-389d4ce5d8509d6d04d0b775519d90f1.json new file mode 100644 index 00000000..b3c23f23 --- /dev/null +++ b/public/assets/.sprockets-manifest-389d4ce5d8509d6d04d0b775519d90f1.json @@ -0,0 +1 @@ +{"files":{"manifest-dad05bf766af0fe3d79dd746db3c1361c0583026cdf35d6a2921bccaea835331.js":{"logical_path":"manifest.js","mtime":"2023-02-06T15:56:10+01:00","size":3,"digest":"6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167","integrity":"sha256-ajz1GSNU9xYVrFEDSz6Xwg7amWQ/yvW75tQa1ZvRIWc="},"ajax-loader-0e899a4e4cea34f16947494fb21069ec1dff319afc0f8c0ba8ba84406e28d3ba.gif":{"logical_path":"ajax-loader.gif","mtime":"2023-02-06T16:04:29+01:00","size":673,"digest":"43a526a07a078d736e5c9d67d8479dd54072b7e5c6ddd2cd466f86a086e49ef5","integrity":"sha256-Q6UmoHoHjXNuXJ1n2Eed1UByt+XG3dLNRm+GoIbknvU="},"calendar-48d011f3b73eb282dc94666ed015b3d2cc4a936b1182b1ba102ea9992d9c561c.gif":{"logical_path":"calendar.gif","mtime":"2023-02-06T16:04:29+01:00","size":127,"digest":"b65951581ff3b4219f3de2544ea69ec30c312dcb1a09b1c1aeadf2db6484dd81","integrity":"sha256-tllRWB/ztCGfPeJUTqaewwwxLcsaCbHBrq3y22SE3YE="},"calendar-61bcc4c23aa409db2f353d136ec0475cd5e7503242cd6adb1c34f5957538b202.png":{"logical_path":"calendar.png","mtime":"2023-02-06T16:04:29+01:00","size":150,"digest":"8184eacb118a0c9bf6891d2e114d91bfc8097933021d22f564f08efbfd25909f","integrity":"sha256-gYTqyxGKDJv2iR0uEU2Rv8gJeTMCHSL1ZPCO+/0lkJ8="},"day-marker-active-d99b32b60616df674b040de1ceef5f8fc77693ae7c8c169788eda089c84d4441.svg":{"logical_path":"day-marker-active.svg","mtime":"2023-02-06T16:04:29+01:00","size":894,"digest":"e74a96dd7d8d47e1dcdef9e39ad349f97eacbe692e56e094f881ffe13e03873f","integrity":"sha256-50qW3X2NR+Hc3vnjmtNJ+X6svmkuVuCU+IH/4T4Dhz8="},"day-marker-holiday-8a281e6680508108fe59cf1c01b6c56ec5510e3b48a36d08e8814ba1693e2ddb.svg":{"logical_path":"day-marker-holiday.svg","mtime":"2023-02-06T16:04:29+01:00","size":898,"digest":"b85d139fc4363666468fc8dd395fd7531ac6d4c5266f8327344dd42f9e55366e","integrity":"sha256-uF0Tn8Q2NmZGj8jdOV/XUxrG1MUmb4MnNE3UL55VNm4="},"day-marker-missing-44a616885a5627c4c6af6ba793461939b5962d6545f8760011508e5e498aec20.svg":{"logical_path":"day-marker-missing.svg","mtime":"2023-02-06T16:04:29+01:00","size":894,"digest":"d1e412d82ce50c05460746cf37d49194f6efb5b948ff0ee8e14e37f616712321","integrity":"sha256-0eQS2CzlDAVGB0bPN9SRlPbvtblI/w7o4U439hZxIyE="},"favicon-e7bb1f6b17d7f9dcab65221e7df38ad9cffe3b61fe1c0a035a8d113073327c96.png":{"logical_path":"favicon.png","mtime":"2023-02-06T16:04:29+01:00","size":490,"digest":"feadf43b00371109b481382aa70dd6535444960c62625b229af0b2b87b96084b","integrity":"sha256-/q30OwA3EQm0gTgqpw3WU1RElgxiYlsimvCyuHuWCEs="},"highrise-cdc00f1faa88687f254449a5b54f3c8c3f2ae8eb94c59d260ab4f107fc8f3f31.png":{"logical_path":"highrise.png","mtime":"2023-02-06T16:04:29+01:00","size":1121,"digest":"6442c3fdd7a632bd77daf86cc5d4c2cf687d00ddb5b6a239c6c82e2a470ae141","integrity":"sha256-ZELD/demMr132vhsxdTCz2h9AN21tqI5xsguKkcK4UE="},"logo-c9792e60e4d11c6511c7d6549ec1a8939ccc8db5f92ec6a00351b5d89e92c426.svg":{"logical_path":"logo.svg","mtime":"2023-02-06T16:04:29+01:00","size":1091,"digest":"8cf26a0e687dd17079efee1414dbfbea1e358c1e3c0e45f4adce44fb0b1e93e8","integrity":"sha256-jPJqDmh90XB57+4UFNv76h41jB48DkX0rc5E+wsek+g="},"lupe-c774b159265725d0245e0cb4f5dd2b323ec7bb823c37fa5249cd5b6004bdfce6.gif":{"logical_path":"lupe.gif","mtime":"2023-02-06T16:04:29+01:00","size":94,"digest":"9336ea2b8357244a75644f46ec6064cd44ff59c878481b830e9fbb3caf578d8f","integrity":"sha256-kzbqK4NXJEp1ZE9G7GBkzUT/Wch4SBuDDp+7PK9XjY8="},"space-9a89381c23d3b40e675948d825ad2df1ffbabb6c44489acc8b54f984d88d1854.gif":{"logical_path":"space.gif","mtime":"2023-02-06T16:04:29+01:00","size":43,"digest":"afe0dcfca292a0fae8bce08a48c14d3e59c9d82c6052ab6d48a22ecc6c48f277","integrity":"sha256-r+Dc/KKSoProvOCKSMFNPlnJ2CxgUqttSKIuzGxI8nc="},"whitebox-aa4dc20192f264f3b20f45ef8c3bcbe8d6d6bd4fe6011087a0c1e035bda32148.gif":{"logical_path":"whitebox.gif","mtime":"2023-02-06T16:04:29+01:00","size":110,"digest":"205a90ec6797eae903a00fea87c86b4b9ffee1599e7c913d537663ee5eca66fd","integrity":"sha256-IFqQ7GeX6ukDoA/qh8hrS5/+4VmefJE9U3Zj7l7KZv0="},"application-b13e430fa55310b01e7be687036d0909a5ea9d39952bb0c8422c6a1fac09a204.js":{"logical_path":"application.js","mtime":"2023-02-06T15:56:10+01:00","size":982710,"digest":"d7216e73ce50b4fe7f0b0b0f9af13a0f78424e378b86cd36759693beb3eb1fb6","integrity":"sha256-1yFuc85QtP5/CwsPmvE6D3hCTjeLhs02dZaTvrPrH7Y="},"application-bd0c129a2e26937339c4fd5440e31830cd84b6fe6115a594ba8d06b2866c3d19.css":{"logical_path":"application.css","mtime":"2023-02-06T16:04:29+01:00","size":522001,"digest":"499e194edfafe4086ed761101b8d8c155e2a87a4ebae1a71f37420f5cb45921c","integrity":"sha256-SZ4ZTt+v5Ahu12EQG42MFV4qh6Trrhpx83Qg9ctFkhw="},"ui-bg_glass_75_ffffff_1x400-e6dec50d602c5c8829427ecd8164ac59dc3e8258d1dc7d0d7fda47239669cad9.png":{"logical_path":"ui-bg_glass_75_ffffff_1x400.png","mtime":"2023-02-06T16:04:29+01:00","size":107,"digest":"2f5ecbbd2965ce36b5b47226f59485bd4fbdf038c460b082a2cdb6dcec01ff4d","integrity":"sha256-L17LvSllzja1tHIm9ZSFvU+98DjEYLCCos223OwB/00="},"ui-icons_222222_256x240-3305696b28a8757b9b73e678cc6c72dc7277107d3df71dc3baf4f77312473ae9.png":{"logical_path":"ui-icons_222222_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":4369,"digest":"a2ccfdc001858222885a9df39200840ac7a3f479ba889727d32a10398db7918a","integrity":"sha256-osz9wAGFgiKIWp3zkgCECsej9Hm6iJcn0yoQOY23kYo="},"ui-icons_888888_256x240-42393eae298d8afe2c78cd1caf6a1abfecc8201d7adf92c594e386f8f10cf987.png":{"logical_path":"ui-icons_888888_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":4369,"digest":"a42b23e21050a0f0f90c1f7a443b8087a409771611eae402861959a793be38e8","integrity":"sha256-pCsj4hBQoPD5DB96RDuAh6QJdxYR6uQChhlZp5O+OOg="},"ui-icons_454545_256x240-989d471b55541d188455465047133795033ae7d4e2b929b2daac08112ac1c3bd.png":{"logical_path":"ui-icons_454545_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":4369,"digest":"cb36e80beaf2a527d463da552a5c679a46c4ff8c881318a194bb0ccb61cb2d5c","integrity":"sha256-yzboC+rypSfUY9pVKlxnmkbE/4yIExihlLsMy2HLLVw="},"ui-icons_2e83ff_256x240-71ec8b5da0ab851bbef886db2d916428fcad59d785b5f505af8cd3c8fbb43cb2.png":{"logical_path":"ui-icons_2e83ff_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":4369,"digest":"4f907b912e024625d36b8af307f1043e6ebc97074e31216175d14bb74c370dc3","integrity":"sha256-T5B7kS4CRiXTa4rzB/EEPm68lwdOMSFhddFLt0w3DcM="},"ui-icons_f6cf3b_256x240-fa649f3fa8d7d4f9343a1d0714d848bf023da59a7985bd51a451d47ee2cc434e.png":{"logical_path":"ui-icons_f6cf3b_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":8884,"digest":"14f31a14a6be5471828aa900b541e125456013941d82b88fe0cb712c36563758","integrity":"sha256-FPMaFKa+VHGCiqkAtUHhJUVgE5QdgriP4MtxLDZWN1g="},"ui-bg_flat_0_aaaaaa_40x100-aa167a04c6c4e82e1fbe80ec8a290de0a228ff1f074e64fda276e18c53b6885a.png":{"logical_path":"ui-bg_flat_0_aaaaaa_40x100.png","mtime":"2023-02-06T16:04:29+01:00","size":180,"digest":"9a8492a580bf85d3e98ae8861fbd45567e5a1f83eeafcf9574da0399d5f602ab","integrity":"sha256-moSSpYC/hdPpiuiGH71FVn5aH4Pur8+VdNoDmdX2Aqs="},"jquery-ui/ui-icons_444444_256x240-f6d8c091e81dfb911fc0815e7c9952c90b361a3f685804be08a5fa2ee6d6198b.png":{"logical_path":"jquery-ui/ui-icons_444444_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":3756,"digest":"31d988765b4e6f56553c29588c500381dc3e6f0aa2980c8212202e5644aefd5d","integrity":"sha256-MdmIdltOb1ZVPClYjFADgdw+bwqimAyCEiAuVkSu/V0="},"jquery-ui/ui-icons_555555_256x240-c23f23416a84a359723ffe28c6231f7e6edd10de024da40c190f9e0706c70276.png":{"logical_path":"jquery-ui/ui-icons_555555_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":3756,"digest":"32175261daee76c82bb0edf0eea16a56421866fbc31e94f3c1d570aa114502f5","integrity":"sha256-MhdSYdrudsgrsO3w7qFqVkIYZvvDHpTzwdVwqhFFAvU="},"jquery-ui/ui-icons_ffffff_256x240-24221a00d39ccfd2da654907e99b5af8d8dc60b91bfb414b4ca82779f7f9ffb6.png":{"logical_path":"jquery-ui/ui-icons_ffffff_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":3756,"digest":"350df1b7131037de20e83c5c0f3a41a770d2ac48b5762ea772b3f4a8a7b9d47a","integrity":"sha256-NQ3xtxMQN94g6DxcDzpBp3DSrEi1di6ncrP0qKe51Ho="},"jquery-ui/ui-icons_777620_256x240-0a3bb6e0c499ea585572cbda26b638d08b96a568bfb173f0dce2c9eee4a441b3.png":{"logical_path":"jquery-ui/ui-icons_777620_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":3756,"digest":"0b020fc6e696d88d296e7bb1f61f1eb2ad827848e2c7382a4c3e0999e702dd9b","integrity":"sha256-CwIPxuaW2I0pbnux9h8esq2CeEjixzgqTD4JmecC3Zs="},"jquery-ui/ui-icons_cc0000_256x240-c7cf3a680d1476856186d49a361c0b3e09d13a2f4ba4ebd638595bbad9d21c2d.png":{"logical_path":"jquery-ui/ui-icons_cc0000_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":3756,"digest":"40985a64b4d5dd213fba27fcd862a1bd1b337a97674f6ff0b9ec20abcee4bc69","integrity":"sha256-QJhaZLTV3SE/uif82GKhvRszepdnT2/wuewgq87kvGk="},"jquery-ui/ui-icons_777777_256x240-ea4f5d5317b31eaaaf01e3db3357884b418c8b3361019647559c835996ddaedb.png":{"logical_path":"jquery-ui/ui-icons_777777_256x240.png","mtime":"2023-02-06T16:04:29+01:00","size":3756,"digest":"faf32007ae120c302213557626e660dd10e711c5dd4f1113d35f26dc05b78d2f","integrity":"sha256-+vMgB64SDDAiE1V2JuZg3RDnEcXdTxET018m3AW3jS8="},"bootstrap/glyphicons-halflings-regular-0805fb1fe24235f70a639f67514990e4bfb6d2cfb00ca563ad4b553c240ddc33.eot":{"logical_path":"bootstrap/glyphicons-halflings-regular.eot","mtime":"2023-02-06T16:04:29+01:00","size":20127,"digest":"13634da87d9e23f8c3ed9108ce1724d183a39ad072e73e1b3d8cbf646d2d0407","integrity":"sha256-E2NNqH2eI/jD7ZEIzhck0YOjmtBy5z4bPYy/ZG0tBAc="},"bootstrap/glyphicons-halflings-regular-403acfcf0cbaebd1c28b404eec442cea53642644b3a73f91c5a4ab46859af772.woff2":{"logical_path":"bootstrap/glyphicons-halflings-regular.woff2","mtime":"2023-02-06T16:04:29+01:00","size":18028,"digest":"fe185d11a49676890d47bb783312a0cda5a44c4039214094e7957b4c040ef11c","integrity":"sha256-/hhdEaSWdokNR7t4MxKgzaWkTEA5IUCU55V7TAQO8Rw="},"bootstrap/glyphicons-halflings-regular-0703369a358a012c0011843ae337a8a20270c336948a8668df5cb89a8827299b.woff":{"logical_path":"bootstrap/glyphicons-halflings-regular.woff","mtime":"2023-02-06T16:04:29+01:00","size":23424,"digest":"a26394f7ede100ca118eff2eda08596275a9839b959c226e15439557a5a80742","integrity":"sha256-omOU9+3hAMoRjv8u2ghZYnWpg5uVnCJuFUOVV6WoB0I="},"bootstrap/glyphicons-halflings-regular-7c9caa5f4e16169b0129fdf93c84e85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf":{"logical_path":"bootstrap/glyphicons-halflings-regular.ttf","mtime":"2023-02-06T16:04:29+01:00","size":45404,"digest":"e395044093757d82afcb138957d06a1ea9361bdcf0b442d06a18a8051af57456","integrity":"sha256-45UEQJN1fYKvyxOJV9BqHqk2G9zwtELQahioBRr1dFY="},"bootstrap/glyphicons-halflings-regular-22d0c88a49d7d0ebe45627143a601061a32a46a9b9afd2dc7f457436f5f15f6e.svg":{"logical_path":"bootstrap/glyphicons-halflings-regular.svg","mtime":"2023-02-06T16:04:29+01:00","size":108738,"digest":"42f60659d265c1a3c30f9fa42abcbb56bd4a53af4d83d316d6dd7a36903c43e5","integrity":"sha256-QvYGWdJlwaPDD5+kKry7Vr1KU69Ng9MW1t16NpA8Q+U="},"puzzletime-2de41030a7af76acbd1f6ccb10bc0b7dd5f348a427b3c12120fe15f61b7903cc.eot":{"logical_path":"puzzletime.eot","mtime":"2023-02-06T16:04:29+01:00","size":6364,"digest":"dd1884a392df8a234f429ee82fb4eb2f3060e2b805c98924022e458f7c272004","integrity":"sha256-3RiEo5LfiiNPQp7oL7TrLzBg4rgFyYkkAi5Fj3wnIAQ="},"puzzletime-61a62a54f3b770abbbdc71254ea1b19283766200e3997f7a474d8d9692c1dddd.woff":{"logical_path":"puzzletime.woff","mtime":"2023-02-06T16:04:29+01:00","size":4504,"digest":"cfa6889178018165190780b16c930e5498e8a6b0182c28fc522bfc023c528e89","integrity":"sha256-z6aIkXgBgWUZB4CxbJMOVJjoprAYLCj8Uiv8AjxSjok="},"puzzletime-e2ba8ea4540e897e42ea9182e7ff59463333c45a93e17f5dda18854cc44076f2.ttf":{"logical_path":"puzzletime.ttf","mtime":"2023-02-06T16:04:29+01:00","size":6188,"digest":"7db581c820b4305d0e6215543b445e124dc38ec414e315d6fde7d8cce139a933","integrity":"sha256-fbWByCC0MF0OYhVUO0ReEk3DjsQU4xXW/efYzOE5qTM="},"puzzletime-d547a1d48c348caf9d81ddc08eba7c0de75acda38363a42c5831513a9a2b3d86.svg":{"logical_path":"puzzletime.svg","mtime":"2023-02-06T16:04:29+01:00","size":12966,"digest":"ff75f7ae9883c9bb9cdc8ee607e608a60050ada9394e74d28974db936f222d94","integrity":"sha256-/3X3rpiDybuc3I7mB+YIpgBQrak5TnTSiXTbk28iLZQ="},"print-23f9a8c22460910734dee3bd913abc3dbea21463a0a0a1ee40bdf9a7fe99e34f.css":{"logical_path":"print.css","mtime":"2023-02-06T16:04:29+01:00","size":309417,"digest":"6941635d181b59ce568a7496b9329d84897dd5d641ee36dcfe9ffb5ee3b6d3ad","integrity":"sha256-aUFjXRgbWc5WinSWuTKdhIl91dZB7jbc/p/7XuO2060="},"phone-559c5ed1cdc3ac647348a0dd390a2609abbae2654ab85b1f65fdfb55a76fe173.css":{"logical_path":"phone.css","mtime":"2023-02-06T16:04:29+01:00","size":2013,"digest":"c646cb7e729fa19760e71e2552e56d0bf44f9a715998a826a8564cd0b3fb1f9e","integrity":"sha256-xkbLfnKfoZdg5x4lUuVtC/RPmnFZmKgmqFZM0LP7H54="},"manifest-6ad05901a908774949f62ed6b92ec813ad3abc1bb688bf3daa9eb134b0186f80.js":{"logical_path":"manifest.js","mtime":"2023-02-06T16:04:29+01:00","size":905457,"digest":"3dcbd6f4cb1ef9e30be2e630f79c89b3a89f58a4202fbc0e4e86e66b79d62683","integrity":"sha256-PcvW9Mse+eML4uYw95yJs6ifWKQgL7wOTobma3nWJoM="},"application-d8f81a2184b7e0302aba9ec8b71a8def6d553c805ce3784542f477ed63499287.coffee":{"logical_path":"application.coffee","mtime":"2023-02-06T16:04:29+01:00","size":3366,"digest":"e1bcaaf6f15096b2131b04a8d9525d9df66159a8a7d15e233450e4a18aa0c1a0","integrity":"sha256-4byq9vFQlrITGwSo2VJdnfZhWain0V4jNFDkoYqgwaA="},"application-795779a25521261357be45bad289128fac47df4ce07ddc3bcc37059e5909dc34.js":{"logical_path":"application.js","mtime":"2023-02-06T16:04:29+01:00","size":77276,"digest":"162013a8415290c8bf8e3e6a5e469bbdf03ac1e1716c725683f0aa53b8293eec","integrity":"sha256-FiATqEFSkMi/jj5qXkabvfA6weFxbHJWg/CqU7gpPuw="}},"assets":{"manifest.js":"manifest-6ad05901a908774949f62ed6b92ec813ad3abc1bb688bf3daa9eb134b0186f80.js","ajax-loader.gif":"ajax-loader-0e899a4e4cea34f16947494fb21069ec1dff319afc0f8c0ba8ba84406e28d3ba.gif","calendar.gif":"calendar-48d011f3b73eb282dc94666ed015b3d2cc4a936b1182b1ba102ea9992d9c561c.gif","calendar.png":"calendar-61bcc4c23aa409db2f353d136ec0475cd5e7503242cd6adb1c34f5957538b202.png","day-marker-active.svg":"day-marker-active-d99b32b60616df674b040de1ceef5f8fc77693ae7c8c169788eda089c84d4441.svg","day-marker-holiday.svg":"day-marker-holiday-8a281e6680508108fe59cf1c01b6c56ec5510e3b48a36d08e8814ba1693e2ddb.svg","day-marker-missing.svg":"day-marker-missing-44a616885a5627c4c6af6ba793461939b5962d6545f8760011508e5e498aec20.svg","favicon.png":"favicon-e7bb1f6b17d7f9dcab65221e7df38ad9cffe3b61fe1c0a035a8d113073327c96.png","highrise.png":"highrise-cdc00f1faa88687f254449a5b54f3c8c3f2ae8eb94c59d260ab4f107fc8f3f31.png","logo.svg":"logo-c9792e60e4d11c6511c7d6549ec1a8939ccc8db5f92ec6a00351b5d89e92c426.svg","lupe.gif":"lupe-c774b159265725d0245e0cb4f5dd2b323ec7bb823c37fa5249cd5b6004bdfce6.gif","space.gif":"space-9a89381c23d3b40e675948d825ad2df1ffbabb6c44489acc8b54f984d88d1854.gif","whitebox.gif":"whitebox-aa4dc20192f264f3b20f45ef8c3bcbe8d6d6bd4fe6011087a0c1e035bda32148.gif","application.js":"application-795779a25521261357be45bad289128fac47df4ce07ddc3bcc37059e5909dc34.js","application.css":"application-bd0c129a2e26937339c4fd5440e31830cd84b6fe6115a594ba8d06b2866c3d19.css","ui-bg_glass_75_ffffff_1x400.png":"ui-bg_glass_75_ffffff_1x400-e6dec50d602c5c8829427ecd8164ac59dc3e8258d1dc7d0d7fda47239669cad9.png","ui-icons_222222_256x240.png":"ui-icons_222222_256x240-3305696b28a8757b9b73e678cc6c72dc7277107d3df71dc3baf4f77312473ae9.png","ui-icons_888888_256x240.png":"ui-icons_888888_256x240-42393eae298d8afe2c78cd1caf6a1abfecc8201d7adf92c594e386f8f10cf987.png","ui-icons_454545_256x240.png":"ui-icons_454545_256x240-989d471b55541d188455465047133795033ae7d4e2b929b2daac08112ac1c3bd.png","ui-icons_2e83ff_256x240.png":"ui-icons_2e83ff_256x240-71ec8b5da0ab851bbef886db2d916428fcad59d785b5f505af8cd3c8fbb43cb2.png","ui-icons_f6cf3b_256x240.png":"ui-icons_f6cf3b_256x240-fa649f3fa8d7d4f9343a1d0714d848bf023da59a7985bd51a451d47ee2cc434e.png","ui-bg_flat_0_aaaaaa_40x100.png":"ui-bg_flat_0_aaaaaa_40x100-aa167a04c6c4e82e1fbe80ec8a290de0a228ff1f074e64fda276e18c53b6885a.png","jquery-ui/ui-icons_444444_256x240.png":"jquery-ui/ui-icons_444444_256x240-f6d8c091e81dfb911fc0815e7c9952c90b361a3f685804be08a5fa2ee6d6198b.png","jquery-ui/ui-icons_555555_256x240.png":"jquery-ui/ui-icons_555555_256x240-c23f23416a84a359723ffe28c6231f7e6edd10de024da40c190f9e0706c70276.png","jquery-ui/ui-icons_ffffff_256x240.png":"jquery-ui/ui-icons_ffffff_256x240-24221a00d39ccfd2da654907e99b5af8d8dc60b91bfb414b4ca82779f7f9ffb6.png","jquery-ui/ui-icons_777620_256x240.png":"jquery-ui/ui-icons_777620_256x240-0a3bb6e0c499ea585572cbda26b638d08b96a568bfb173f0dce2c9eee4a441b3.png","jquery-ui/ui-icons_cc0000_256x240.png":"jquery-ui/ui-icons_cc0000_256x240-c7cf3a680d1476856186d49a361c0b3e09d13a2f4ba4ebd638595bbad9d21c2d.png","jquery-ui/ui-icons_777777_256x240.png":"jquery-ui/ui-icons_777777_256x240-ea4f5d5317b31eaaaf01e3db3357884b418c8b3361019647559c835996ddaedb.png","bootstrap/glyphicons-halflings-regular.eot":"bootstrap/glyphicons-halflings-regular-0805fb1fe24235f70a639f67514990e4bfb6d2cfb00ca563ad4b553c240ddc33.eot","bootstrap/glyphicons-halflings-regular.woff2":"bootstrap/glyphicons-halflings-regular-403acfcf0cbaebd1c28b404eec442cea53642644b3a73f91c5a4ab46859af772.woff2","bootstrap/glyphicons-halflings-regular.woff":"bootstrap/glyphicons-halflings-regular-0703369a358a012c0011843ae337a8a20270c336948a8668df5cb89a8827299b.woff","bootstrap/glyphicons-halflings-regular.ttf":"bootstrap/glyphicons-halflings-regular-7c9caa5f4e16169b0129fdf93c84e85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf","bootstrap/glyphicons-halflings-regular.svg":"bootstrap/glyphicons-halflings-regular-22d0c88a49d7d0ebe45627143a601061a32a46a9b9afd2dc7f457436f5f15f6e.svg","puzzletime.eot":"puzzletime-2de41030a7af76acbd1f6ccb10bc0b7dd5f348a427b3c12120fe15f61b7903cc.eot","puzzletime.woff":"puzzletime-61a62a54f3b770abbbdc71254ea1b19283766200e3997f7a474d8d9692c1dddd.woff","puzzletime.ttf":"puzzletime-e2ba8ea4540e897e42ea9182e7ff59463333c45a93e17f5dda18854cc44076f2.ttf","puzzletime.svg":"puzzletime-d547a1d48c348caf9d81ddc08eba7c0de75acda38363a42c5831513a9a2b3d86.svg","print.css":"print-23f9a8c22460910734dee3bd913abc3dbea21463a0a0a1ee40bdf9a7fe99e34f.css","phone.css":"phone-559c5ed1cdc3ac647348a0dd390a2609abbae2654ab85b1f65fdfb55a76fe173.css","application.coffee":"application-d8f81a2184b7e0302aba9ec8b71a8def6d553c805ce3784542f477ed63499287.coffee"}} \ No newline at end of file diff --git a/public/assets/ajax-loader-0e899a4e4cea34f16947494fb21069ec1dff319afc0f8c0ba8ba84406e28d3ba.gif b/public/assets/ajax-loader-0e899a4e4cea34f16947494fb21069ec1dff319afc0f8c0ba8ba84406e28d3ba.gif new file mode 100644 index 0000000000000000000000000000000000000000..d0bce1542342e912da81a2c260562df172f30d73 GIT binary patch literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nnmm28Kh24mmkF0U1e2Nli^nlO|14{Lk&@8WQa67~pE8 zXTZz|lvDgC+Z`3#dv5h=E26FfcG1 zbL_hF&)}42ws10s6^G;;cE1^EoUR)U5A70}d2pLv!jVIT7j&Z~EblI3x0K*v_sV|m z0kj3v921Z^em#l`(k(o@H$3ZdDRc@9NidXDNbqrumReCGv$gd8+e8WW28HVqkJ_9i zH>s*<31KtHjANIPvi2#*6BEu%3Dak5O_t&NBI)H?V$TxT}#l{vOTn5naXTfF^&~Hhq+NX@#Ccc>y7T?;vjI&jdhsDsPJyAw*m0Qz>i}K7# zL9w50Ng{fT}A5JUe8lRK1h7_Y2;BWJDd=c6f&i?Wv5(5q?6|P zQw{>maxZP<537OA37Uk}7@%_$4o$EWe_Zl>&#id|lE-BpDC#+Fn|msJ%_2h{Hg1vP z#N8WAzfWasG}yq|xqE)DrWaOofX=z|?*pgc%{ig5vl!pqDlC|q&~Z0$&Rvsft&VO- z4MZj+%-+Vx%W}v;V76hyp=;+R;x+~t^Q%*xuFTQAF2})fSfTHDAs>sO!OBw`)&)o$ c0!CNZt))x~rAZP^^P&YOFfdqy5)K#u0POD40{{R3 literal 0 HcmV?d00001 diff --git a/public/assets/application-795779a25521261357be45bad289128fac47df4ce07ddc3bcc37059e5909dc34.js b/public/assets/application-795779a25521261357be45bad289128fac47df4ce07ddc3bcc37059e5909dc34.js new file mode 100644 index 00000000..210edaed --- /dev/null +++ b/public/assets/application-795779a25521261357be45bad289128fac47df4ce07ddc3bcc37059e5909dc34.js @@ -0,0 +1,2452 @@ +(function() { + debugger; + var app; + + app = window.App || (window.App = {}); + + if (typeof String.prototype.endsWith !== 'function') { + String.prototype.endsWith = function(suffix) { + return this.indexOf(suffix, this.length - suffix.length) !== -1; + }; + } + + Object.defineProperty(Object.prototype, 'do', { + value: function(callback) { + callback.call(this, this); + return this; + } + }); + + if (typeof Object.assign !== 'function') { + Object.assign = function(target) { + 'use strict'; + var index, nextKey, output, source; + if (target == null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + output = Object(target); + index = 1; + while (index < arguments.length) { + source = arguments[index]; + if (source !== void 0 && source !== null) { + for (nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + index++; + } + return output; + }; + } + + Selectize.define('required-fix', function(options) { + return this.refreshValidityState = (function(_this) { + return function() { + var invalid; + if (!_this.isRequired) { + return false; + } + invalid = !_this.items.length; + _this.isInvalid = invalid; + if (invalid) { + _this.$control_input.attr('required', ''); + return _this.$input.removeAttr('required'); + } else { + _this.$control_input.removeAttr('required'); + return _this.$input.attr('required'); + } + }; + })(this); + }); + + $(document).on('click', '[data-toggle]', function(event) { + var id; + id = $(this).data('toggle'); + if (id !== 'tooltip') { + $('#' + id).slideToggle(200); + return event.preventDefault(); + } + }); + + $(document).on('change', '[data-submit]', function(event) { + return $(this).closest('form').submit(); + }); + + $(document).tooltip({ + selector: '[data-toggle=tooltip]', + container: 'body', + placement: 'top', + html: true + }); + + $(document).on("fields_added.nested_form_fields", function(event, param) { + return $('select.searchable').selectize(); + }); + + $(document).on('ajax:error', function(event, xhr, status, error) { + return alert('Sorry, something went wrong\n(' + error + ')'); + }); + + $(document).on('turbolinks:load', function() { + $('select.searchable:not([multiple])').selectize({ + selectOnTab: true + }); + $('select[multiple].searchable').selectize({ + plugins: ['remove_button'], + selectOnTab: true + }); + $('[data-toggle=buttons]').button(); + $('body').on('click', 'a.disabled', function(event) { + event.preventDefault(); + event.stopImmediatePropagation(); + return event.stopPropagation(); + }); + $('.initial-focus, .initial-focus input').focus(); + return setTimeout(function() { + return $('.initial-focus.selectized').next('.selectize-control').find('input').focus(); + }); + }); + +}).call(this); +(function() { + var app; + + app = window.App || (window.App = {}); + + app.Autocomplete = (function() { + function Autocomplete() {} + + Autocomplete.prototype.bind = function(input) { + return $(input).selectize({ + plugins: ['required-fix'], + valueField: 'id', + searchField: this.searchFields(), + selectOnTab: true, + openOnFocus: false, + render: { + option: this.renderOption.bind(this), + item: this.renderItem + }, + load: this.loadOptions(input), + onItemAdd: this.onItemAdd + }); + }; + + Autocomplete.prototype.searchFields = function() { + return ['name', 'path_shortnames', 'path_names']; + }; + + Autocomplete.prototype.onItemAdd = function() {}; + + Autocomplete.prototype.renderOption = function(item, escape) { + return "
" + ("
" + (escape(item.path_shortnames)) + "
") + ("
" + (escape(this.limitText(item.name, 70))) + "
") + "
"; + }; + + Autocomplete.prototype.renderItem = function(item, escape) { + return "
" + (escape(item.path_shortnames)) + ": " + (escape(item.name)) + "
"; + }; + + Autocomplete.prototype.loadOptions = function(input) { + return function(query, callback) { + if (query.length) { + return $.ajax({ + url: Autocomplete.prototype.buildUrl(input, query), + type: 'GET', + error: function() { + return callback(); + }, + success: function(res) { + return callback(res); + } + }); + } else { + return callback(); + } + }; + }; + + Autocomplete.prototype.buildUrl = function(input, query) { + var param, param_char, url; + url = $(input).data('url'); + param = encodeURIComponent(query); + param_char = url.indexOf('?') >= 0 ? '&' : '?'; + return "" + url + param_char + "q=" + param; + }; + + Autocomplete.prototype.limitText = function(string, max) { + if (!string) { + return ''; + } else if (string.length > max) { + return string.substr(0, max) + '…'; + } else { + return string; + } + }; + + return Autocomplete; + + })(); + +}).call(this); +(function() { + var app; + + app = window.App || (window.App = {}); + + app.checkbox || (app.checkbox = {}); + + app.checkbox.Toggler = (function() { + function Toggler(data, action) { + this.data = data; + this.action = action; + this.toggleChecked = function(checkbox) { + var checked, selector; + selector = $(checkbox).data(this.data); + checked = $(checkbox).prop('checked'); + return new this.action(selector).toggle(checked); + }; + } + + Toggler.prototype.bind = function() { + var selector, self; + self = this; + selector = '[data-' + this.data + ']'; + $(document).on('click', selector, function(event) { + return self.toggleChecked(this); + }); + return $(document).on('turbolinks:load', function() { + return $(selector).each(function(i, e) { + return self.toggleChecked(e); + }); + }); + }; + + return Toggler; + + })(); + +}).call(this); +(function() { + var app; + + app = window.App || (window.App = {}); + + app.checkbox || (app.checkbox = {}); + + app.checkbox.AllChecker = (function() { + function AllChecker(name) { + this.name = name; + } + + AllChecker.prototype.toggle = function(checked) { + return $('input[type=checkbox][name="' + this.name + '"]').prop('checked', checked); + }; + + return AllChecker; + + })(); + + new app.checkbox.Toggler('check', app.checkbox.AllChecker).bind(); + +}).call(this); +(function() { + var app; + + app = window.App || (window.App = {}); + + app.checkbox || (app.checkbox = {}); + + app.checkbox.ElementHider = (function() { + function ElementHider(selector) { + this.selector = selector; + } + + ElementHider.prototype.toggle = function(hide) { + return $(this.selector).toggle(!hide); + }; + + return ElementHider; + + })(); + + new app.checkbox.Toggler('hide', app.checkbox.ElementHider).bind(); + +}).call(this); +(function() { + var app; + + app = window.App || (window.App = {}); + + app.checkbox || (app.checkbox = {}); + + app.checkbox.InputEnabler = (function() { + function InputEnabler(selector) { + this.selector = selector; + this.inputs = function() { + return $('input' + this.selector + ', select' + this.selector + ', textarea' + this.selector); + }; + this.affected = function() { + return $(this.selector); + }; + } + + InputEnabler.prototype.toggle = function(enabled) { + if (enabled) { + return this.enable(); + } else { + return this.disable(); + } + }; + + InputEnabler.prototype.enable = function() { + this.inputs().prop('disabled', false); + this.affected().removeClass('disabled'); + return $.each(this.affected(), function(i, e) { + if (e.selectize) { + return e.selectize.enable(); + } + }); + }; + + InputEnabler.prototype.disable = function() { + this.inputs().prop('disabled', true); + this.affected().addClass('disabled'); + return $.each(this.affected(), function(i, e) { + if (e.selectize) { + return e.selectize.disable(); + } + }); + }; + + return InputEnabler; + + })(); + + new app.checkbox.Toggler('enable', app.checkbox.InputEnabler).bind(); + +}).call(this); +(function() { + var ClearInput; + + ClearInput = (function() { + function ClearInput() {} + + ClearInput.prototype.clear = function(cross) { + console.log('click'); + this._input(cross).val('').trigger('change'); + return this._input(cross).parents('form').submit(); + }; + + ClearInput.prototype.toggleHide = function(input) { + var group; + group = input.parents('.has-clear'); + if (input.val() === '') { + return group.addClass('has-empty-value'); + } else { + return group.removeClass('has-empty-value'); + } + }; + + ClearInput.prototype._input = function(cross) { + return cross.parents('.has-clear').find('input[type=search]'); + }; + + ClearInput.prototype.bind = function() { + var self; + self = this; + $(document).on('click', '[data-clear]', function() { + return self.clear($(this)); + }); + return $(document).on('change', '.has-clear input[type=search]', function() { + return self.toggleHide($(this)); + }); + }; + + return ClearInput; + + })(); + + new ClearInput().bind(); + + $(document).on('turbolinks:load', function() { + return $('.has-clear input[type=search]').each(function(i, e) { + return new ClearInput().toggleHide($(e)); + }); + }); + +}).call(this); +(function() { + var app; + + app = window.App || (window.App = {}); + + app.DynamicParams = (function() { + function DynamicParams(element, request1) { + this.element = element; + this.request = request1; + this.url = function() { + return this.request.url + this.joint() + this.urlParams().join('&'); + }; + this.urlParams = function() { + var i, len, p, ref, results, value; + ref = this.dynamicParams(); + results = []; + for (i = 0, len = ref.length; i < len; i++) { + p = ref[i]; + value = $('#' + p.replace('[', '_').replace(']', '')).val() || ''; + results.push(encodeURIComponent(p) + "=" + value); + } + return results; + }; + this.dynamicParams = function() { + return $(this.element).data('dynamic-params').split(','); + }; + this.joint = function() { + if (this.request.url.indexOf('?') === -1) { + return '?'; + } else { + return '&'; + } + }; + } + + DynamicParams.prototype.append = function() { + return this.request.url = this.url(); + }; + + return DynamicParams; + + })(); + + $(document).on('ajax:beforeSend', '[data-dynamic-params]', function(event, xhr, request) { + return new app.DynamicParams(this, request).append(); + }); + +}).call(this); +(function() { + var app, + slice = [].slice; + + app = window.App || (window.App = {}); + + app.FormUpdater = (function() { + function FormUpdater() { + var event, formSelector, url, watchSelectors; + url = arguments[0], event = arguments[1], formSelector = arguments[2], watchSelectors = 4 <= arguments.length ? slice.call(arguments, 3) : []; + this.url = url; + this.event = event; + this.form = $(formSelector); + this.watchedElements = watchSelectors.join(', '); + this._bind(); + } + + FormUpdater.prototype.updateForm = function() { + return this._getUrl(this.url); + }; + + FormUpdater.prototype._params = function() { + return this.form.serialize(); + }; + + FormUpdater.prototype._getUrl = function() { + return $.getScript(this.url + "?" + (this._params())); + }; + + FormUpdater.prototype._bind = function() { + return $(document).on(this.event, this.watchedElements, (function(_this) { + return function(event) { + return _this.updateForm(); + }; + })(this)); + }; + + return FormUpdater; + + })(); + +}).call(this); +(function() { + var app; + + app = window.App || (window.App = {}); + + app.LinkedTableRow = (function() { + function LinkedTableRow(cell) { + this.row = $(cell).closest('tr'); + this.url = function() { + return this.urlTemplate().replace('/:id/', '/' + this.rowId() + '/'); + }; + this.urlTemplate = function() { + return this.row.closest('[data-row-link]').data('row-link'); + }; + this.rowId = function() { + return this.row.get(0).id.match(/\w+_(\d+)/)[1]; + }; + } + + LinkedTableRow.prototype.openLink = function() { + return window.location = this.url(); + }; + + return LinkedTableRow; + + })(); + + $(document).on('click', '[data-row-link] tbody tr:not([data-no-link=true]) td:not(.no-link)', function(event) { + return new app.LinkedTableRow(this).openLink(); + }); + +}).call(this); +(function() { + var app, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + app = window.App || (window.App = {}); + + app.OrderAutocomplete = (function(superClass) { + extend(OrderAutocomplete, superClass); + + function OrderAutocomplete() { + return OrderAutocomplete.__super__.constructor.apply(this, arguments); + } + + OrderAutocomplete.prototype.onItemAdd = function(value, item) { + if (value) { + return window.location = window.location.toString().replace(/orders\/\d+/, 'orders/' + value); + } + }; + + return OrderAutocomplete; + + })(app.Autocomplete); + + $(document).on('turbolinks:load', function() { + return $('[data-autocomplete=order]').each(function(i, element) { + return new app.OrderAutocomplete().bind(element); + }); + }); + +}).call(this); +(function() { + var app; + + app = window.App || (window.App = {}); + + app.SelectizeRefresher = (function() { + function SelectizeRefresher(master) { + this.master = master; + this.url = function() { + return this.master.data('url'); + }; + this.params = function() { + return this.master.serialize(); + }; + this.selectize = function() { + return $(this.master.data('update'))[0].selectize; + }; + } + + SelectizeRefresher.prototype.load = function() { + return $.getJSON(this.url(), this.params(), (function(_this) { + return function(data) { + return _this.refresh(data); + }; + })(this)); + }; + + SelectizeRefresher.prototype.refresh = function(data) { + var selectize; + selectize = this.selectize(); + selectize.clear(); + selectize.clearOptions(); + data.forEach(function(e) { + return selectize.addOption({ + value: e.id, + text: e.label + }); + }); + return selectize.refreshOptions(false); + }; + + return SelectizeRefresher; + + })(); + + $(document).on('change', '[data-update][data-url]', function(event) { + return new app.SelectizeRefresher($(this)).load(); + }); + +}).call(this); +(function() { + var app; + + app = window.App || (window.App = {}); + + app.Spinner = (function() { + function Spinner() {} + + Spinner.prototype.show = function(button) { + button.prop('disable', true).addClass('disabled'); + button.siblings('.spinner').show(); + return button.find('.spinner').show(); + }; + + Spinner.prototype.hide = function(button) { + button.prop('disable', false).removeClass('disabled'); + button.siblings('.spinner').hide(); + return button.find('.spinner').hide(); + }; + + Spinner.prototype.bind = function() { + var self; + self = this; + $(document).on('ajax:beforeSend', '[data-spin]', function() { + return self.show($(this)); + }); + return $(document).on('ajax:complete', '[data-spin]', function() { + return self.hide($(this)); + }); + }; + + return Spinner; + + })(); + + new app.Spinner().bind(); + +}).call(this); +(function() { + var app, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + app = window.App || (window.App = {}); + + app.WorkItemAutocomplete = (function(superClass) { + extend(WorkItemAutocomplete, superClass); + + function WorkItemAutocomplete() { + return WorkItemAutocomplete.__super__.constructor.apply(this, arguments); + } + + WorkItemAutocomplete.prototype.onItemAdd = function(value, item) { + var billable, meal_compensation; + billable = item.attr('data-billable') === 'true'; + meal_compensation = item.attr('data-meal_compensation') === 'true'; + $('#ordertime_billable').prop('checked', billable); + return $('#ordertime_meal_compensation').prop('checked', meal_compensation); + }; + + WorkItemAutocomplete.prototype.renderOption = function(item, escape) { + return "
" + ("
" + (escape(item.path_shortnames)) + "
") + ("
" + (escape(this.limitText(item.name, 70))) + "
") + ("
" + (escape(this.limitText(item.description || '', 120))) + "
") + "
"; + }; + + WorkItemAutocomplete.prototype.renderItem = function(item, escape) { + return ("
") + ((escape(item.path_shortnames)) + ": " + (escape(item.name)) + "
"); + }; + + return WorkItemAutocomplete; + + })(app.Autocomplete); + + $(document).on('turbolinks:load', function() { + return $('[data-autocomplete=work_item]').each(function(i, element) { + return new app.WorkItemAutocomplete().bind(element); + }); + }); + +}).call(this); +(function() { + window.nested_form_fields || (window.nested_form_fields = {}); + + nested_form_fields.bind_nested_forms_links = function() { + $('body').off("click", '.add_nested_fields_link'); + $('body').on('click', '.add_nested_fields_link', function(event, additional_data) { + var $child_templates, $link, $parsed_template, $template, added_index, association_path, index_placeholder, object_class, target, template_html; + $link = $(this); + object_class = $link.data('object-class'); + association_path = $link.data('association-path'); + added_index = $(".nested_" + association_path).length; + $.event.trigger("fields_adding.nested_form_fields", { + object_class: object_class, + added_index: added_index, + association_path: association_path, + additional_data: additional_data + }); + if ($link.data('scope')) { + $template = $(($link.data('scope')) + " #" + association_path + "_template"); + } else { + $template = $("#" + association_path + "_template"); + } + target = $link.data('insert-into'); + template_html = $template.html(); + index_placeholder = "__" + association_path + "_index__"; + template_html = template_html.replace(new RegExp(index_placeholder, "g"), added_index); + template_html = template_html.replace(new RegExp("__nested_field_for_replace_with_index__", "g"), added_index); + $parsed_template = $(template_html); + $child_templates = $parsed_template.closestChild('.form_template'); + $child_templates.each(function() { + var $child; + $child = $(this); + return $child.replaceWith($("