From 138fab798df2776e2c61347357aa9dc85348d1bd Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Thu, 4 Jul 2024 11:55:11 +0200
Subject: [PATCH 01/37] Add first approach for fetching people from ptime API

---
 Gemfile.lock                       |  8 --------
 app/helpers/person_helper.rb       | 20 ++++++++++++++++++++
 app/views/people/_search.html.haml |  2 +-
 bin/delayed_job                    |  5 -----
 config/application.rb              |  2 --
 5 files changed, 21 insertions(+), 16 deletions(-)
 delete mode 100755 bin/delayed_job

diff --git a/Gemfile.lock b/Gemfile.lock
index 22c5b1e14..53b350008 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -125,7 +125,6 @@ GEM
     crass (1.0.6)
     cssbundling-rails (1.4.0)
       railties (>= 6.0.0)
-    daemons (1.4.1)
     database_cleaner (2.0.2)
       database_cleaner-active_record (>= 2, < 3)
     database_cleaner-active_record (2.1.0)
@@ -134,11 +133,6 @@ GEM
     database_cleaner-core (2.0.1)
     date (3.3.4)
     deep_merge (1.2.2)
-    delayed_job (4.1.11)
-      activesupport (>= 3.0, < 8.0)
-    delayed_job_active_record (4.1.8)
-      activerecord (>= 3.0, < 8.0)
-      delayed_job (>= 3.0, < 5)
     devise (4.9.4)
       bcrypt (~> 3.0)
       orm_adapter (~> 0.1)
@@ -484,9 +478,7 @@ DEPENDENCIES
   config
   countries
   cssbundling-rails
-  daemons
   database_cleaner
-  delayed_job_active_record
   devise
   dotenv
   faker
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 6d2bcd6b3..a75fe1e5e 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -76,4 +76,24 @@ def not_rated_default_skills(person)
                         certificate: false, core_competence: false })
     end
   end
+
+  def fetch_ptime_employees
+    ptime_employees = Ptime::Client.new.get('employees', { per_page: 1000 })['data']
+    ptime_employee_id_map = Person.take(3).map do |p|
+      { p.ptime_employee_id.to_s => p.id }
+    end.reduce({}, :merge)
+    ptime_employees_dropdown_data = []
+    ptime_employees.each do |ptime_employee|
+      ptime_employee_name = append_ptime_employee_name(ptime_employee)
+      person_id = ptime_employee_id_map[ptime_employee['id']]
+      ptime_employees_dropdown_data << [person_id, ptime_employee_name]
+    end
+    ptime_employees_dropdown_data
+  end
+
+  def append_ptime_employee_name(ptime_employee)
+    ptime_employee_firstname = ptime_employee['attributes']['firstname']
+    ptime_employee_lastname = ptime_employee['attributes']['lastname']
+    "#{ptime_employee_firstname} #{ptime_employee_lastname}"
+  end
 end
diff --git a/app/views/people/_search.html.haml b/app/views/people/_search.html.haml
index be4edee70..d25fb849b 100644
--- a/app/views/people/_search.html.haml
+++ b/app/views/people/_search.html.haml
@@ -1,7 +1,7 @@
 %div.d-flex.align-items-center.justify-content-between
   %div.d-flex.col-9.gap-3
     %span.col-6{"data-controller": "dropdown"}
-      = collection_select :person_id, :person, Person.all.sort_by {|p| p.name.downcase }, :id, :name, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
+      = collection_select :person_id, :person, fetch_ptime_employees, :first, :last, {}, {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
     %div.d-flex.align-items-center.text-gray
       = "#{t 'profile.updated_at'}: #{@person&.last_updated_at.strftime("%d.%m.%Y")}" if @person&.last_updated_at
   %a.d-flex.justify-content-between#new-person-button{href: "/people/new"}
diff --git a/bin/delayed_job b/bin/delayed_job
deleted file mode 100755
index edf195985..000000000
--- a/bin/delayed_job
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env ruby
-
-require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
-require 'delayed/command'
-Delayed::Command.new(ARGV).daemonize
diff --git a/config/application.rb b/config/application.rb
index 0a6ebec84..dee2a1781 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -37,7 +37,5 @@ class Application < Rails::Application
 
     config.assets.enabled = true
     config.assets.paths << Rails.root.join("uploads")
-
-    config.active_job.queue_adapter = :delayed_job
   end
 end

From 7f6abdd1511ca6548767b76379e827bbe02eef42 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Thu, 4 Jul 2024 15:49:46 +0200
Subject: [PATCH 02/37] Add helper and write test for fetching employee data

---
 spec/features/people_spec.rb  | 78 +++++++++++++++++++++++++++++++++++
 spec/support/ptime_helpers.rb |  0
 2 files changed, 78 insertions(+)
 create mode 100644 spec/support/ptime_helpers.rb

diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index 32942bfbc..c9bfc3f2e 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -2,6 +2,61 @@
 
 describe :people do
   describe 'People Search', type: :feature, js: true do
+    employees_json = {
+    'data': [
+      {
+        'id': 33,
+        'type': 'employee',
+        'attributes': {
+          'shortname': 'LSM',
+          'firstname': 'Longmax',
+          'lastname': 'Smith',
+          'email': 'longmax@example.com',
+          'marital_status': 'single',
+          'nationalities': [
+            'ZW'
+          ],
+          'graduation': 'BSc in Architecture',
+          'department_shortname': 'SYS',
+          'employment_roles': []
+        }
+      },
+      {
+        'id': 21,
+        'type': 'employee',
+        'attributes': {
+          'shortname': 'AMA',
+          'firstname': 'Alice',
+          'lastname': 'Mante',
+          'email': 'alice@example.com',
+          'marital_status': 'single',
+          'nationalities': [
+            'AU'
+          ],
+          'graduation': 'MSc in writing',
+          'department_shortname': 'SYS',
+          'employment_roles': []
+        }
+      },
+      {
+        'id': 45,
+        'type': 'employee',
+        'attributes': {
+          'shortname': 'CFO',
+          'firstname': 'Charlie',
+          'lastname': 'Ford',
+          'email': 'charlie@example.com',
+          'marital_status': 'married',
+          'nationalities': [
+            'GB'
+          ],
+          'graduation': 'MSc in networking',
+          'department_shortname': 'SYS',
+          'employment_roles': []
+        }
+      },
+    ]
+  }.to_json
 
     before(:each) do
       sign_in auth_users(:user), scope: :auth_user
@@ -49,6 +104,29 @@
         expect(page).to have_no_text(name)
       end
     end
+
+    it 'should redirect to correct person ' do
+      visit people_path
+      longmax = people(:longmax)
+      alice = people(:alice)
+      charlie = people(:charlie)
+
+      longmax.ptime_employee_id = 33
+      alice.ptime_employee_id = 21
+      charlie.ptime_employee_id = 45
+      longmax.save!
+      alice.save!
+      charlie.save!
+
+      stub_request(:get, "#{ptime_base_test_url}/api/v1/employees?per_page=1000").
+      to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
+                                                                 .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
+
+      fetch_ptime_employees
+
+      select_from_slim_select("#person_id_person", longmax.name)
+      expect(page).to have_current_path(person_path(longmax))
+    end
   end
 
   def common_languages_translated
diff --git a/spec/support/ptime_helpers.rb b/spec/support/ptime_helpers.rb
new file mode 100644
index 000000000..e69de29bb

From ea3d4e56ebd3374d8b6e95d4b6cb74cc23b1c6ca Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Fri, 5 Jul 2024 09:50:22 +0200
Subject: [PATCH 03/37] Finish test and fix logic in displaying people

---
 app/helpers/person_helper.rb       |  4 ++--
 app/views/people/_search.html.haml |  2 +-
 spec/features/people_spec.rb       | 28 +++++++++++++---------------
 3 files changed, 16 insertions(+), 18 deletions(-)

diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index a75fe1e5e..ce4d8a4fe 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -79,13 +79,13 @@ def not_rated_default_skills(person)
 
   def fetch_ptime_employees
     ptime_employees = Ptime::Client.new.get('employees', { per_page: 1000 })['data']
-    ptime_employee_id_map = Person.take(3).map do |p|
+    ptime_employee_id_map = Person.order(:name).all.map do |p|
       { p.ptime_employee_id.to_s => p.id }
     end.reduce({}, :merge)
     ptime_employees_dropdown_data = []
     ptime_employees.each do |ptime_employee|
       ptime_employee_name = append_ptime_employee_name(ptime_employee)
-      person_id = ptime_employee_id_map[ptime_employee['id']]
+      person_id = ptime_employee_id_map[ptime_employee['id'].to_s]
       ptime_employees_dropdown_data << [person_id, ptime_employee_name]
     end
     ptime_employees_dropdown_data
diff --git a/app/views/people/_search.html.haml b/app/views/people/_search.html.haml
index d25fb849b..e52108dbd 100644
--- a/app/views/people/_search.html.haml
+++ b/app/views/people/_search.html.haml
@@ -1,7 +1,7 @@
 %div.d-flex.align-items-center.justify-content-between
   %div.d-flex.col-9.gap-3
     %span.col-6{"data-controller": "dropdown"}
-      = collection_select :person_id, :person, fetch_ptime_employees, :first, :last, {}, {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
+      = collection_select :person_id, :person, fetch_ptime_employees, :first, :last, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
     %div.d-flex.align-items-center.text-gray
       = "#{t 'profile.updated_at'}: #{@person&.last_updated_at.strftime("%d.%m.%Y")}" if @person&.last_updated_at
   %a.d-flex.justify-content-between#new-person-button{href: "/people/new"}
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index c9bfc3f2e..3f4f87080 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -1,5 +1,12 @@
 require 'rails_helper'
 
+ptime_base_test_url = "www.ptime.example.com"
+ptime_api_test_username = "test username"
+ptime_api_test_password = "test password"
+ENV["PTIME_BASE_URL"] = ptime_base_test_url
+ENV["PTIME_API_USERNAME"] = ptime_api_test_username
+ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
+
 describe :people do
   describe 'People Search', type: :feature, js: true do
     employees_json = {
@@ -106,26 +113,17 @@
     end
 
     it 'should redirect to correct person ' do
-      visit people_path
-      longmax = people(:longmax)
       alice = people(:alice)
-      charlie = people(:charlie)
-
-      longmax.ptime_employee_id = 33
       alice.ptime_employee_id = 21
-      charlie.ptime_employee_id = 45
-      longmax.save!
       alice.save!
-      charlie.save!
-
-      stub_request(:get, "#{ptime_base_test_url}/api/v1/employees?per_page=1000").
+      stub_request(:get, "http://#{ptime_base_test_url}/api/v1/employees?per_page=1000").
       to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
                                                                  .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
-
-      fetch_ptime_employees
-
-      select_from_slim_select("#person_id_person", longmax.name)
-      expect(page).to have_current_path(person_path(longmax))
+      visit people_path
+      select_from_slim_select("#person_id_person", alice.name)
+      require "pry"; binding.pry
+      expect(page).to have_current_path(person_path(alice))
+      expect(page).to have_css('.ss-single', text: 'Alice Mante')
     end
   end
 

From 3a6b7c0a38e6c8b9e4bd2073b5a1cd553b066b8b Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Fri, 5 Jul 2024 15:47:41 +0200
Subject: [PATCH 04/37] Implement correct routing of person and fix a problem
 in the docker setup with yminder

---
 app/helpers/person_helper.rb                  |  10 +-
 .../controllers/dropdown_controller.js        |   2 +-
 app/views/people/_search.html.haml            |   3 +-
 docker-compose.yml                            |   2 +-
 package.json                                  |   5 +-
 spec/features/people_spec.rb                  |   4 +
 yarn.lock                                     | 301 +++++++++---------
 7 files changed, 175 insertions(+), 152 deletions(-)

diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index ce4d8a4fe..b181f3019 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -79,14 +79,20 @@ def not_rated_default_skills(person)
 
   def fetch_ptime_employees
     ptime_employees = Ptime::Client.new.get('employees', { per_page: 1000 })['data']
+    # employed_employees = ptime_employees.select { |employee| employee['is_employed'] }
     ptime_employee_id_map = Person.order(:name).all.map do |p|
       { p.ptime_employee_id.to_s => p.id }
     end.reduce({}, :merge)
     ptime_employees_dropdown_data = []
-    ptime_employees.each do |ptime_employee|
+    ptime_employee_ids = Person.all.pluck(:ptime_employee_id)
+    ptime_employees.each do |ptime_employee| # Replace 'ptime_employees' with 'employed_employees'
       ptime_employee_name = append_ptime_employee_name(ptime_employee)
       person_id = ptime_employee_id_map[ptime_employee['id'].to_s]
-      ptime_employees_dropdown_data << [person_id, ptime_employee_name]
+      ptime_employees_dropdown_data << {id: person_id,
+                                        ptime_employee_id: ptime_employee['id'],
+                                        name: ptime_employee_name,
+                                        already_exists: ptime_employee['id'].in?(ptime_employee_ids)
+                                      }
     end
     ptime_employees_dropdown_data
   end
diff --git a/app/javascript/controllers/dropdown_controller.js b/app/javascript/controllers/dropdown_controller.js
index 5639ce0ac..370e15e4c 100644
--- a/app/javascript/controllers/dropdown_controller.js
+++ b/app/javascript/controllers/dropdown_controller.js
@@ -11,6 +11,6 @@ export default class extends Controller {
   }
 
   handleChange(event) {
-      window.location.href = event.target.dataset.value + event.target.value;
+    window.location.href = event.target.dataset.value + event.target.value;
   }
 }
diff --git a/app/views/people/_search.html.haml b/app/views/people/_search.html.haml
index e52108dbd..28398c39e 100644
--- a/app/views/people/_search.html.haml
+++ b/app/views/people/_search.html.haml
@@ -1,7 +1,8 @@
 %div.d-flex.align-items-center.justify-content-between
   %div.d-flex.col-9.gap-3
     %span.col-6{"data-controller": "dropdown"}
-      = collection_select :person_id, :person, fetch_ptime_employees, :first, :last, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
+      = select :person_id, :person, options_for_select(fetch_ptime_employees.map { |p| [ p[:name], p[:already_exists] ? p[:id] : "new?ptime_employee_id=#{p[:ptime_employee_id]}" ] }), {}, {data: { "dropdown-target" => "dropdown", action: "change->dropdown#handleChange", value: "/people/" }}
+      -# = collection_select :person_id, :person, fetch_ptime_employees, :first, :last, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
     %div.d-flex.align-items-center.text-gray
       = "#{t 'profile.updated_at'}: #{@person&.last_updated_at.strftime("%d.%m.%Y")}" if @person&.last_updated_at
   %a.d-flex.justify-content-between#new-person-button{href: "/people/new"}
diff --git a/docker-compose.yml b/docker-compose.yml
index 8568cb03a..b3e7e2063 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -41,7 +41,7 @@ services:
      /bin/bash -c "
       curl -fsSL https://deb.nodesource.com/setup_18.x | bash - &&
       apt-get install -y nodejs &&
-      npm install -g yarn && bin/assets &&
+      npm install -g yarn && yarn add nodemon esbuild add && bin/assets &&
       sleep infinity"
     volumes:
       - ./:/myapp
diff --git a/package.json b/package.json
index 41be726a2..bc5fe6873 100644
--- a/package.json
+++ b/package.json
@@ -5,11 +5,12 @@
     "@hotwired/stimulus": "^3.2.2",
     "@hotwired/turbo-rails": "^7.3.0",
     "@popperjs/core": "^2.11.8",
+    "add": "^2.0.6",
     "autoprefixer": "^10.4.17",
     "bootstrap": "^5.3.2",
     "bootstrap-icons": "^1.11.3",
-    "esbuild": "^0.20.0",
-    "nodemon": "^3.0.3",
+    "esbuild": "^0.23.0",
+    "nodemon": "^3.1.4",
     "postcss": "^8.4.33",
     "postcss-cli": "^11.0.0",
     "sass": "^1.70.0",
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index 3f4f87080..e94f65f90 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -35,6 +35,7 @@
           'shortname': 'AMA',
           'firstname': 'Alice',
           'lastname': 'Mante',
+          'full_name': 'Alice Mante',
           'email': 'alice@example.com',
           'marital_status': 'single',
           'nationalities': [
@@ -43,6 +44,9 @@
           'graduation': 'MSc in writing',
           'department_shortname': 'SYS',
           'employment_roles': []
+          'is_employed': false,
+          'birthdate': '01.04.2001'
+          'location': 'Bern'
         }
       },
       {
diff --git a/yarn.lock b/yarn.lock
index 00836deb0..00ff6b4cc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,120 +2,125 @@
 # yarn lockfile v1
 
 
-"@esbuild/aix-ppc64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz#eafa8775019b3650a77e8310ba4dbd17ca7af6d5"
-  integrity sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==
-
-"@esbuild/android-arm64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz#68791afa389550736f682c15b963a4f37ec2f5f6"
-  integrity sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==
-
-"@esbuild/android-arm@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.1.tgz#38c91d8ee8d5196f7fbbdf4f0061415dde3a473a"
-  integrity sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==
-
-"@esbuild/android-x64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.1.tgz#93f6190ce997b313669c20edbf3645fc6c8d8f22"
-  integrity sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==
-
-"@esbuild/darwin-arm64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz#0d391f2e81fda833fe609182cc2fbb65e03a3c46"
-  integrity sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==
-
-"@esbuild/darwin-x64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz#92504077424584684862f483a2242cfde4055ba2"
-  integrity sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==
-
-"@esbuild/freebsd-arm64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz#a1646fa6ba87029c67ac8a102bb34384b9290774"
-  integrity sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==
-
-"@esbuild/freebsd-x64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz#41c9243ab2b3254ea7fb512f71ffdb341562e951"
-  integrity sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==
-
-"@esbuild/linux-arm64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz#f3c1e1269fbc9eedd9591a5bdd32bf707a883156"
-  integrity sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==
-
-"@esbuild/linux-arm@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz#4503ca7001a8ee99589c072801ce9d7540717a21"
-  integrity sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==
-
-"@esbuild/linux-ia32@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz#98c474e3e0cbb5bcbdd8561a6e65d18f5767ce48"
-  integrity sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==
-
-"@esbuild/linux-loong64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz#a8097d28d14b9165c725fe58fc438f80decd2f33"
-  integrity sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==
-
-"@esbuild/linux-mips64el@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz#c44f6f0d7d017c41ad3bb15bfdb69b690656b5ea"
-  integrity sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==
-
-"@esbuild/linux-ppc64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz#0765a55389a99237b3c84227948c6e47eba96f0d"
-  integrity sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==
-
-"@esbuild/linux-riscv64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz#e4153b032288e3095ddf4c8be07893781b309a7e"
-  integrity sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==
-
-"@esbuild/linux-s390x@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz#b9ab8af6e4b73b26d63c1c426d7669a5d53eb5a7"
-  integrity sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==
-
-"@esbuild/linux-x64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz#0b25da17ac38c3e11cdd06ca3691d4d6bef2755f"
-  integrity sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==
-
-"@esbuild/netbsd-x64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz#3148e48406cd0d4f7ba1e0bf3f4d77d548c98407"
-  integrity sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==
-
-"@esbuild/openbsd-x64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz#7b73e852986a9750192626d377ac96ac2b749b76"
-  integrity sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==
-
-"@esbuild/sunos-x64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz#402a441cdac2eee98d8be378c7bc23e00c1861c5"
-  integrity sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==
-
-"@esbuild/win32-arm64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz#36c4e311085806a6a0c5fc54d1ac4d7b27e94d7b"
-  integrity sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==
-
-"@esbuild/win32-ia32@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz#0cf933be3fb9dc58b45d149559fe03e9e22b54fe"
-  integrity sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==
-
-"@esbuild/win32-x64@0.20.1":
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz#77583b6ea54cee7c1410ebbd54051b6a3fcbd8ba"
-  integrity sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==
+"@esbuild/aix-ppc64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz#145b74d5e4a5223489cabdc238d8dad902df5259"
+  integrity sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==
+
+"@esbuild/android-arm64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz#453bbe079fc8d364d4c5545069e8260228559832"
+  integrity sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==
+
+"@esbuild/android-arm@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.0.tgz#26c806853aa4a4f7e683e519cd9d68e201ebcf99"
+  integrity sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==
+
+"@esbuild/android-x64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.0.tgz#1e51af9a6ac1f7143769f7ee58df5b274ed202e6"
+  integrity sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==
+
+"@esbuild/darwin-arm64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz#d996187a606c9534173ebd78c58098a44dd7ef9e"
+  integrity sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==
+
+"@esbuild/darwin-x64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz#30c8f28a7ef4e32fe46501434ebe6b0912e9e86c"
+  integrity sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==
+
+"@esbuild/freebsd-arm64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz#30f4fcec8167c08a6e8af9fc14b66152232e7fb4"
+  integrity sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==
+
+"@esbuild/freebsd-x64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz#1003a6668fe1f5d4439e6813e5b09a92981bc79d"
+  integrity sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==
+
+"@esbuild/linux-arm64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz#3b9a56abfb1410bb6c9138790f062587df3e6e3a"
+  integrity sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==
+
+"@esbuild/linux-arm@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz#237a8548e3da2c48cd79ae339a588f03d1889aad"
+  integrity sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==
+
+"@esbuild/linux-ia32@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz#4269cd19cb2de5de03a7ccfc8855dde3d284a238"
+  integrity sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==
+
+"@esbuild/linux-loong64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz#82b568f5658a52580827cc891cb69d2cb4f86280"
+  integrity sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==
+
+"@esbuild/linux-mips64el@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz#9a57386c926262ae9861c929a6023ed9d43f73e5"
+  integrity sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==
+
+"@esbuild/linux-ppc64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz#f3a79fd636ba0c82285d227eb20ed8e31b4444f6"
+  integrity sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==
+
+"@esbuild/linux-riscv64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz#f9d2ef8356ce6ce140f76029680558126b74c780"
+  integrity sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==
+
+"@esbuild/linux-s390x@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz#45390f12e802201f38a0229e216a6aed4351dfe8"
+  integrity sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==
+
+"@esbuild/linux-x64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz#c8409761996e3f6db29abcf9b05bee8d7d80e910"
+  integrity sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==
+
+"@esbuild/netbsd-x64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz#ba70db0114380d5f6cfb9003f1d378ce989cd65c"
+  integrity sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==
+
+"@esbuild/openbsd-arm64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz#72fc55f0b189f7a882e3cf23f332370d69dfd5db"
+  integrity sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==
+
+"@esbuild/openbsd-x64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz#b6ae7a0911c18fe30da3db1d6d17a497a550e5d8"
+  integrity sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==
+
+"@esbuild/sunos-x64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz#58f0d5e55b9b21a086bfafaa29f62a3eb3470ad8"
+  integrity sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==
+
+"@esbuild/win32-arm64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz#b858b2432edfad62e945d5c7c9e5ddd0f528ca6d"
+  integrity sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==
+
+"@esbuild/win32-ia32@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz#167ef6ca22a476c6c0c014a58b4f43ae4b80dec7"
+  integrity sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==
+
+"@esbuild/win32-x64@0.23.0":
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz#db44a6a08520b5f25bbe409f34a59f2d4bcc7ced"
+  integrity sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==
 
 "@hotwired/stimulus@^3.2.2":
   version "3.2.2"
@@ -176,6 +181,11 @@ abbrev@1:
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
   integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
 
+add@^2.0.6:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/add/-/add-2.0.6.tgz#248f0a9f6e5a528ef2295dbeec30532130ae2235"
+  integrity sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q==
+
 ansi-regex@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
@@ -321,34 +331,35 @@ emoji-regex@^8.0.0:
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
   integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
 
-esbuild@^0.20.0:
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.1.tgz#1e4cbb380ad1959db7609cb9573ee77257724a3e"
-  integrity sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==
+esbuild@^0.23.0:
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.0.tgz#de06002d48424d9fdb7eb52dbe8e95927f852599"
+  integrity sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==
   optionalDependencies:
-    "@esbuild/aix-ppc64" "0.20.1"
-    "@esbuild/android-arm" "0.20.1"
-    "@esbuild/android-arm64" "0.20.1"
-    "@esbuild/android-x64" "0.20.1"
-    "@esbuild/darwin-arm64" "0.20.1"
-    "@esbuild/darwin-x64" "0.20.1"
-    "@esbuild/freebsd-arm64" "0.20.1"
-    "@esbuild/freebsd-x64" "0.20.1"
-    "@esbuild/linux-arm" "0.20.1"
-    "@esbuild/linux-arm64" "0.20.1"
-    "@esbuild/linux-ia32" "0.20.1"
-    "@esbuild/linux-loong64" "0.20.1"
-    "@esbuild/linux-mips64el" "0.20.1"
-    "@esbuild/linux-ppc64" "0.20.1"
-    "@esbuild/linux-riscv64" "0.20.1"
-    "@esbuild/linux-s390x" "0.20.1"
-    "@esbuild/linux-x64" "0.20.1"
-    "@esbuild/netbsd-x64" "0.20.1"
-    "@esbuild/openbsd-x64" "0.20.1"
-    "@esbuild/sunos-x64" "0.20.1"
-    "@esbuild/win32-arm64" "0.20.1"
-    "@esbuild/win32-ia32" "0.20.1"
-    "@esbuild/win32-x64" "0.20.1"
+    "@esbuild/aix-ppc64" "0.23.0"
+    "@esbuild/android-arm" "0.23.0"
+    "@esbuild/android-arm64" "0.23.0"
+    "@esbuild/android-x64" "0.23.0"
+    "@esbuild/darwin-arm64" "0.23.0"
+    "@esbuild/darwin-x64" "0.23.0"
+    "@esbuild/freebsd-arm64" "0.23.0"
+    "@esbuild/freebsd-x64" "0.23.0"
+    "@esbuild/linux-arm" "0.23.0"
+    "@esbuild/linux-arm64" "0.23.0"
+    "@esbuild/linux-ia32" "0.23.0"
+    "@esbuild/linux-loong64" "0.23.0"
+    "@esbuild/linux-mips64el" "0.23.0"
+    "@esbuild/linux-ppc64" "0.23.0"
+    "@esbuild/linux-riscv64" "0.23.0"
+    "@esbuild/linux-s390x" "0.23.0"
+    "@esbuild/linux-x64" "0.23.0"
+    "@esbuild/netbsd-x64" "0.23.0"
+    "@esbuild/openbsd-arm64" "0.23.0"
+    "@esbuild/openbsd-x64" "0.23.0"
+    "@esbuild/sunos-x64" "0.23.0"
+    "@esbuild/win32-arm64" "0.23.0"
+    "@esbuild/win32-ia32" "0.23.0"
+    "@esbuild/win32-x64" "0.23.0"
 
 escalade@^3.1.1:
   version "3.1.2"
@@ -543,10 +554,10 @@ node-releases@^2.0.14:
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
   integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
 
-nodemon@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.3.tgz#244a62d1c690eece3f6165c6cdb0db03ebd80b76"
-  integrity sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==
+nodemon@^3.1.4:
+  version "3.1.4"
+  resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.4.tgz#c34dcd8eb46a05723ccde60cbdd25addcc8725e4"
+  integrity sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==
   dependencies:
     chokidar "^3.5.2"
     debug "^4"

From c25e18c8f0253f4e87bcd6f18554bcab329285de Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Mon, 8 Jul 2024 11:37:27 +0200
Subject: [PATCH 05/37] Possibly fix docker compose file

---
 docker-compose.yml | 2 +-
 package.json       | 1 -
 yarn.lock          | 5 -----
 3 files changed, 1 insertion(+), 7 deletions(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index b3e7e2063..c54c7c2ce 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -41,7 +41,7 @@ services:
      /bin/bash -c "
       curl -fsSL https://deb.nodesource.com/setup_18.x | bash - &&
       apt-get install -y nodejs &&
-      npm install -g yarn && yarn add nodemon esbuild add && bin/assets &&
+      npm install -g yarn && yarn add nodemon esbuild && bin/assets &&
       sleep infinity"
     volumes:
       - ./:/myapp
diff --git a/package.json b/package.json
index bc5fe6873..1316e6270 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,6 @@
     "@hotwired/stimulus": "^3.2.2",
     "@hotwired/turbo-rails": "^7.3.0",
     "@popperjs/core": "^2.11.8",
-    "add": "^2.0.6",
     "autoprefixer": "^10.4.17",
     "bootstrap": "^5.3.2",
     "bootstrap-icons": "^1.11.3",
diff --git a/yarn.lock b/yarn.lock
index 00ff6b4cc..1c57f5884 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -181,11 +181,6 @@ abbrev@1:
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
   integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
 
-add@^2.0.6:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/add/-/add-2.0.6.tgz#248f0a9f6e5a528ef2295dbeec30532130ae2235"
-  integrity sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q==
-
 ansi-regex@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"

From 4385c4ac5779f0149dc96a7230c4c6b4a81cd821 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Mon, 8 Jul 2024 14:58:59 +0200
Subject: [PATCH 06/37] Fix tests and implement logic for sorting people

---
 app/views/people/_search.html.haml |   3 +-
 spec/features/people_spec.rb       | 109 ++++++++++++++---------------
 2 files changed, 53 insertions(+), 59 deletions(-)

diff --git a/app/views/people/_search.html.haml b/app/views/people/_search.html.haml
index 28398c39e..746cba0ab 100644
--- a/app/views/people/_search.html.haml
+++ b/app/views/people/_search.html.haml
@@ -1,7 +1,8 @@
 %div.d-flex.align-items-center.justify-content-between
   %div.d-flex.col-9.gap-3
     %span.col-6{"data-controller": "dropdown"}
-      = select :person_id, :person, options_for_select(fetch_ptime_employees.map { |p| [ p[:name], p[:already_exists] ? p[:id] : "new?ptime_employee_id=#{p[:ptime_employee_id]}" ] }), {}, {data: { "dropdown-target" => "dropdown", action: "change->dropdown#handleChange", value: "/people/" }}
+      - sorted_employees = fetch_ptime_employees.sort_by { |p| p[:name] }
+      = select :person_id, :person, options_for_select(sorted_employees.map { |p| [ p[:name], p[:already_exists] ? p[:id] : "new?ptime_employee_id=#{p[:ptime_employee_id]}" ] }), {}, {data: { "dropdown-target" => "dropdown", action: "change->dropdown#handleChange", value: "/people/" }}
       -# = collection_select :person_id, :person, fetch_ptime_employees, :first, :last, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
     %div.d-flex.align-items-center.text-gray
       = "#{t 'profile.updated_at'}: #{@person&.last_updated_at.strftime("%d.%m.%Y")}" if @person&.last_updated_at
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index e94f65f90..b9d022776 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -10,64 +10,58 @@
 describe :people do
   describe 'People Search', type: :feature, js: true do
     employees_json = {
-    'data': [
-      {
-        'id': 33,
-        'type': 'employee',
-        'attributes': {
-          'shortname': 'LSM',
-          'firstname': 'Longmax',
-          'lastname': 'Smith',
-          'email': 'longmax@example.com',
-          'marital_status': 'single',
-          'nationalities': [
-            'ZW'
-          ],
-          'graduation': 'BSc in Architecture',
-          'department_shortname': 'SYS',
-          'employment_roles': []
+      'data' => [
+        {
+          'id' => 33,
+          'type' => 'employee',
+          'attributes' => {
+            'shortname' => 'LSM',
+            'firstname' => 'Longmax',
+            'lastname' => 'Smith',
+            'email' => 'longmax@example.com',
+            'marital_status' => 'single',
+            'nationalities' => ['ZW'],
+            'graduation' => 'BSc in Architecture',
+            'department_shortname' => 'SYS',
+            'employment_roles' => []
+          }
+        },
+        {
+          'id' => 21,
+          'type' => 'employee',
+          'attributes' => {
+            'shortname' => 'AMA',
+            'firstname' => 'Alice',
+            'lastname' => 'Mante',
+            'full_name' => 'Alice Mante',
+            'email' => 'alice@example.com',
+            'marital_status' => 'single',
+            'nationalities' => ['AU'],
+            'graduation' => 'MSc in writing',
+            'department_shortname' => 'SYS',
+            'employment_roles' => [],
+            'is_employed' => false,
+            'birthdate' => '01.04.2001',
+            'location' => 'Bern'
+          }
+        },
+        {
+          'id' => 45,
+          'type' => 'employee',
+          'attributes' => {
+            'shortname' => 'CFO',
+            'firstname' => 'Charlie',
+            'lastname' => 'Ford',
+            'email' => 'charlie@example.com',
+            'marital_status' => 'married',
+            'nationalities' => ['GB'],
+            'graduation' => 'MSc in networking',
+            'department_shortname' => 'SYS',
+            'employment_roles' => []
+          }
         }
-      },
-      {
-        'id': 21,
-        'type': 'employee',
-        'attributes': {
-          'shortname': 'AMA',
-          'firstname': 'Alice',
-          'lastname': 'Mante',
-          'full_name': 'Alice Mante',
-          'email': 'alice@example.com',
-          'marital_status': 'single',
-          'nationalities': [
-            'AU'
-          ],
-          'graduation': 'MSc in writing',
-          'department_shortname': 'SYS',
-          'employment_roles': []
-          'is_employed': false,
-          'birthdate': '01.04.2001'
-          'location': 'Bern'
-        }
-      },
-      {
-        'id': 45,
-        'type': 'employee',
-        'attributes': {
-          'shortname': 'CFO',
-          'firstname': 'Charlie',
-          'lastname': 'Ford',
-          'email': 'charlie@example.com',
-          'marital_status': 'married',
-          'nationalities': [
-            'GB'
-          ],
-          'graduation': 'MSc in networking',
-          'department_shortname': 'SYS',
-          'employment_roles': []
-        }
-      },
-    ]
-  }.to_json
+      ]
+    }.to_json
 
     before(:each) do
       sign_in auth_users(:user), scope: :auth_user
@@ -125,7 +119,6 @@
                                                                  .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
       visit people_path
       select_from_slim_select("#person_id_person", alice.name)
-      require "pry"; binding.pry
       expect(page).to have_current_path(person_path(alice))
       expect(page).to have_css('.ss-single', text: 'Alice Mante')
     end

From 202006a9bd9508577557e0032c1a11b73f1982fc Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Tue, 9 Jul 2024 12:46:25 +0200
Subject: [PATCH 07/37] Implement logic for fallback and cleanup helper method

---
 app/domain/ptime/client.rb         |  5 +--
 app/helpers/person_helper.rb       | 51 +++++++++++++++++++-----------
 app/views/people/_search.html.haml |  8 +++--
 3 files changed, 41 insertions(+), 23 deletions(-)

diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index def0d8c5c..52356c92b 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -16,13 +16,14 @@ def get(endpoint, params = {})
     private
 
     def request(method, path, params = nil)
+      ENV['PTIME_API_ACCESSIBLE'] = 'true'
       path = "#{path}?#{URI.encode_www_form(params)}"
       response = RestClient.send(method, path, headers)
       JSON.parse(response.body)
-    rescue RestClient::BadRequest => e
+    rescue RestClient::ExceptionWithResponse => e
+      ENV['PTIME_API_ACCESSIBLE'] = 'false'
       msg = response_error_message(e)
       e.message = msg if msg.present?
-      raise e
     end
 
     def response_error_message(exception)
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index b181f3019..bfd49920c 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -78,28 +78,43 @@ def not_rated_default_skills(person)
   end
 
   def fetch_ptime_employees
-    ptime_employees = Ptime::Client.new.get('employees', { per_page: 1000 })['data']
-    # employed_employees = ptime_employees.select { |employee| employee['is_employed'] }
-    ptime_employee_id_map = Person.order(:name).all.map do |p|
-      { p.ptime_employee_id.to_s => p.id }
-    end.reduce({}, :merge)
-    ptime_employees_dropdown_data = []
-    ptime_employee_ids = Person.all.pluck(:ptime_employee_id)
-    ptime_employees.each do |ptime_employee| # Replace 'ptime_employees' with 'employed_employees'
+    ptime_employees = fetch_all_ptime_employees
+    ptime_employee_ids = fetch_all_ptime_employee_ids
+    build_dropdown_data(ptime_employees, ptime_employee_ids)
+  end
+
+  def fetch_all_ptime_employees
+    Ptime::Client.new.get('employees', { per_page: 1000 })['data']
+  end
+
+  def fetch_all_ptime_employee_ids
+    Person.all.pluck(:ptime_employee_id)
+  end
+
+  def build_dropdown_data(ptime_employees, ptime_employee_ids)
+    ptime_employees.map do |ptime_employee|
       ptime_employee_name = append_ptime_employee_name(ptime_employee)
-      person_id = ptime_employee_id_map[ptime_employee['id'].to_s]
-      ptime_employees_dropdown_data << {id: person_id,
-                                        ptime_employee_id: ptime_employee['id'],
-                                        name: ptime_employee_name,
-                                        already_exists: ptime_employee['id'].in?(ptime_employee_ids)
-                                      }
+      person_id = map_ptime_employee_id(ptime_employee)
+      {
+        id: person_id,
+        ptime_employee_id: ptime_employee['id'],
+        name: ptime_employee_name,
+        already_exists: ptime_employee['id'].in?(ptime_employee_ids)
+      }
     end
-    ptime_employees_dropdown_data
+  end
+
+  def map_ptime_employee_id(ptime_employee)
+    ptime_employee_id_map = Person.all.each_with_object({}) do |person, hash|
+      hash[person.ptime_employee_id.to_s] = person.id
+    end
+
+    ptime_employee_id_map[ptime_employee['id'].to_s]
   end
 
   def append_ptime_employee_name(ptime_employee)
-    ptime_employee_firstname = ptime_employee['attributes']['firstname']
-    ptime_employee_lastname = ptime_employee['attributes']['lastname']
-    "#{ptime_employee_firstname} #{ptime_employee_lastname}"
+    firstname = ptime_employee['attributes']['firstname']
+    lastname = ptime_employee['attributes']['lastname']
+    "#{firstname} #{lastname}"
   end
 end
diff --git a/app/views/people/_search.html.haml b/app/views/people/_search.html.haml
index 746cba0ab..f9f9e4da9 100644
--- a/app/views/people/_search.html.haml
+++ b/app/views/people/_search.html.haml
@@ -1,9 +1,11 @@
 %div.d-flex.align-items-center.justify-content-between
   %div.d-flex.col-9.gap-3
     %span.col-6{"data-controller": "dropdown"}
-      - sorted_employees = fetch_ptime_employees.sort_by { |p| p[:name] }
-      = select :person_id, :person, options_for_select(sorted_employees.map { |p| [ p[:name], p[:already_exists] ? p[:id] : "new?ptime_employee_id=#{p[:ptime_employee_id]}" ] }), {}, {data: { "dropdown-target" => "dropdown", action: "change->dropdown#handleChange", value: "/people/" }}
-      -# = collection_select :person_id, :person, fetch_ptime_employees, :first, :last, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
+      - if ENV['PTIME_API_ACCESSIBLE'] == 'true'
+        - sorted_employees = fetch_ptime_employees.sort_by { |p| p[:name] }
+        = select :person_id, :person, options_for_select(sorted_employees.map { |p| [ p[:name], p[:already_exists] ? p[:id] : "new?ptime_employee_id=#{p[:ptime_employee_id]}" ] }), {}, {data: { "dropdown-target" => "dropdown", action: "change->dropdown#handleChange", value: "/people/" }}
+      - else
+        = collection_select :person_id, :person, Person.all.sort_by {|p| p.name.downcase }, :id, :name, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
     %div.d-flex.align-items-center.text-gray
       = "#{t 'profile.updated_at'}: #{@person&.last_updated_at.strftime("%d.%m.%Y")}" if @person&.last_updated_at
   %a.d-flex.justify-content-between#new-person-button{href: "/people/new"}

From 1dc306579a88c5b492472a58734af4edef518c39 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Tue, 9 Jul 2024 15:43:42 +0200
Subject: [PATCH 08/37] Improve fallback logic by making it more readable and
 shorter

---
 app/controllers/people_controller.rb | 7 +++++++
 app/domain/ptime/client.rb           | 3 +--
 app/views/people/_search.html.haml   | 6 +++---
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/app/controllers/people_controller.rb b/app/controllers/people_controller.rb
index 6d1fed1b8..78e148cef 100644
--- a/app/controllers/people_controller.rb
+++ b/app/controllers/people_controller.rb
@@ -5,6 +5,8 @@ class PeopleController < CrudController
   include ParamConverters
   include PeopleControllerConcerns
 
+  helper_method :ptime_broken?
+
   self.permitted_attrs = [:birthdate, :location, :marital_status, :updated_by, :name, :nationality,
                           :nationality2, :title, :competence_notes, :company_id, :email,
                           :department_id, :shortname, :picture, :picture_cache,
@@ -69,4 +71,9 @@ def fetch_entries
   def person
     @person ||= Person.find(params[:person_id])
   end
+
+  def ptime_broken?
+    # Test what happens when ptime_accessible is nil
+    !ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE', true))
+  end
 end
diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index 52356c92b..ab3d55ffd 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -10,13 +10,12 @@ def initialize
     end
 
     def get(endpoint, params = {})
-      request(:get, @base_url + endpoint, params)
+      request(:get, @base_url + endpoint, params) unless ENV['PTIME_API_ACCESSIBLE']
     end
 
     private
 
     def request(method, path, params = nil)
-      ENV['PTIME_API_ACCESSIBLE'] = 'true'
       path = "#{path}?#{URI.encode_www_form(params)}"
       response = RestClient.send(method, path, headers)
       JSON.parse(response.body)
diff --git a/app/views/people/_search.html.haml b/app/views/people/_search.html.haml
index f9f9e4da9..a3cfcb51f 100644
--- a/app/views/people/_search.html.haml
+++ b/app/views/people/_search.html.haml
@@ -1,11 +1,11 @@
 %div.d-flex.align-items-center.justify-content-between
   %div.d-flex.col-9.gap-3
     %span.col-6{"data-controller": "dropdown"}
-      - if ENV['PTIME_API_ACCESSIBLE'] == 'true'
+      - if ptime_broken?
+        = collection_select :person_id, :person, Person.all.sort_by {|p| p.name.downcase }, :id, :name, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}        
+      - else
         - sorted_employees = fetch_ptime_employees.sort_by { |p| p[:name] }
         = select :person_id, :person, options_for_select(sorted_employees.map { |p| [ p[:name], p[:already_exists] ? p[:id] : "new?ptime_employee_id=#{p[:ptime_employee_id]}" ] }), {}, {data: { "dropdown-target" => "dropdown", action: "change->dropdown#handleChange", value: "/people/" }}
-      - else
-        = collection_select :person_id, :person, Person.all.sort_by {|p| p.name.downcase }, :id, :name, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}
     %div.d-flex.align-items-center.text-gray
       = "#{t 'profile.updated_at'}: #{@person&.last_updated_at.strftime("%d.%m.%Y")}" if @person&.last_updated_at
   %a.d-flex.justify-content-between#new-person-button{href: "/people/new"}

From 12677362eb19496706507c5e1bec4b1c4273b4ee Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Wed, 10 Jul 2024 13:42:01 +0200
Subject: [PATCH 09/37] Implement first approach on calling the ptime api after
 5 minutes

---
 app/domain/ptime/client.rb   |  3 +++
 app/helpers/person_helper.rb | 10 ++++++++++
 2 files changed, 13 insertions(+)

diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index ab3d55ffd..77860dbe9 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -21,8 +21,11 @@ def request(method, path, params = nil)
       JSON.parse(response.body)
     rescue RestClient::ExceptionWithResponse => e
       ENV['PTIME_API_ACCESSIBLE'] = 'false'
+      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
       msg = response_error_message(e)
       e.message = msg if msg.present?
+
+      get('employees', { per_page: 1000 })['data']
     end
 
     def response_error_message(exception)
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index bfd49920c..6bb90bffe 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -117,4 +117,14 @@ def append_ptime_employee_name(ptime_employee)
     lastname = ptime_employee['attributes']['lastname']
     "#{firstname} #{lastname}"
   end
+
+  def try_new_ptime_api_call
+    last_datetime = ENV['LAST_PTIME_API_REQUEST'].to_datetime
+    current_datetime = DateTime.now
+    difference_in_minutes = ((current_datetime - last_datetime) * 24 * 60).to_i
+
+    if difference_in_minutes >= 5
+      fetch_all_ptime_employees
+    end
+  end
 end

From 512593bad81f9a061a8746cf6228069e237e0e4a Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Thu, 11 Jul 2024 09:44:00 +0200
Subject: [PATCH 10/37] Improve logic of 5 minutes request and change client.rb

---
 app/domain/ptime/assign_employee_ids.rb |  2 +-
 app/domain/ptime/client.rb              | 44 ++++++++++++++++---------
 app/domain/ptime/update_people_data.rb  |  2 +-
 app/helpers/person_helper.rb            | 12 +------
 spec/domain/ptime/client_spec.rb        |  4 +--
 5 files changed, 34 insertions(+), 30 deletions(-)

diff --git a/app/domain/ptime/assign_employee_ids.rb b/app/domain/ptime/assign_employee_ids.rb
index ea4416952..c9ee84a0a 100644
--- a/app/domain/ptime/assign_employee_ids.rb
+++ b/app/domain/ptime/assign_employee_ids.rb
@@ -60,7 +60,7 @@ def map_employees(should_map)
 
     def fetch_data
       puts 'Fetching required data...'
-      @ptime_employees = Ptime::Client.new.get('employees', { per_page: 1000 })['data']
+      @ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })['data']
       @skills_people = Person.all
       puts 'Successfully fetched data'
     end
diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index 77860dbe9..f463d099b 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -9,25 +9,19 @@ def initialize
       @base_url = "#{ENV.fetch('PTIME_BASE_URL')}/api/v1/"
     end
 
-    def get(endpoint, params = {})
-      request(:get, @base_url + endpoint, params) unless ENV['PTIME_API_ACCESSIBLE']
+    def request(method, endpoint, params = {})
+      path = @base_url + endpoint
+      if ENV['PTIME_API_ACCESSIBLE'].nil? && last_ptime_api_request_more_than_5_minutes
+        send_request_and_parse_response(method, path, params)
+      else
+        skills_database_request
+      end
+    rescue RestClient::ExceptionWithResponse
+      skills_database_request
     end
 
     private
 
-    def request(method, path, params = nil)
-      path = "#{path}?#{URI.encode_www_form(params)}"
-      response = RestClient.send(method, path, headers)
-      JSON.parse(response.body)
-    rescue RestClient::ExceptionWithResponse => e
-      ENV['PTIME_API_ACCESSIBLE'] = 'false'
-      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
-      msg = response_error_message(e)
-      e.message = msg if msg.present?
-
-      get('employees', { per_page: 1000 })['data']
-    end
-
     def response_error_message(exception)
       JSON.parse(exception.response.body).dig('error', 'message')
     rescue JSON::ParserError # rescue only JSON parsing errors
@@ -45,5 +39,25 @@ def headers
     def basic_token
       Base64.encode64("#{ENV.fetch('PTIME_API_USERNAME')}:#{ENV.fetch('PTIME_API_PASSWORD')}")
     end
+
+    def last_ptime_api_request_more_than_5_minutes
+      last_request_time = ENV.fetch('LAST_PTIME_API_REQUEST', nil)
+      return true if last_request_time.nil?
+
+      last_request_time.to_datetime <= 5.minutes.ago
+    end
+
+    def send_request_and_parse_response(method, path, params)
+      path = "#{path}?#{URI.encode_www_form(params)}" if method == :get
+      response = RestClient.send(method, path, headers)
+      JSON.parse(response.body)
+    end
+
+    def skills_database_request
+      ENV['PTIME_API_ACCESSIBLE'] = 'false'
+      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
+
+      get('employees', { per_page: 1000 })['data']
+    end
   end
 end
diff --git a/app/domain/ptime/update_people_data.rb b/app/domain/ptime/update_people_data.rb
index db885ea87..24eb88558 100644
--- a/app/domain/ptime/update_people_data.rb
+++ b/app/domain/ptime/update_people_data.rb
@@ -7,7 +7,7 @@ class UpdatePeopleData
 
     # rubocop:disable Metrics
     def run
-      ptime_employees = Ptime::Client.new.get('employees', { per_page: 1000 })['data']
+      ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })['data']
       ptime_employees.each do |ptime_employee|
         ptime_employee_firstname = ptime_employee['attributes']['firstname']
         ptime_employee_lastname = ptime_employee['attributes']['lastname']
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 6bb90bffe..8c563123b 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -84,7 +84,7 @@ def fetch_ptime_employees
   end
 
   def fetch_all_ptime_employees
-    Ptime::Client.new.get('employees', { per_page: 1000 })['data']
+    Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })['data']
   end
 
   def fetch_all_ptime_employee_ids
@@ -117,14 +117,4 @@ def append_ptime_employee_name(ptime_employee)
     lastname = ptime_employee['attributes']['lastname']
     "#{firstname} #{lastname}"
   end
-
-  def try_new_ptime_api_call
-    last_datetime = ENV['LAST_PTIME_API_REQUEST'].to_datetime
-    current_datetime = DateTime.now
-    difference_in_minutes = ((current_datetime - last_datetime) * 24 * 60).to_i
-
-    if difference_in_minutes >= 5
-      fetch_all_ptime_employees
-    end
-  end
 end
diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index bdcc6b9c7..062d59c73 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -69,7 +69,7 @@
           to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
                                                                        .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
 
-        fetched_employees = Ptime::Client.new.get("employees")
+        fetched_employees = Ptime::Client.new.request(:get, "employees")
         expect(fetched_employees).to eq(JSON.parse(employees_json))
     end
 
@@ -80,6 +80,6 @@
           to_return(body: employees_json, status: 404)
           .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
 
-        expect{ Ptime::Client.new.get("") }.to raise_error(RestClient::NotFound)
+        expect{ Ptime::Client.new.request(:get, "") }.to raise_error(RestClient::NotFound)
     end
 end

From c9e982520724ab11f8445bfae2991c15d7630063 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Fri, 12 Jul 2024 09:50:11 +0200
Subject: [PATCH 11/37] Add logic to load site correctly and remove unnecessary
 code

---
 .../people/people_skills_create_controller.rb          |  6 ++++++
 app/controllers/people_controller.rb                   |  9 ++++-----
 app/domain/ptime/client.rb                             |  2 --
 spec/domain/ptime/client_spec.rb                       | 10 ----------
 4 files changed, 10 insertions(+), 17 deletions(-)

diff --git a/app/controllers/people/people_skills_create_controller.rb b/app/controllers/people/people_skills_create_controller.rb
index 1c7430fb5..287c8ded6 100644
--- a/app/controllers/people/people_skills_create_controller.rb
+++ b/app/controllers/people/people_skills_create_controller.rb
@@ -8,6 +8,8 @@ class People::PeopleSkillsCreateController < CrudController
   self.nesting = Person
   layout 'person'
 
+  helper_method :ptime_broken?
+
   def index
     return super if params[:rating].present?
 
@@ -44,4 +46,8 @@ def filter_by_rating(people_skills, rating)
   def self.controller_path
     'people/people_skills'
   end
+
+  def ptime_broken?
+    !ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE', true))
+  end
 end
diff --git a/app/controllers/people_controller.rb b/app/controllers/people_controller.rb
index 78e148cef..b47a5b3d7 100644
--- a/app/controllers/people_controller.rb
+++ b/app/controllers/people_controller.rb
@@ -62,6 +62,10 @@ def export
               disposition: content_disposition('attachment', filename)
   end
 
+  def ptime_broken?
+    !ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE', true))
+  end
+
   private
 
   def fetch_entries
@@ -71,9 +75,4 @@ def fetch_entries
   def person
     @person ||= Person.find(params[:person_id])
   end
-
-  def ptime_broken?
-    # Test what happens when ptime_accessible is nil
-    !ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE', true))
-  end
 end
diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index f463d099b..bed6204a4 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -56,8 +56,6 @@ def send_request_and_parse_response(method, path, params)
     def skills_database_request
       ENV['PTIME_API_ACCESSIBLE'] = 'false'
       ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
-
-      get('employees', { per_page: 1000 })['data']
     end
   end
 end
diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index 062d59c73..4b60f1b4d 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -72,14 +72,4 @@
         fetched_employees = Ptime::Client.new.request(:get, "employees")
         expect(fetched_employees).to eq(JSON.parse(employees_json))
     end
-
-    it 'should throw error message when host is not reachable' do
-        ENV["PTIME_BASE_URL"] = "www.unreachablehost.example.com"
-
-        stub_request(:get, "www.unreachablehost.example.com/api/v1/").
-          to_return(body: employees_json, status: 404)
-          .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
-
-        expect{ Ptime::Client.new.request(:get, "") }.to raise_error(RestClient::NotFound)
-    end
 end

From 39acf6e4d0c722f4db0c69b53865c0d99535e53a Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Mon, 15 Jul 2024 09:51:35 +0200
Subject: [PATCH 12/37] Fix logic of rendering people and cleanup code

---
 app/domain/ptime/client.rb         |  3 ++-
 app/helpers/person_helper.rb       | 21 +++++++--------------
 app/views/people/_search.html.haml |  9 +++++----
 3 files changed, 14 insertions(+), 19 deletions(-)

diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index bed6204a4..2dd72f743 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -11,7 +11,7 @@ def initialize
 
     def request(method, endpoint, params = {})
       path = @base_url + endpoint
-      if ENV['PTIME_API_ACCESSIBLE'].nil? && last_ptime_api_request_more_than_5_minutes
+      if last_ptime_api_request_more_than_5_minutes
         send_request_and_parse_response(method, path, params)
       else
         skills_database_request
@@ -48,6 +48,7 @@ def last_ptime_api_request_more_than_5_minutes
     end
 
     def send_request_and_parse_response(method, path, params)
+      ENV['PTIME_API_ACCESSIBLE'] = 'true'
       path = "#{path}?#{URI.encode_www_form(params)}" if method == :get
       response = RestClient.send(method, path, headers)
       JSON.parse(response.body)
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 8c563123b..61bf95bbb 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -77,18 +77,13 @@ def not_rated_default_skills(person)
     end
   end
 
-  def fetch_ptime_employees
-    ptime_employees = fetch_all_ptime_employees
-    ptime_employee_ids = fetch_all_ptime_employee_ids
-    build_dropdown_data(ptime_employees, ptime_employee_ids)
-  end
+  def fetch_ptime_or_skills_data
+    ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })['data']
+    all_skills_people = Person.all
+    return all_skills_people if ENV['PTIME_API_ACCESSIBLE'] == 'false'
 
-  def fetch_all_ptime_employees
-    Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })['data']
-  end
-
-  def fetch_all_ptime_employee_ids
-    Person.all.pluck(:ptime_employee_id)
+    ptime_employee_ids = all_skills_people.pluck(:ptime_employee_id)
+    build_dropdown_data(ptime_employees, ptime_employee_ids)
   end
 
   def build_dropdown_data(ptime_employees, ptime_employee_ids)
@@ -113,8 +108,6 @@ def map_ptime_employee_id(ptime_employee)
   end
 
   def append_ptime_employee_name(ptime_employee)
-    firstname = ptime_employee['attributes']['firstname']
-    lastname = ptime_employee['attributes']['lastname']
-    "#{firstname} #{lastname}"
+    "#{ptime_employee['attributes']['firstname']} #{ptime_employee['attributes']['lastname']}"
   end
 end
diff --git a/app/views/people/_search.html.haml b/app/views/people/_search.html.haml
index a3cfcb51f..23ea31441 100644
--- a/app/views/people/_search.html.haml
+++ b/app/views/people/_search.html.haml
@@ -1,10 +1,11 @@
 %div.d-flex.align-items-center.justify-content-between
   %div.d-flex.col-9.gap-3
     %span.col-6{"data-controller": "dropdown"}
-      - if ptime_broken?
-        = collection_select :person_id, :person, Person.all.sort_by {|p| p.name.downcase }, :id, :name, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}        
-      - else
-        - sorted_employees = fetch_ptime_employees.sort_by { |p| p[:name] }
+      - people_data = fetch_ptime_or_skills_data
+      - if ptime_broken? # people_data == skills
+        = collection_select :person_id, :person, people_data.sort_by {|p| p.name.downcase }, :id, :name, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}        
+      - elsif ENV['PTIME_API_ACCESSIBLE'] = 'true' # people_data == time
+        - sorted_employees = people_data.sort_by { |p| p[:name] }
         = select :person_id, :person, options_for_select(sorted_employees.map { |p| [ p[:name], p[:already_exists] ? p[:id] : "new?ptime_employee_id=#{p[:ptime_employee_id]}" ] }), {}, {data: { "dropdown-target" => "dropdown", action: "change->dropdown#handleChange", value: "/people/" }}
     %div.d-flex.align-items-center.text-gray
       = "#{t 'profile.updated_at'}: #{@person&.last_updated_at.strftime("%d.%m.%Y")}" if @person&.last_updated_at

From d967660aca62be755c64686645653cb1284fd384 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Wed, 17 Jul 2024 09:19:08 +0200
Subject: [PATCH 13/37] Add tests for person_helper

---
 spec/helpers/person_helper_spec.rb | 109 +++++++++++++++++++++++++++++
 1 file changed, 109 insertions(+)
 create mode 100644 spec/helpers/person_helper_spec.rb

diff --git a/spec/helpers/person_helper_spec.rb b/spec/helpers/person_helper_spec.rb
new file mode 100644
index 000000000..2289352eb
--- /dev/null
+++ b/spec/helpers/person_helper_spec.rb
@@ -0,0 +1,109 @@
+require 'rails_helper'
+
+ptime_base_test_url = "www.ptime.example.com"
+ptime_api_test_username = "test username"
+ptime_api_test_password = "test password"
+ENV["PTIME_BASE_URL"] = ptime_base_test_url
+ENV["PTIME_API_USERNAME"] = ptime_api_test_username
+ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
+
+RSpec.describe PersonHelper, type: :helper do
+  employees_json = {
+    'data' => [
+      {
+        'id' => 33,
+        'type' => 'employee',
+        'attributes' => {
+          'shortname' => 'LSM',
+          'firstname' => 'Longmax',
+          'lastname' => 'Smith',
+          'email' => 'longmax@example.com',
+          'marital_status' => 'single',
+          'nationalities' => ['ZW'],
+          'graduation' => 'BSc in Architecture',
+          'department_shortname' => 'SYS',
+          'employment_roles' => []
+        }
+      },
+      {
+        'id' => 21,
+        'type' => 'employee',
+        'attributes' => {
+          'shortname' => 'AMA',
+          'firstname' => 'Alice',
+          'lastname' => 'Mante',
+          'full_name' => 'Alice Mante',
+          'email' => 'alice@example.com',
+          'marital_status' => 'single',
+          'nationalities' => ['AU'],
+          'graduation' => 'MSc in writing',
+          'department_shortname' => 'SYS',
+          'employment_roles' => [],
+          'is_employed' => false,
+          'birthdate' => '01.04.2001',
+          'location' => 'Bern'
+        }
+      },
+      {
+        'id' => 45,
+        'type' => 'employee',
+        'attributes' => {
+          'shortname' => 'CFO',
+          'firstname' => 'Charlie',
+          'lastname' => 'Ford',
+          'email' => 'charlie@example.com',
+          'marital_status' => 'married',
+          'nationalities' => ['GB'],
+          'graduation' => 'MSc in networking',
+          'department_shortname' => 'SYS',
+          'employment_roles' => []
+        }
+      }
+    ]
+  }.to_json
+
+  describe '#fetch_ptime_or_skills_data' do
+    it 'should send request to ptime api' do
+      stub_request(:get, "http://#{ptime_base_test_url}/api/v1/employees?per_page=1000").
+      to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
+                                                    .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
+      fetch_ptime_or_skills_data
+    end
+
+    it 'should return people from skills database if last request was right now' do
+      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
+      skills_people = fetch_ptime_or_skills_data
+      expect(skills_people).to eq(Person.all)
+    end
+  end
+
+  describe '#build_dropdown_data' do
+    it 'should build correct dropdown data' do
+      longmax = people(:longmax)
+      alice = people(:alice)
+      charlie = people(:charlie)
+
+      longmax.update!(ptime_employee_id: 33)
+      alice.update!(ptime_employee_id: 21)
+      charlie.update!(ptime_employee_id: 45)
+
+      parsed_employees = JSON.parse(employees_json)
+      dropdown_data = build_dropdown_data(parsed_employees['data'], Person.all.pluck(:ptime_employee_id))
+
+      expect(dropdown_data[0][:id]).to eq(longmax.id)
+      expect(dropdown_data[0][:ptime_employee_id]).to eq(longmax.ptime_employee_id)
+      expect(dropdown_data[0][:name]).to eq("Longmax Smith")
+      expect(dropdown_data[0][:already_exists]).to be(true)
+
+      expect(dropdown_data[1][:id]).to eq(alice.id)
+      expect(dropdown_data[1][:ptime_employee_id]).to eq(alice.ptime_employee_id)
+      expect(dropdown_data[1][:name]).to eq("Alice Mante")
+      expect(dropdown_data[1][:already_exists]).to be(true)
+
+      expect(dropdown_data[2][:id]).to eq(charlie.id)
+      expect(dropdown_data[2][:ptime_employee_id]).to eq(charlie.ptime_employee_id)
+      expect(dropdown_data[2][:name]).to eq("Charlie Ford")
+      expect(dropdown_data[2][:already_exists]).to be(true)
+    end
+  end
+end
\ No newline at end of file

From c657e8f3e2af6e41b57a4207ee305e54187467c0 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Wed, 17 Jul 2024 11:29:59 +0200
Subject: [PATCH 14/37] Add ptime_helpers to cleanup code

---
 spec/helpers/person_helper_spec.rb | 68 ++---------------------------
 spec/rails_helper.rb               |  1 +
 spec/support/ptime_helpers.rb      | 70 ++++++++++++++++++++++++++++++
 3 files changed, 74 insertions(+), 65 deletions(-)

diff --git a/spec/helpers/person_helper_spec.rb b/spec/helpers/person_helper_spec.rb
index 2289352eb..f007140bf 100644
--- a/spec/helpers/person_helper_spec.rb
+++ b/spec/helpers/person_helper_spec.rb
@@ -1,72 +1,10 @@
 require 'rails_helper'
-
-ptime_base_test_url = "www.ptime.example.com"
-ptime_api_test_username = "test username"
-ptime_api_test_password = "test password"
-ENV["PTIME_BASE_URL"] = ptime_base_test_url
-ENV["PTIME_API_USERNAME"] = ptime_api_test_username
-ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
-
 RSpec.describe PersonHelper, type: :helper do
-  employees_json = {
-    'data' => [
-      {
-        'id' => 33,
-        'type' => 'employee',
-        'attributes' => {
-          'shortname' => 'LSM',
-          'firstname' => 'Longmax',
-          'lastname' => 'Smith',
-          'email' => 'longmax@example.com',
-          'marital_status' => 'single',
-          'nationalities' => ['ZW'],
-          'graduation' => 'BSc in Architecture',
-          'department_shortname' => 'SYS',
-          'employment_roles' => []
-        }
-      },
-      {
-        'id' => 21,
-        'type' => 'employee',
-        'attributes' => {
-          'shortname' => 'AMA',
-          'firstname' => 'Alice',
-          'lastname' => 'Mante',
-          'full_name' => 'Alice Mante',
-          'email' => 'alice@example.com',
-          'marital_status' => 'single',
-          'nationalities' => ['AU'],
-          'graduation' => 'MSc in writing',
-          'department_shortname' => 'SYS',
-          'employment_roles' => [],
-          'is_employed' => false,
-          'birthdate' => '01.04.2001',
-          'location' => 'Bern'
-        }
-      },
-      {
-        'id' => 45,
-        'type' => 'employee',
-        'attributes' => {
-          'shortname' => 'CFO',
-          'firstname' => 'Charlie',
-          'lastname' => 'Ford',
-          'email' => 'charlie@example.com',
-          'marital_status' => 'married',
-          'nationalities' => ['GB'],
-          'graduation' => 'MSc in networking',
-          'department_shortname' => 'SYS',
-          'employment_roles' => []
-        }
-      }
-    ]
-  }.to_json
 
   describe '#fetch_ptime_or_skills_data' do
+
     it 'should send request to ptime api' do
-      stub_request(:get, "http://#{ptime_base_test_url}/api/v1/employees?per_page=1000").
-      to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                    .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
+      set_env_variables_and_stub_request
       fetch_ptime_or_skills_data
     end
 
@@ -87,7 +25,7 @@
       alice.update!(ptime_employee_id: 21)
       charlie.update!(ptime_employee_id: 45)
 
-      parsed_employees = JSON.parse(employees_json)
+      parsed_employees = JSON.parse(return_ptime_employees_json)
       dropdown_data = build_dropdown_data(parsed_employees['data'], Person.all.pluck(:ptime_employee_id))
 
       expect(dropdown_data[0][:id]).to eq(longmax.id)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index b2531af7d..bc7f2fca6 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -74,6 +74,7 @@
   config.include(PersonRelationsHelpers, type: :feature)
   config.include(SlimselectHelpers, type: :feature)
   config.include(PeopleSkillsHelpers, type: :feature)
+  config.include(PtimeHelpers)
 
   config.infer_spec_type_from_file_location!
   config.filter_rails_from_backtrace!
diff --git a/spec/support/ptime_helpers.rb b/spec/support/ptime_helpers.rb
index e69de29bb..8bd79203a 100644
--- a/spec/support/ptime_helpers.rb
+++ b/spec/support/ptime_helpers.rb
@@ -0,0 +1,70 @@
+module PtimeHelpers
+    def set_env_variables_and_stub_request
+        ptime_base_test_url = "www.ptime.example.com"
+        ptime_api_test_username = "test username"
+        ptime_api_test_password = "test password"
+        ENV["PTIME_BASE_URL"] = ptime_base_test_url
+        ENV["PTIME_API_USERNAME"] = ptime_api_test_username
+        ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
+
+        stub_request(:get, "http://#{ptime_base_test_url}/api/v1/employees?per_page=1000").
+        to_return(body: return_ptime_employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
+                                                    .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
+    end
+
+    def return_ptime_employees_json
+        employees_json = {
+            'data' => [
+            {
+                'id' => 33,
+                'type' => 'employee',
+                'attributes' => {
+                'shortname' => 'LSM',
+                'firstname' => 'Longmax',
+                'lastname' => 'Smith',
+                'email' => 'longmax@example.com',
+                'marital_status' => 'single',
+                'nationalities' => ['ZW'],
+                'graduation' => 'BSc in Architecture',
+                'department_shortname' => 'SYS',
+                'employment_roles' => []
+                }
+            },
+            {
+                'id' => 21,
+                'type' => 'employee',
+                'attributes' => {
+                'shortname' => 'AMA',
+                'firstname' => 'Alice',
+                'lastname' => 'Mante',
+                'full_name' => 'Alice Mante',
+                'email' => 'alice@example.com',
+                'marital_status' => 'single',
+                'nationalities' => ['AU'],
+                'graduation' => 'MSc in writing',
+                'department_shortname' => 'SYS',
+                'employment_roles' => [],
+                'is_employed' => false,
+                'birthdate' => '01.04.2001',
+                'location' => 'Bern'
+                }
+            },
+            {
+                'id' => 45,
+                'type' => 'employee',
+                'attributes' => {
+                'shortname' => 'CFO',
+                'firstname' => 'Charlie',
+                'lastname' => 'Ford',
+                'email' => 'charlie@example.com',
+                'marital_status' => 'married',
+                'nationalities' => ['GB'],
+                'graduation' => 'MSc in networking',
+                'department_shortname' => 'SYS',
+                'employment_roles' => []
+                }
+            }
+            ]
+        }.to_json
+    end
+end
\ No newline at end of file

From bcb3ebe373fc62e7047661851c63dbeab5d9f0c2 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Wed, 17 Jul 2024 16:07:17 +0200
Subject: [PATCH 15/37] Fix tests

---
 spec/features/activities_spec.rb              |  2 +
 .../features/add_create_people_skills_spec.rb |  1 +
 spec/features/advanced_trainings_spec.rb      |  2 +
 spec/features/auth_spec.rb                    |  2 +
 spec/features/core_competences_spec.rb        |  1 +
 spec/features/cv_export_spec.rb               |  1 +
 spec/features/cv_search_spec.rb               |  1 +
 spec/features/edit_people_skills_spec.rb      |  3 +
 spec/features/educations_spec.rb              |  2 +
 spec/features/people_show_spec.rb             |  1 +
 spec/features/people_spec.rb                  | 69 ++-----------------
 spec/support/people_skills_helper.rb          |  1 +
 12 files changed, 23 insertions(+), 63 deletions(-)

diff --git a/spec/features/activities_spec.rb b/spec/features/activities_spec.rb
index 56415ca3a..8441ecd0f 100644
--- a/spec/features/activities_spec.rb
+++ b/spec/features/activities_spec.rb
@@ -4,6 +4,8 @@
   let(:person) { people(:bob) }
 
   before(:each) do
+    Capybara.page.driver.browser.manage.window.maximize
+    set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit person_path(person)
   end
diff --git a/spec/features/add_create_people_skills_spec.rb b/spec/features/add_create_people_skills_spec.rb
index cd6ef498b..7ecd6fcf9 100644
--- a/spec/features/add_create_people_skills_spec.rb
+++ b/spec/features/add_create_people_skills_spec.rb
@@ -5,6 +5,7 @@
     let(:person) { people(:bob) }
 
     before(:each) do
+      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
       visit person_people_skills_path(person)
       click_link text: "Skill hinzufügen"
diff --git a/spec/features/advanced_trainings_spec.rb b/spec/features/advanced_trainings_spec.rb
index 11ed6981d..8bf641ca4 100644
--- a/spec/features/advanced_trainings_spec.rb
+++ b/spec/features/advanced_trainings_spec.rb
@@ -4,6 +4,8 @@
   let(:person) { people(:bob) }
 
   before(:each) do
+    Capybara.page.driver.browser.manage.window.maximize
+    set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit person_path(person)
   end
diff --git a/spec/features/auth_spec.rb b/spec/features/auth_spec.rb
index 5f7124d2e..e119750ee 100644
--- a/spec/features/auth_spec.rb
+++ b/spec/features/auth_spec.rb
@@ -4,6 +4,7 @@
   context 'Check user privileges' do
 
     before(:each) do
+      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
       visit root_path
     end
@@ -23,6 +24,7 @@
   context 'Check admin privileges' do
 
     before(:each) do
+      set_env_variables_and_stub_request
       sign_in(auth_users(:admin))
       visit visit people_path
     end
diff --git a/spec/features/core_competences_spec.rb b/spec/features/core_competences_spec.rb
index a094c6a63..c2148cb0b 100644
--- a/spec/features/core_competences_spec.rb
+++ b/spec/features/core_competences_spec.rb
@@ -2,6 +2,7 @@
 
 describe "Core competences" do
   before(:each) do
+    set_env_variables_and_stub_request
     sign_in auth_users(:user), scope: :auth_user
     visit root_path
   end
diff --git a/spec/features/cv_export_spec.rb b/spec/features/cv_export_spec.rb
index d6febe027..08fedf9e8 100644
--- a/spec/features/cv_export_spec.rb
+++ b/spec/features/cv_export_spec.rb
@@ -2,6 +2,7 @@
 
 describe :people do
   before(:each) do
+    set_env_variables_and_stub_request
     sign_in auth_users(:user), scope: :auth_user
     visit root_path
   end
diff --git a/spec/features/cv_search_spec.rb b/spec/features/cv_search_spec.rb
index d44ed7908..a56e88d4a 100644
--- a/spec/features/cv_search_spec.rb
+++ b/spec/features/cv_search_spec.rb
@@ -5,6 +5,7 @@
   let(:person) { people(:bob) }
 
   before(:each) do
+    set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit("/cv_search")
   end
diff --git a/spec/features/edit_people_skills_spec.rb b/spec/features/edit_people_skills_spec.rb
index 13a30b28d..2ec46e013 100644
--- a/spec/features/edit_people_skills_spec.rb
+++ b/spec/features/edit_people_skills_spec.rb
@@ -5,6 +5,7 @@
     let(:person) { people(:bob) }
 
     before(:each) do
+      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
 
@@ -119,6 +120,8 @@ def not_rated_default_skills(person)
     let(:bob) { people(:bob) }
 
     before(:each) do
+      Capybara.page.driver.browser.manage.window.maximize
+      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
       visit person_people_skills_path(bob, rating: 1)
     end
diff --git a/spec/features/educations_spec.rb b/spec/features/educations_spec.rb
index 9eea45190..de747f570 100644
--- a/spec/features/educations_spec.rb
+++ b/spec/features/educations_spec.rb
@@ -4,6 +4,8 @@
   let(:person) { people(:bob) }
 
   before(:each) do
+    Capybara.page.driver.browser.manage.window.maximize
+    set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit person_path(person)
   end
diff --git a/spec/features/people_show_spec.rb b/spec/features/people_show_spec.rb
index a3304fed8..a548cb311 100644
--- a/spec/features/people_show_spec.rb
+++ b/spec/features/people_show_spec.rb
@@ -2,6 +2,7 @@
 
 describe 'People skills Show', type: :feature, js: true do
   before(:each) do
+    set_env_variables_and_stub_request
     sign_in auth_users(:user), scope: :auth_user
     visit root_path
   end
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index b9d022776..67e16ea95 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -1,69 +1,11 @@
 require 'rails_helper'
 
-ptime_base_test_url = "www.ptime.example.com"
-ptime_api_test_username = "test username"
-ptime_api_test_password = "test password"
-ENV["PTIME_BASE_URL"] = ptime_base_test_url
-ENV["PTIME_API_USERNAME"] = ptime_api_test_username
-ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
-
 describe :people do
   describe 'People Search', type: :feature, js: true do
-    employees_json = {
-      'data' => [
-        {
-          'id' => 33,
-          'type' => 'employee',
-          'attributes' => {
-            'shortname' => 'LSM',
-            'firstname' => 'Longmax',
-            'lastname' => 'Smith',
-            'email' => 'longmax@example.com',
-            'marital_status' => 'single',
-            'nationalities' => ['ZW'],
-            'graduation' => 'BSc in Architecture',
-            'department_shortname' => 'SYS',
-            'employment_roles' => []
-          }
-        },
-        {
-          'id' => 21,
-          'type' => 'employee',
-          'attributes' => {
-            'shortname' => 'AMA',
-            'firstname' => 'Alice',
-            'lastname' => 'Mante',
-            'full_name' => 'Alice Mante',
-            'email' => 'alice@example.com',
-            'marital_status' => 'single',
-            'nationalities' => ['AU'],
-            'graduation' => 'MSc in writing',
-            'department_shortname' => 'SYS',
-            'employment_roles' => [],
-            'is_employed' => false,
-            'birthdate' => '01.04.2001',
-            'location' => 'Bern'
-          }
-        },
-        {
-          'id' => 45,
-          'type' => 'employee',
-          'attributes' => {
-            'shortname' => 'CFO',
-            'firstname' => 'Charlie',
-            'lastname' => 'Ford',
-            'email' => 'charlie@example.com',
-            'marital_status' => 'married',
-            'nationalities' => ['GB'],
-            'graduation' => 'MSc in networking',
-            'department_shortname' => 'SYS',
-            'employment_roles' => []
-          }
-        }
-      ]
-    }.to_json
 
     before(:each) do
+      set_env_variables_and_stub_request
+      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
       sign_in auth_users(:user), scope: :auth_user
     end
 
@@ -114,9 +56,6 @@
       alice = people(:alice)
       alice.ptime_employee_id = 21
       alice.save!
-      stub_request(:get, "http://#{ptime_base_test_url}/api/v1/employees?per_page=1000").
-      to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                 .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
       visit people_path
       select_from_slim_select("#person_id_person", alice.name)
       expect(page).to have_current_path(person_path(alice))
@@ -265,6 +204,8 @@ def add_language(language)
 
   describe 'Edit person', type: :feature, js: true do
     before(:each) do
+      Capybara.page.driver.browser.manage.window.maximize
+      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
 
@@ -349,6 +290,7 @@ def add_language(language)
 
   describe 'Create person', type: :feature, js: true do
     before(:each) do
+      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
 
@@ -382,6 +324,7 @@ def add_language(language)
     let(:longmax) { people(:longmax) }
 
     before(:each) do
+      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
 
diff --git a/spec/support/people_skills_helper.rb b/spec/support/people_skills_helper.rb
index 46e72bc35..b431e392f 100644
--- a/spec/support/people_skills_helper.rb
+++ b/spec/support/people_skills_helper.rb
@@ -44,6 +44,7 @@ def validate_skill_level_label(level)
   end
 
   def validate_interest(interest)
+    require "pry"; binding.pry
     star_labels = page.find_all("label[id^='star']", visible: false).to_a.reverse
     star_labels.each_with_index do |label, index|
       body = page.document.find('body')

From 456f27b606da3602ee4cdfe187855fe00e8b9437 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Wed, 17 Jul 2024 16:32:27 +0200
Subject: [PATCH 16/37] Fix last test and remove pry

---
 spec/features/edit_people_skills_spec.rb | 1 +
 spec/support/people_skills_helper.rb     | 1 -
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/spec/features/edit_people_skills_spec.rb b/spec/features/edit_people_skills_spec.rb
index 2ec46e013..b1192c1b8 100644
--- a/spec/features/edit_people_skills_spec.rb
+++ b/spec/features/edit_people_skills_spec.rb
@@ -5,6 +5,7 @@
     let(:person) { people(:bob) }
 
     before(:each) do
+      Capybara.page.driver.browser.manage.window.maximize
       set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
diff --git a/spec/support/people_skills_helper.rb b/spec/support/people_skills_helper.rb
index b431e392f..46e72bc35 100644
--- a/spec/support/people_skills_helper.rb
+++ b/spec/support/people_skills_helper.rb
@@ -44,7 +44,6 @@ def validate_skill_level_label(level)
   end
 
   def validate_interest(interest)
-    require "pry"; binding.pry
     star_labels = page.find_all("label[id^='star']", visible: false).to_a.reverse
     star_labels.each_with_index do |label, index|
       body = page.document.find('body')

From 4718e92e5bddc0607df8928f0d44763bebb74ac0 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Thu, 18 Jul 2024 14:09:36 +0200
Subject: [PATCH 17/37] Fix test and add comment

---
 spec/features/edit_people_skills_spec.rb | 1 +
 spec/features/people_spec.rb             | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/spec/features/edit_people_skills_spec.rb b/spec/features/edit_people_skills_spec.rb
index b1192c1b8..f841f7387 100644
--- a/spec/features/edit_people_skills_spec.rb
+++ b/spec/features/edit_people_skills_spec.rb
@@ -34,6 +34,7 @@
       within("#default-skill-#{ember.skill.id}") do
         select_star_rating(2)
         select_level(3, "person[people_skills_attributes][0][level]")
+        find('#person_people_skills_attributes_0_certificate').hover
       end
 
       # Check if changes were saved
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index 67e16ea95..14313b4fd 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -5,7 +5,7 @@
 
     before(:each) do
       set_env_variables_and_stub_request
-      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
+      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s # This is needed to activate the fallback and still test for the skills people
       sign_in auth_users(:user), scope: :auth_user
     end
 
@@ -204,7 +204,7 @@ def add_language(language)
 
   describe 'Edit person', type: :feature, js: true do
     before(:each) do
-      Capybara.page.driver.browser.manage.window.maximize
+      # Capybara.page.driver.browser.manage.window.maximize
       set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end

From bf771aa302f8549b8ec23044ab38b68c1e4054a4 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Thu, 18 Jul 2024 14:39:44 +0200
Subject: [PATCH 18/37] Generalize window resizing of capybara

---
 spec/features/activities_spec.rb         | 1 -
 spec/features/advanced_trainings_spec.rb | 1 -
 spec/features/edit_people_skills_spec.rb | 2 --
 spec/features/educations_spec.rb         | 1 -
 spec/features/people_spec.rb             | 1 -
 spec/features/profile_scroll_to_spec.rb  | 1 -
 spec/rails_helper.rb                     | 4 ++++
 7 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/spec/features/activities_spec.rb b/spec/features/activities_spec.rb
index 8441ecd0f..2957106b6 100644
--- a/spec/features/activities_spec.rb
+++ b/spec/features/activities_spec.rb
@@ -4,7 +4,6 @@
   let(:person) { people(:bob) }
 
   before(:each) do
-    Capybara.page.driver.browser.manage.window.maximize
     set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit person_path(person)
diff --git a/spec/features/advanced_trainings_spec.rb b/spec/features/advanced_trainings_spec.rb
index 8bf641ca4..3baa6adb8 100644
--- a/spec/features/advanced_trainings_spec.rb
+++ b/spec/features/advanced_trainings_spec.rb
@@ -4,7 +4,6 @@
   let(:person) { people(:bob) }
 
   before(:each) do
-    Capybara.page.driver.browser.manage.window.maximize
     set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit person_path(person)
diff --git a/spec/features/edit_people_skills_spec.rb b/spec/features/edit_people_skills_spec.rb
index f841f7387..f44ad48e2 100644
--- a/spec/features/edit_people_skills_spec.rb
+++ b/spec/features/edit_people_skills_spec.rb
@@ -5,7 +5,6 @@
     let(:person) { people(:bob) }
 
     before(:each) do
-      Capybara.page.driver.browser.manage.window.maximize
       set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
@@ -122,7 +121,6 @@ def not_rated_default_skills(person)
     let(:bob) { people(:bob) }
 
     before(:each) do
-      Capybara.page.driver.browser.manage.window.maximize
       set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
       visit person_people_skills_path(bob, rating: 1)
diff --git a/spec/features/educations_spec.rb b/spec/features/educations_spec.rb
index de747f570..e6ea8ce06 100644
--- a/spec/features/educations_spec.rb
+++ b/spec/features/educations_spec.rb
@@ -4,7 +4,6 @@
   let(:person) { people(:bob) }
 
   before(:each) do
-    Capybara.page.driver.browser.manage.window.maximize
     set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit person_path(person)
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index 14313b4fd..10d935c73 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -204,7 +204,6 @@ def add_language(language)
 
   describe 'Edit person', type: :feature, js: true do
     before(:each) do
-      # Capybara.page.driver.browser.manage.window.maximize
       set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
diff --git a/spec/features/profile_scroll_to_spec.rb b/spec/features/profile_scroll_to_spec.rb
index ca99864d6..f227eb7c4 100644
--- a/spec/features/profile_scroll_to_spec.rb
+++ b/spec/features/profile_scroll_to_spec.rb
@@ -6,7 +6,6 @@
   before(:each) do
     sign_in auth_users(:user), scope: :auth_user
     visit root_path
-    Capybara.page.driver.browser.manage.window.maximize
   end
 
   it 'Should change background of selected section' do
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index bc7f2fca6..66bdf0270 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -45,6 +45,10 @@
     self.class.fixtures :all
   end
 
+  config.before(:each, js: true) do
+    Capybara.page.current_window.resize_to(1920, 1080)
+  end
+
   if Bullet.enable?
     config.before(:each) do
       Bullet.start_request

From 189ab8009c1f7676c06236102775726300fb1bd6 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Fri, 19 Jul 2024 10:37:31 +0200
Subject: [PATCH 19/37] Cleanup code and add comments

---
 app/domain/ptime/client.rb                    |  1 +
 app/helpers/person_helper.rb                  |  1 +
 .../20240701085558_create_delayed_jobs.rb     | 22 -----
 spec/domain/ptime/assign_employee_ids_spec.rb | 83 ++-----------------
 spec/domain/ptime/client_spec.rb              | 60 +-------------
 spec/features/people_spec.rb                  |  2 +-
 6 files changed, 11 insertions(+), 158 deletions(-)
 delete mode 100644 db/migrate/20240701085558_create_delayed_jobs.rb

diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index 2dd72f743..173c59b94 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -22,6 +22,7 @@ def request(method, endpoint, params = {})
 
     private
 
+    # Currently not in use can be removed
     def response_error_message(exception)
       JSON.parse(exception.response.body).dig('error', 'message')
     rescue JSON::ParserError # rescue only JSON parsing errors
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 61bf95bbb..84618f3b3 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -107,6 +107,7 @@ def map_ptime_employee_id(ptime_employee)
     ptime_employee_id_map[ptime_employee['id'].to_s]
   end
 
+  # Once https://github.com/puzzle/skills/issues/744 is merged there should be no need for this
   def append_ptime_employee_name(ptime_employee)
     "#{ptime_employee['attributes']['firstname']} #{ptime_employee['attributes']['lastname']}"
   end
diff --git a/db/migrate/20240701085558_create_delayed_jobs.rb b/db/migrate/20240701085558_create_delayed_jobs.rb
deleted file mode 100644
index c3ce5afc9..000000000
--- a/db/migrate/20240701085558_create_delayed_jobs.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-class CreateDelayedJobs < ActiveRecord::Migration[7.0]
-  def self.up
-    create_table :delayed_jobs do |table|
-      table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue
-      table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually.
-      table.text :handler,                 null: false # YAML-encoded string of the object that will do work
-      table.text :last_error                           # reason for last failure (See Note below)
-      table.datetime :run_at                           # When to run. Could be Time.zone.now for immediately, or sometime in the future.
-      table.datetime :locked_at                        # Set when a client is working on this object
-      table.datetime :failed_at                        # Set when all retries have failed (actually, by default, the record is deleted instead)
-      table.string :locked_by                          # Who is working on this object (if locked)
-      table.string :queue                              # The name of the queue this job is in
-      table.timestamps null: true
-    end
-
-    add_index :delayed_jobs, [:priority, :run_at], name: "delayed_jobs_priority"
-  end
-
-  def self.down
-    drop_table :delayed_jobs
-  end
-end
diff --git a/spec/domain/ptime/assign_employee_ids_spec.rb b/spec/domain/ptime/assign_employee_ids_spec.rb
index 1e7e7a462..5fd7e22b8 100644
--- a/spec/domain/ptime/assign_employee_ids_spec.rb
+++ b/spec/domain/ptime/assign_employee_ids_spec.rb
@@ -1,74 +1,11 @@
 require 'rails_helper'
 
-ptime_base_test_url = "www.ptime.example.com"
-ptime_api_test_username = "test username"
-ptime_api_test_password = "test password"
-ENV["PTIME_BASE_URL"] = ptime_base_test_url
-ENV["PTIME_API_USERNAME"] = ptime_api_test_username
-ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
-
 describe Ptime::AssignEmployeeIds do
-  employees_json = {
-    'data': [
-      {
-        'id': 33,
-        'type': 'employee',
-        'attributes': {
-          'shortname': 'LSM',
-          'firstname': 'Longmax',
-          'lastname': 'Smith',
-          'email': 'longmax@example.com',
-          'marital_status': 'single',
-          'nationalities': [
-            'ZW'
-          ],
-          'graduation': 'BSc in Architecture',
-          'department_shortname': 'SYS',
-          'employment_roles': []
-        }
-      },
-      {
-        'id': 21,
-        'type': 'employee',
-        'attributes': {
-          'shortname': 'AMA',
-          'firstname': 'Alice',
-          'lastname': 'Mante',
-          'email': 'alice@example.com',
-          'marital_status': 'single',
-          'nationalities': [
-            'AU'
-          ],
-          'graduation': 'MSc in writing',
-          'department_shortname': 'SYS',
-          'employment_roles': []
-        }
-      },
-      {
-        'id': 45,
-        'type': 'employee',
-        'attributes': {
-          'shortname': 'CFO',
-          'firstname': 'Charlie',
-          'lastname': 'Ford',
-          'email': 'charlie@example.com',
-          'marital_status': 'married',
-          'nationalities': [
-            'GB'
-          ],
-          'graduation': 'MSc in networking',
-          'department_shortname': 'SYS',
-          'employment_roles': []
-        }
-      },
-    ]
-  }.to_json
+  before(:each) do
+    set_env_variables_and_stub_request
+  end
 
   it 'should map people with the correct puzzletime id' do
-    stub_request(:get, "#{ptime_base_test_url}/api/v1/employees?per_page=1000").
-      to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                 .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
-
     person_longmax = people(:longmax)
     person_alice = people(:alice)
     person_charlie = people(:charlie)
@@ -81,12 +18,12 @@
   end
 
   it 'should not map people that are not found' do
-    parsed_employees_json = JSON.parse(employees_json)
+    parsed_employees_json = JSON.parse(return_ptime_employees_json)
     parsed_employees_json['data'].first["attributes"]["firstname"] = "Rochus"
     parsed_employees_json['data'].second["attributes"]["firstname"] = "Melchior"
-    stub_request(:get, "#{ptime_base_test_url}/api/v1/employees?per_page=1000").
+    stub_request(:get, "#{ENV["PTIME_BASE_URL"]}/api/v1/employees?per_page=1000").
       to_return(body: parsed_employees_json.to_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                               .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
+                                                                               .with(basic_auth: [ENV["PTIME_API_USERNAME"], ENV["PTIME_API_PASSWORD"]])
 
     person_longmax = people(:longmax)
     person_alice = people(:alice)
@@ -100,10 +37,6 @@
   end
 
   it 'should not map people that are ambiguous' do
-    stub_request(:get, "#{ptime_base_test_url}/api/v1/employees?per_page=1000").
-      to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                 .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
-    
     person_longmax = people(:longmax)
     person_alice = people(:alice)
     person_charlie = people(:charlie)
@@ -120,10 +53,6 @@
   end
 
   it 'should not map people on dry run' do
-    stub_request(:get, "#{ptime_base_test_url}/api/v1/employees?per_page=1000").
-      to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                               .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
-
     person_longmax = people(:longmax)
     person_alice = people(:alice)
     person_charlie = people(:charlie)
diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index 4b60f1b4d..26945e7ab 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -8,68 +8,12 @@
 ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
 
 describe Ptime::Client do
-    employees_json = {
-      'data': [
-        {
-          'id': 33,
-          'type': 'employee',
-          'attributes': {
-            'shortname': 'LSM',
-            'firstname': 'Longmax',
-            'lastname': 'Smith',
-            'email': 'longmax@example.com',
-            'marital_status': 'single',
-            'nationalities': [
-              'ZW'
-            ],
-            'graduation': 'BSc in Architecture',
-            'department_shortname': 'SYS',
-            'employment_roles': []
-          }
-        },
-        {
-          'id': 21,
-          'type': 'employee',
-          'attributes': {
-            'shortname': 'AMA',
-            'firstname': 'Alice',
-            'lastname': 'Mante',
-            'email': 'alice@example.com',
-            'marital_status': 'single',
-            'nationalities': [
-              'AU'
-            ],
-            'graduation': 'MSc in writing',
-            'department_shortname': 'SYS',
-            'employment_roles': []
-          }
-        },
-        {
-          'id': 45,
-          'type': 'employee',
-          'attributes': {
-            'shortname': 'CFO',
-            'firstname': 'Charlie',
-            'lastname': 'Ford',
-            'email': 'charlie@example.com',
-            'marital_status': 'married',
-            'nationalities': [
-              'GB'
-            ],
-            'graduation': 'MSc in networking',
-            'department_shortname': 'SYS',
-            'employment_roles': []
-          }
-        },
-      ]
-    }.to_json
-
     it 'should be able to fetch employee data' do
         stub_request(:get, "#{ptime_base_test_url}/api/v1/employees").
-          to_return(body: employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
+          to_return(body: return_ptime_employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
                                                                        .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
 
         fetched_employees = Ptime::Client.new.request(:get, "employees")
-        expect(fetched_employees).to eq(JSON.parse(employees_json))
+        expect(fetched_employees).to eq(JSON.parse(return_ptime_employees_json))
     end
 end
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index 10d935c73..8015ccd58 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -5,7 +5,7 @@
 
     before(:each) do
       set_env_variables_and_stub_request
-      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s # This is needed to activate the fallback and still test for the skills people
+      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s # This is needed to activate the fallback and test for the skills people
       sign_in auth_users(:user), scope: :auth_user
     end
 

From 2e2ae6b48b0c1767a0091505b6f1ed728f938b95 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Fri, 19 Jul 2024 12:49:30 +0200
Subject: [PATCH 20/37] Add one more test for client.rb

---
 spec/domain/ptime/client_spec.rb | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index 26945e7ab..b6e03cb4b 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -16,4 +16,12 @@
         fetched_employees = Ptime::Client.new.request(:get, "employees")
         expect(fetched_employees).to eq(JSON.parse(return_ptime_employees_json))
     end
+
+    it 'should fallback to skills if last request was right now' do
+        ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
+
+        skills_people = Ptime::Client.new.request(:get, "employees")
+
+        expect(ENV['PTIME_API_ACCESSIBLE']).to eq('false')
+    end
 end

From 766e9423f80f5c3169a3afb58e01ad22b199d304 Mon Sep 17 00:00:00 2001
From: Manuel <moeri@puzzle.ch>
Date: Fri, 19 Jul 2024 13:15:14 +0200
Subject: [PATCH 21/37] Reverse test from last commit

---
 spec/domain/ptime/client_spec.rb | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index b6e03cb4b..26945e7ab 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -16,12 +16,4 @@
         fetched_employees = Ptime::Client.new.request(:get, "employees")
         expect(fetched_employees).to eq(JSON.parse(return_ptime_employees_json))
     end
-
-    it 'should fallback to skills if last request was right now' do
-        ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
-
-        skills_people = Ptime::Client.new.request(:get, "employees")
-
-        expect(ENV['PTIME_API_ACCESSIBLE']).to eq('false')
-    end
 end

From aa36e826b5ccbb9e7619a2bc2f328fdd5f21b9f4 Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 09:06:31 +0200
Subject: [PATCH 22/37] change location of helper

---
 app/controllers/people/people_skills_create_controller.rb | 6 ------
 app/controllers/people_controller.rb                      | 6 ------
 app/helpers/auth_helper.rb                                | 4 ++++
 3 files changed, 4 insertions(+), 12 deletions(-)

diff --git a/app/controllers/people/people_skills_create_controller.rb b/app/controllers/people/people_skills_create_controller.rb
index 287c8ded6..1c7430fb5 100644
--- a/app/controllers/people/people_skills_create_controller.rb
+++ b/app/controllers/people/people_skills_create_controller.rb
@@ -8,8 +8,6 @@ class People::PeopleSkillsCreateController < CrudController
   self.nesting = Person
   layout 'person'
 
-  helper_method :ptime_broken?
-
   def index
     return super if params[:rating].present?
 
@@ -46,8 +44,4 @@ def filter_by_rating(people_skills, rating)
   def self.controller_path
     'people/people_skills'
   end
-
-  def ptime_broken?
-    !ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE', true))
-  end
 end
diff --git a/app/controllers/people_controller.rb b/app/controllers/people_controller.rb
index b47a5b3d7..6d1fed1b8 100644
--- a/app/controllers/people_controller.rb
+++ b/app/controllers/people_controller.rb
@@ -5,8 +5,6 @@ class PeopleController < CrudController
   include ParamConverters
   include PeopleControllerConcerns
 
-  helper_method :ptime_broken?
-
   self.permitted_attrs = [:birthdate, :location, :marital_status, :updated_by, :name, :nationality,
                           :nationality2, :title, :competence_notes, :company_id, :email,
                           :department_id, :shortname, :picture, :picture_cache,
@@ -62,10 +60,6 @@ def export
               disposition: content_disposition('attachment', filename)
   end
 
-  def ptime_broken?
-    !ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE', true))
-  end
-
   private
 
   def fetch_entries
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 2d98e159f..b5e216197 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -21,4 +21,8 @@ def find_person_by_auth_user
   def devise?
     AuthConfig.keycloak? || Rails.env.test?
   end
+
+  def ptime_broken?
+    !ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE', true))
+  end
 end

From 4153a5ba4158074d6e2d1c7ab8fecf367d313ae3 Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 09:24:57 +0200
Subject: [PATCH 23/37] set ptime stubing global

---
 spec/domain/ptime/assign_employee_ids_spec.rb  | 3 ---
 spec/features/activities_spec.rb               | 1 -
 spec/features/add_create_people_skills_spec.rb | 1 -
 spec/features/advanced_trainings_spec.rb       | 1 -
 spec/features/auth_spec.rb                     | 2 --
 spec/features/core_competences_spec.rb         | 1 -
 spec/features/cv_export_spec.rb                | 1 -
 spec/features/cv_search_spec.rb                | 1 -
 spec/features/edit_people_skills_spec.rb       | 2 --
 spec/features/educations_spec.rb               | 1 -
 spec/features/people_show_spec.rb              | 1 -
 spec/features/people_spec.rb                   | 4 ----
 spec/helpers/person_helper_spec.rb             | 3 +--
 spec/rails_helper.rb                           | 4 ++++
 14 files changed, 5 insertions(+), 21 deletions(-)

diff --git a/spec/domain/ptime/assign_employee_ids_spec.rb b/spec/domain/ptime/assign_employee_ids_spec.rb
index 5fd7e22b8..50a948a9a 100644
--- a/spec/domain/ptime/assign_employee_ids_spec.rb
+++ b/spec/domain/ptime/assign_employee_ids_spec.rb
@@ -1,9 +1,6 @@
 require 'rails_helper'
 
 describe Ptime::AssignEmployeeIds do
-  before(:each) do
-    set_env_variables_and_stub_request
-  end
 
   it 'should map people with the correct puzzletime id' do
     person_longmax = people(:longmax)
diff --git a/spec/features/activities_spec.rb b/spec/features/activities_spec.rb
index 2957106b6..56415ca3a 100644
--- a/spec/features/activities_spec.rb
+++ b/spec/features/activities_spec.rb
@@ -4,7 +4,6 @@
   let(:person) { people(:bob) }
 
   before(:each) do
-    set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit person_path(person)
   end
diff --git a/spec/features/add_create_people_skills_spec.rb b/spec/features/add_create_people_skills_spec.rb
index 7ecd6fcf9..cd6ef498b 100644
--- a/spec/features/add_create_people_skills_spec.rb
+++ b/spec/features/add_create_people_skills_spec.rb
@@ -5,7 +5,6 @@
     let(:person) { people(:bob) }
 
     before(:each) do
-      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
       visit person_people_skills_path(person)
       click_link text: "Skill hinzufügen"
diff --git a/spec/features/advanced_trainings_spec.rb b/spec/features/advanced_trainings_spec.rb
index 3baa6adb8..11ed6981d 100644
--- a/spec/features/advanced_trainings_spec.rb
+++ b/spec/features/advanced_trainings_spec.rb
@@ -4,7 +4,6 @@
   let(:person) { people(:bob) }
 
   before(:each) do
-    set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit person_path(person)
   end
diff --git a/spec/features/auth_spec.rb b/spec/features/auth_spec.rb
index e119750ee..5f7124d2e 100644
--- a/spec/features/auth_spec.rb
+++ b/spec/features/auth_spec.rb
@@ -4,7 +4,6 @@
   context 'Check user privileges' do
 
     before(:each) do
-      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
       visit root_path
     end
@@ -24,7 +23,6 @@
   context 'Check admin privileges' do
 
     before(:each) do
-      set_env_variables_and_stub_request
       sign_in(auth_users(:admin))
       visit visit people_path
     end
diff --git a/spec/features/core_competences_spec.rb b/spec/features/core_competences_spec.rb
index c2148cb0b..a094c6a63 100644
--- a/spec/features/core_competences_spec.rb
+++ b/spec/features/core_competences_spec.rb
@@ -2,7 +2,6 @@
 
 describe "Core competences" do
   before(:each) do
-    set_env_variables_and_stub_request
     sign_in auth_users(:user), scope: :auth_user
     visit root_path
   end
diff --git a/spec/features/cv_export_spec.rb b/spec/features/cv_export_spec.rb
index 08fedf9e8..d6febe027 100644
--- a/spec/features/cv_export_spec.rb
+++ b/spec/features/cv_export_spec.rb
@@ -2,7 +2,6 @@
 
 describe :people do
   before(:each) do
-    set_env_variables_and_stub_request
     sign_in auth_users(:user), scope: :auth_user
     visit root_path
   end
diff --git a/spec/features/cv_search_spec.rb b/spec/features/cv_search_spec.rb
index a56e88d4a..d44ed7908 100644
--- a/spec/features/cv_search_spec.rb
+++ b/spec/features/cv_search_spec.rb
@@ -5,7 +5,6 @@
   let(:person) { people(:bob) }
 
   before(:each) do
-    set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit("/cv_search")
   end
diff --git a/spec/features/edit_people_skills_spec.rb b/spec/features/edit_people_skills_spec.rb
index f44ad48e2..da178ab78 100644
--- a/spec/features/edit_people_skills_spec.rb
+++ b/spec/features/edit_people_skills_spec.rb
@@ -5,7 +5,6 @@
     let(:person) { people(:bob) }
 
     before(:each) do
-      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
 
@@ -121,7 +120,6 @@ def not_rated_default_skills(person)
     let(:bob) { people(:bob) }
 
     before(:each) do
-      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
       visit person_people_skills_path(bob, rating: 1)
     end
diff --git a/spec/features/educations_spec.rb b/spec/features/educations_spec.rb
index e6ea8ce06..9eea45190 100644
--- a/spec/features/educations_spec.rb
+++ b/spec/features/educations_spec.rb
@@ -4,7 +4,6 @@
   let(:person) { people(:bob) }
 
   before(:each) do
-    set_env_variables_and_stub_request
     sign_in auth_users(:admin)
     visit person_path(person)
   end
diff --git a/spec/features/people_show_spec.rb b/spec/features/people_show_spec.rb
index a548cb311..a3304fed8 100644
--- a/spec/features/people_show_spec.rb
+++ b/spec/features/people_show_spec.rb
@@ -2,7 +2,6 @@
 
 describe 'People skills Show', type: :feature, js: true do
   before(:each) do
-    set_env_variables_and_stub_request
     sign_in auth_users(:user), scope: :auth_user
     visit root_path
   end
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index 8015ccd58..9cfda38c6 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -4,7 +4,6 @@
   describe 'People Search', type: :feature, js: true do
 
     before(:each) do
-      set_env_variables_and_stub_request
       ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s # This is needed to activate the fallback and test for the skills people
       sign_in auth_users(:user), scope: :auth_user
     end
@@ -204,7 +203,6 @@ def add_language(language)
 
   describe 'Edit person', type: :feature, js: true do
     before(:each) do
-      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
 
@@ -289,7 +287,6 @@ def add_language(language)
 
   describe 'Create person', type: :feature, js: true do
     before(:each) do
-      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
 
@@ -323,7 +320,6 @@ def add_language(language)
     let(:longmax) { people(:longmax) }
 
     before(:each) do
-      set_env_variables_and_stub_request
       sign_in auth_users(:user), scope: :auth_user
     end
 
diff --git a/spec/helpers/person_helper_spec.rb b/spec/helpers/person_helper_spec.rb
index f007140bf..9072eceab 100644
--- a/spec/helpers/person_helper_spec.rb
+++ b/spec/helpers/person_helper_spec.rb
@@ -4,7 +4,6 @@
   describe '#fetch_ptime_or_skills_data' do
 
     it 'should send request to ptime api' do
-      set_env_variables_and_stub_request
       fetch_ptime_or_skills_data
     end
 
@@ -44,4 +43,4 @@
       expect(dropdown_data[2][:already_exists]).to be(true)
     end
   end
-end
\ No newline at end of file
+end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 66bdf0270..f5a7ffb73 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -62,6 +62,10 @@
 
   config.infer_spec_type_from_file_location!
 
+  config.before(:each) do
+    set_env_variables_and_stub_request
+  end
+
   # Controller helper
   config.include(JsonMacros, type: :controller)
   config.include(JsonAssertion, type: :controller)

From 87d84d8e9534d82738dd208d2453c2a78f20e6c5 Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 09:41:13 +0200
Subject: [PATCH 24/37] clean up ptime client specs

---
 app/domain/ptime/client.rb       |  2 +-
 spec/domain/ptime/client_spec.rb | 13 +------------
 2 files changed, 2 insertions(+), 13 deletions(-)

diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index 173c59b94..f84120f39 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -50,7 +50,7 @@ def last_ptime_api_request_more_than_5_minutes
 
     def send_request_and_parse_response(method, path, params)
       ENV['PTIME_API_ACCESSIBLE'] = 'true'
-      path = "#{path}?#{URI.encode_www_form(params)}" if method == :get
+      path = "#{path}?#{URI.encode_www_form(params)}" if method == :get && params.present?
       response = RestClient.send(method, path, headers)
       JSON.parse(response.body)
     end
diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index 26945e7ab..12401112e 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -1,19 +1,8 @@
 require 'rails_helper'
 
-ptime_base_test_url = "www.ptime.example.com"
-ptime_api_test_username = "test username"
-ptime_api_test_password = "test password"
-ENV["PTIME_BASE_URL"] = ptime_base_test_url
-ENV["PTIME_API_USERNAME"] = ptime_api_test_username
-ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
-
 describe Ptime::Client do
     it 'should be able to fetch employee data' do
-        stub_request(:get, "#{ptime_base_test_url}/api/v1/employees").
-          to_return(body: return_ptime_employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                       .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
-
-        fetched_employees = Ptime::Client.new.request(:get, "employees")
+        fetched_employees = Ptime::Client.new.request(:get, "employees?per_page=1000")
         expect(fetched_employees).to eq(JSON.parse(return_ptime_employees_json))
     end
 end

From 96391b22c00c9f12ecef3f9c626896fa6a71c358 Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 09:59:25 +0200
Subject: [PATCH 25/37] create json file for fixtures

---
 .../files/json/all_ptime_employees.json       | 53 ++++++++++++++++++
 spec/rails_helper.rb                          |  6 +-
 spec/support/json_helpers.rb                  |  6 ++
 spec/support/ptime_helpers.rb                 | 56 +------------------
 4 files changed, 66 insertions(+), 55 deletions(-)
 create mode 100644 spec/fixtures/files/json/all_ptime_employees.json
 create mode 100644 spec/support/json_helpers.rb

diff --git a/spec/fixtures/files/json/all_ptime_employees.json b/spec/fixtures/files/json/all_ptime_employees.json
new file mode 100644
index 000000000..685e11445
--- /dev/null
+++ b/spec/fixtures/files/json/all_ptime_employees.json
@@ -0,0 +1,53 @@
+{
+    "data": [
+        {
+            "id" : 33,
+            "type" : "employee",
+            "attributes" : {
+            "shortname" : "LSM",
+            "firstname" : "Longmax",
+            "lastname" : "Smith",
+            "email" : "longmax@example.com",
+            "marital_status" : "single",
+            "nationalities" : ["ZW"],
+            "graduation" : "BSc in Architecture",
+            "department_shortname" : "SYS",
+            "employment_roles" : []
+            }
+        },
+        {
+            "id" : 21,
+            "type" : "employee",
+            "attributes" : {
+            "shortname" : "AMA",
+            "firstname" : "Alice",
+            "lastname" : "Mante",
+            "full_name" : "Alice Mante",
+            "email" : "alice@example.com",
+            "marital_status" : "single",
+            "nationalities" : ["AU"],
+            "graduation" : "MSc in writing",
+            "department_shortname" : "SYS",
+            "employment_roles" : [],
+            "is_employed" : false,
+            "birthdate" : "01.04.2001",
+            "location" : "Bern"
+            }
+        },
+        {
+            "id" : 45,
+            "type" : "employee",
+            "attributes" : {
+            "shortname" : "CFO",
+            "firstname" : "Charlie",
+            "lastname" : "Ford",
+            "email" : "charlie@example.com",
+            "marital_status" : "married",
+            "nationalities" : ["GB"],
+            "graduation" : "MSc in networking",
+            "department_shortname" : "SYS",
+            "employment_roles" : []
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index f5a7ffb73..dacc6e9ba 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -79,10 +79,14 @@
   config.include(ActionView::RecordIdentifier, type: :feature)
 
   # Custom helpers
+  ## Global helpers
+  config.include(PtimeHelpers)
+  config.include(JsonHelpers)
+
+  ## Feature helpers
   config.include(PersonRelationsHelpers, type: :feature)
   config.include(SlimselectHelpers, type: :feature)
   config.include(PeopleSkillsHelpers, type: :feature)
-  config.include(PtimeHelpers)
 
   config.infer_spec_type_from_file_location!
   config.filter_rails_from_backtrace!
diff --git a/spec/support/json_helpers.rb b/spec/support/json_helpers.rb
new file mode 100644
index 000000000..fa66633ac
--- /dev/null
+++ b/spec/support/json_helpers.rb
@@ -0,0 +1,6 @@
+module JsonHelpers
+
+  def json_data(filename:)
+    file_fixture("json/#{filename}.json").read
+  end
+end
diff --git a/spec/support/ptime_helpers.rb b/spec/support/ptime_helpers.rb
index 8bd79203a..cbfda4d06 100644
--- a/spec/support/ptime_helpers.rb
+++ b/spec/support/ptime_helpers.rb
@@ -13,58 +13,6 @@ def set_env_variables_and_stub_request
     end
 
     def return_ptime_employees_json
-        employees_json = {
-            'data' => [
-            {
-                'id' => 33,
-                'type' => 'employee',
-                'attributes' => {
-                'shortname' => 'LSM',
-                'firstname' => 'Longmax',
-                'lastname' => 'Smith',
-                'email' => 'longmax@example.com',
-                'marital_status' => 'single',
-                'nationalities' => ['ZW'],
-                'graduation' => 'BSc in Architecture',
-                'department_shortname' => 'SYS',
-                'employment_roles' => []
-                }
-            },
-            {
-                'id' => 21,
-                'type' => 'employee',
-                'attributes' => {
-                'shortname' => 'AMA',
-                'firstname' => 'Alice',
-                'lastname' => 'Mante',
-                'full_name' => 'Alice Mante',
-                'email' => 'alice@example.com',
-                'marital_status' => 'single',
-                'nationalities' => ['AU'],
-                'graduation' => 'MSc in writing',
-                'department_shortname' => 'SYS',
-                'employment_roles' => [],
-                'is_employed' => false,
-                'birthdate' => '01.04.2001',
-                'location' => 'Bern'
-                }
-            },
-            {
-                'id' => 45,
-                'type' => 'employee',
-                'attributes' => {
-                'shortname' => 'CFO',
-                'firstname' => 'Charlie',
-                'lastname' => 'Ford',
-                'email' => 'charlie@example.com',
-                'marital_status' => 'married',
-                'nationalities' => ['GB'],
-                'graduation' => 'MSc in networking',
-                'department_shortname' => 'SYS',
-                'employment_roles' => []
-                }
-            }
-            ]
-        }.to_json
+        json_data filename: "all_ptime_employees"
     end
-end
\ No newline at end of file
+end

From cce291b5e637d887463e15ff7c3318a16532bde8 Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 12:03:22 +0200
Subject: [PATCH 26/37] use hashes instead of strings for json keys create
 global ptime stub method

---
 app/domain/ptime/assign_employee_ids.rb       |  14 +--
 app/domain/ptime/client.rb                    |   3 +-
 app/domain/ptime/update_people_data.rb        |  12 +-
 app/helpers/person_helper.rb                  |  10 +-
 spec/domain/ptime/assign_employee_ids_spec.rb |   8 +-
 spec/domain/ptime/client_spec.rb              |   5 +-
 spec/domain/ptime/update_people_data_spec.rb  | 118 ++----------------
 .../files/json/new_ptime_employee.json        |  21 ++++
 .../files/json/updating_ptime_employees.json  |  72 +++++++++++
 spec/helpers/person_helper_spec.rb            |   4 +-
 spec/rails_helper.rb                          |   2 +
 spec/support/json_helpers.rb                  |   5 +-
 spec/support/ptime_helpers.rb                 |  18 ++-
 13 files changed, 152 insertions(+), 140 deletions(-)
 create mode 100644 spec/fixtures/files/json/new_ptime_employee.json
 create mode 100644 spec/fixtures/files/json/updating_ptime_employees.json

diff --git a/app/domain/ptime/assign_employee_ids.rb b/app/domain/ptime/assign_employee_ids.rb
index c9ee84a0a..c25518004 100644
--- a/app/domain/ptime/assign_employee_ids.rb
+++ b/app/domain/ptime/assign_employee_ids.rb
@@ -25,25 +25,25 @@ def map_employees(should_map)
       mapped_people_count = 0
 
       @ptime_employees.each do |ptime_employee|
-        ptime_employee_firstname = ptime_employee['attributes']['firstname']
-        ptime_employee_lastname = ptime_employee['attributes']['lastname']
+        ptime_employee_firstname = ptime_employee[:attributes][:firstname]
+        ptime_employee_lastname = ptime_employee[:attributes][:lastname]
         ptime_employee_name = "#{ptime_employee_firstname} #{ptime_employee_lastname}"
-        ptime_employee_email = ptime_employee['attributes']['email']
+        ptime_employee_email = ptime_employee[:attributes][:email]
         matched_skills_people = Person.where(ptime_employee_id: nil)
                                       .where(name: ptime_employee_name)
                                       .where(email: ptime_employee_email)
 
         if matched_skills_people.empty?
-          unmatched_entries << { name: ptime_employee_name, id: ptime_employee['id'] }
+          unmatched_entries << { name: ptime_employee_name, id: ptime_employee[:id] }
         elsif matched_skills_people.one?
           if should_map
             matched_skills_person = matched_skills_people.first
-            matched_skills_person.ptime_employee_id = ptime_employee['id']
+            matched_skills_person.ptime_employee_id = ptime_employee[:id]
             matched_skills_person.save!
           end
           mapped_people_count += 1
         else
-          ambiguous_entries << { name: ptime_employee_name, id: ptime_employee['id'] }
+          ambiguous_entries << { name: ptime_employee_name, id: ptime_employee[:id] }
         end
       end
 
@@ -60,7 +60,7 @@ def map_employees(should_map)
 
     def fetch_data
       puts 'Fetching required data...'
-      @ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })['data']
+      @ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })[:data]
       @skills_people = Person.all
       puts 'Successfully fetched data'
     end
diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index f84120f39..f9e528ff0 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -52,12 +52,13 @@ def send_request_and_parse_response(method, path, params)
       ENV['PTIME_API_ACCESSIBLE'] = 'true'
       path = "#{path}?#{URI.encode_www_form(params)}" if method == :get && params.present?
       response = RestClient.send(method, path, headers)
-      JSON.parse(response.body)
+      JSON.parse(response.body, symbolize_names: true)
     end
 
     def skills_database_request
       ENV['PTIME_API_ACCESSIBLE'] = 'false'
       ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
+      {}
     end
   end
 end
diff --git a/app/domain/ptime/update_people_data.rb b/app/domain/ptime/update_people_data.rb
index 24eb88558..1edcf2de5 100644
--- a/app/domain/ptime/update_people_data.rb
+++ b/app/domain/ptime/update_people_data.rb
@@ -7,18 +7,18 @@ class UpdatePeopleData
 
     # rubocop:disable Metrics
     def run
-      ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })['data']
+      ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })[:data]
       ptime_employees.each do |ptime_employee|
-        ptime_employee_firstname = ptime_employee['attributes']['firstname']
-        ptime_employee_lastname = ptime_employee['attributes']['lastname']
+        ptime_employee_firstname = ptime_employee[:attributes][:firstname]
+        ptime_employee_lastname = ptime_employee[:attributes][:lastname]
         ptime_employee_name = "#{ptime_employee_firstname} #{ptime_employee_lastname}"
 
-        skills_person = Person.find_by(ptime_employee_id: ptime_employee['id'])
+        skills_person = Person.find_by(ptime_employee_id: ptime_employee[:id])
         skills_person ||= Person.new
 
         skills_person.name = ptime_employee_name
-        skills_person.ptime_employee_id ||= ptime_employee['id']
-        ptime_employee['attributes'].each do |key, value|
+        skills_person.ptime_employee_id ||= ptime_employee[:id]
+        ptime_employee[:attributes].each do |key, value|
           if key.to_sym.in?(ATTRIBUTE_MAPPING.keys)
             skills_person[ATTRIBUTE_MAPPING[key.to_sym]] = value
           end
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 84618f3b3..23fc3456b 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -78,7 +78,7 @@ def not_rated_default_skills(person)
   end
 
   def fetch_ptime_or_skills_data
-    ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })['data']
+    ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })[:data]
     all_skills_people = Person.all
     return all_skills_people if ENV['PTIME_API_ACCESSIBLE'] == 'false'
 
@@ -92,9 +92,9 @@ def build_dropdown_data(ptime_employees, ptime_employee_ids)
       person_id = map_ptime_employee_id(ptime_employee)
       {
         id: person_id,
-        ptime_employee_id: ptime_employee['id'],
+        ptime_employee_id: ptime_employee[:id],
         name: ptime_employee_name,
-        already_exists: ptime_employee['id'].in?(ptime_employee_ids)
+        already_exists: ptime_employee[:id].in?(ptime_employee_ids)
       }
     end
   end
@@ -104,11 +104,11 @@ def map_ptime_employee_id(ptime_employee)
       hash[person.ptime_employee_id.to_s] = person.id
     end
 
-    ptime_employee_id_map[ptime_employee['id'].to_s]
+    ptime_employee_id_map[ptime_employee[:id].to_s]
   end
 
   # Once https://github.com/puzzle/skills/issues/744 is merged there should be no need for this
   def append_ptime_employee_name(ptime_employee)
-    "#{ptime_employee['attributes']['firstname']} #{ptime_employee['attributes']['lastname']}"
+    "#{ptime_employee[:attributes][:firstname]} #{ptime_employee[:attributes][:lastname]}"
   end
 end
diff --git a/spec/domain/ptime/assign_employee_ids_spec.rb b/spec/domain/ptime/assign_employee_ids_spec.rb
index 50a948a9a..464260b13 100644
--- a/spec/domain/ptime/assign_employee_ids_spec.rb
+++ b/spec/domain/ptime/assign_employee_ids_spec.rb
@@ -15,9 +15,11 @@
   end
 
   it 'should not map people that are not found' do
-    parsed_employees_json = JSON.parse(return_ptime_employees_json)
-    parsed_employees_json['data'].first["attributes"]["firstname"] = "Rochus"
-    parsed_employees_json['data'].second["attributes"]["firstname"] = "Melchior"
+    parsed_employees_json = ptime_employees
+    parsed_employees_json[:data].first[:attributes][:firstname] = "Rochus"
+    parsed_employees_json[:data].second[:attributes][:firstname] = "Melchior"
+
+
     stub_request(:get, "#{ENV["PTIME_BASE_URL"]}/api/v1/employees?per_page=1000").
       to_return(body: parsed_employees_json.to_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
                                                                                .with(basic_auth: [ENV["PTIME_API_USERNAME"], ENV["PTIME_API_PASSWORD"]])
diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index 12401112e..98cedb545 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -2,7 +2,8 @@
 
 describe Ptime::Client do
     it 'should be able to fetch employee data' do
-        fetched_employees = Ptime::Client.new.request(:get, "employees?per_page=1000")
-        expect(fetched_employees).to eq(JSON.parse(return_ptime_employees_json))
+        fetched_employees = Ptime::Client.new.request(:get, "employees", { per_page: 1000 })
+        ptime_employee_json = fixture_data("all_ptime_employees")
+        expect(fetched_employees).to eq(ptime_employee_json)
     end
 end
diff --git a/spec/domain/ptime/update_people_data_spec.rb b/spec/domain/ptime/update_people_data_spec.rb
index 45d6fd365..fa345dfe9 100644
--- a/spec/domain/ptime/update_people_data_spec.rb
+++ b/spec/domain/ptime/update_people_data_spec.rb
@@ -12,82 +12,9 @@
   end
 
   it 'should update the data of existing people after mapping' do
-    employees = {
-      'data': [
-        {
-          'id': 33,
-          'type': 'employee',
-          'attributes': {
-            'shortname': 'LSM',
-            'firstname': 'Longmax',
-            'lastname': 'Smith',
-            'email': 'longmax@example.com',
-            'marital_status': 'single',
-            'nationalities': [
-              'ZW'
-            ],
-            'graduation': 'BSc in Architecture',
-            'department_shortname': 'SYS',
-            'employment_roles': []
-          }
-        },
-        {
-          'id': 21,
-          'type': 'employee',
-          'attributes': {
-            'shortname': 'AMA',
-            'firstname': 'Alice',
-            'lastname': 'Mante',
-            'email': 'alice@example.com',
-            'marital_status': 'single',
-            'nationalities': [
-              'AU'
-            ],
-            'graduation': 'MSc in writing',
-            'department_shortname': 'SYS',
-            'employment_roles': []
-          }
-        },
-        {
-          'id': 45,
-          'type': 'employee',
-          'attributes': {
-            'shortname': 'CFO',
-            'firstname': 'Charlie',
-            'lastname': 'Ford',
-            'email': 'charlie@example.com',
-            'marital_status': 'married',
-            'nationalities': [
-              'GB'
-            ],
-            'graduation': 'MSc in networking',
-            'department_shortname': 'SYS',
-            'employment_roles': []
-          }
-        },
-        {
-          'id': 50,
-          'type': 'employee',
-          'attributes': {
-            'shortname': 'WAL',
-            'firstname': 'Wally',
-            'lastname': 'Allround',
-            'email': 'wally@example.com',
-            'marital_status': 'married',
-            'nationalities': [
-              'US'
-            ],
-            'graduation': 'Full-Stack Developer',
-            'department_shortname': 'SYS',
-            'employment_roles': []
-          }
-        },
-      ]
-    }
+    employees = fixture_data "updating_ptime_employees"
 
-    stub_request(:get, "#{ptime_base_test_url}/api/v1/employees?per_page=1000").
-      to_return(body: employees.to_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                               .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
+    stub_ptime_request(employees.to_json)
 
     person_longmax = people(:longmax)
     person_alice = people(:alice)
@@ -95,15 +22,14 @@
     person_wally = people(:wally)
 
     Ptime::AssignEmployeeIds.new.run(should_map: true)
+    employees_data = employees[:data]
+    employees_data.first[:attributes][:email] = "changedmax@example.com"
+    employees_data.second[:attributes][:graduation] = "MSc in some other field"
+    employees_data.third[:attributes][:firstname] = "Claudius"
+    employees_data.fourth[:attributes][:marital_status] = "single"
 
-    employees[:data].first[:attributes][:email] = "changedmax@example.com"
-    employees[:data].second[:attributes][:graduation] = "MSc in some other field"
-    employees[:data].third[:attributes][:firstname] = "Claudius"
-    employees[:data].fourth[:attributes][:marital_status] = "single"
 
-    stub_request(:get, "#{ptime_base_test_url}/api/v1/employees?per_page=1000").
-      to_return(body: employees.to_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                               .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
+    stub_ptime_request(employees.to_json)
 
     Ptime::UpdatePeopleData.new.run
 
@@ -114,31 +40,9 @@
   end
 
   it 'should create new person when person does not exist' do
-    new_employee = {
-      'data': [
-        {
-          'id': 33,
-          'type': 'employee',
-          'attributes': {
-            'shortname': 'PFI',
-            'firstname': 'Peterson',
-            'lastname': 'Findus',
-            'email': 'peterson@example.com',
-            'marital_status': 'single',
-            'nationalities': [
-              'ZW'
-            ],
-            'graduation': 'Cat caretaker',
-            'department_shortname': 'CAT',
-            'employment_roles': []
-          }
-        }
-      ]
-    }
+    new_employee =  fixture_data "new_ptime_employee"
 
-    stub_request(:get, "#{ptime_base_test_url}/api/v1/employees?per_page=1000").
-      to_return(body: new_employee.to_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                               .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
+    stub_ptime_request(new_employee.to_json)
 
     Ptime::AssignEmployeeIds.new.run(should_map: true)
     Ptime::UpdatePeopleData.new.run
@@ -158,4 +62,4 @@
     expect(created_person.location).to eq('Bern')
     expect(created_person.nationality).to eq('CH')
   end
-end
\ No newline at end of file
+end
diff --git a/spec/fixtures/files/json/new_ptime_employee.json b/spec/fixtures/files/json/new_ptime_employee.json
new file mode 100644
index 000000000..308d4fcb4
--- /dev/null
+++ b/spec/fixtures/files/json/new_ptime_employee.json
@@ -0,0 +1,21 @@
+{
+    "data": [
+      {
+        "id": 33,
+        "type": "employee",
+        "attributes": {
+          "shortname": "PFI",
+          "firstname": "Peterson",
+          "lastname": "Findus",
+          "email": "peterson@example.com",
+          "marital_status": "single",
+          "nationalities": [
+            "ZW"
+          ],
+          "graduation": "Cat caretaker",
+          "department_shortname": "CAT",
+          "employment_roles": []
+        }
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/spec/fixtures/files/json/updating_ptime_employees.json b/spec/fixtures/files/json/updating_ptime_employees.json
new file mode 100644
index 000000000..14be70aba
--- /dev/null
+++ b/spec/fixtures/files/json/updating_ptime_employees.json
@@ -0,0 +1,72 @@
+{
+    "data": [
+      {
+        "id": 33,
+        "type": "employee",
+        "attributes": {
+          "shortname": "LSM",
+          "firstname": "Longmax",
+          "lastname": "Smith",
+          "email": "longmax@example.com",
+          "marital_status": "single",
+          "nationalities": [
+            "ZW"
+          ],
+          "graduation": "BSc in Architecture",
+          "department_shortname": "SYS",
+          "employment_roles": []
+        }
+      },
+      {
+        "id": 21,
+        "type": "employee",
+        "attributes": {
+          "shortname": "AMA",
+          "firstname": "Alice",
+          "lastname": "Mante",
+          "email": "alice@example.com",
+          "marital_status": "single",
+          "nationalities": [
+            "AU"
+          ],
+          "graduation": "MSc in writing",
+          "department_shortname": "SYS",
+          "employment_roles": []
+        }
+      },
+      {
+        "id": 45,
+        "type": "employee",
+        "attributes": {
+          "shortname": "CFO",
+          "firstname": "Charlie",
+          "lastname": "Ford",
+          "email": "charlie@example.com",
+          "marital_status": "married",
+          "nationalities": [
+            "GB"
+          ],
+          "graduation": "MSc in networking",
+          "department_shortname": "SYS",
+          "employment_roles": []
+        }
+      },
+      {
+        "id": 50,
+        "type": "employee",
+        "attributes": {
+          "shortname": "WAL",
+          "firstname": "Wally",
+          "lastname": "Allround",
+          "email": "wally@example.com",
+          "marital_status": "married",
+          "nationalities": [
+            "US"
+          ],
+          "graduation": "Full-Stack Developer",
+          "department_shortname": "SYS",
+          "employment_roles": []
+        }
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/spec/helpers/person_helper_spec.rb b/spec/helpers/person_helper_spec.rb
index 9072eceab..e5c2d93bb 100644
--- a/spec/helpers/person_helper_spec.rb
+++ b/spec/helpers/person_helper_spec.rb
@@ -24,8 +24,8 @@
       alice.update!(ptime_employee_id: 21)
       charlie.update!(ptime_employee_id: 45)
 
-      parsed_employees = JSON.parse(return_ptime_employees_json)
-      dropdown_data = build_dropdown_data(parsed_employees['data'], Person.all.pluck(:ptime_employee_id))
+      parsed_employees = ptime_employees
+      dropdown_data = build_dropdown_data(parsed_employees[:data], Person.all.pluck(:ptime_employee_id))
 
       expect(dropdown_data[0][:id]).to eq(longmax.id)
       expect(dropdown_data[0][:ptime_employee_id]).to eq(longmax.ptime_employee_id)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index dacc6e9ba..4e8d29fe8 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -66,6 +66,8 @@
     set_env_variables_and_stub_request
   end
 
+  config.before { allow($stdout).to receive(:puts) }
+
   # Controller helper
   config.include(JsonMacros, type: :controller)
   config.include(JsonAssertion, type: :controller)
diff --git a/spec/support/json_helpers.rb b/spec/support/json_helpers.rb
index fa66633ac..66c213946 100644
--- a/spec/support/json_helpers.rb
+++ b/spec/support/json_helpers.rb
@@ -1,6 +1,7 @@
 module JsonHelpers
 
-  def json_data(filename:)
-    file_fixture("json/#{filename}.json").read
+  def fixture_data(filename, symbolize_names= true)
+    file_content= file_fixture("json/#{filename}.json").read
+    JSON.parse(file_content, symbolize_names: symbolize_names)
   end
 end
diff --git a/spec/support/ptime_helpers.rb b/spec/support/ptime_helpers.rb
index cbfda4d06..45aa3fd41 100644
--- a/spec/support/ptime_helpers.rb
+++ b/spec/support/ptime_helpers.rb
@@ -7,12 +7,20 @@ def set_env_variables_and_stub_request
         ENV["PTIME_API_USERNAME"] = ptime_api_test_username
         ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
 
-        stub_request(:get, "http://#{ptime_base_test_url}/api/v1/employees?per_page=1000").
-        to_return(body: return_ptime_employees_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                    .with(basic_auth: [ptime_api_test_username, ptime_api_test_password])
+        stub_ptime_request(ptime_employees.to_json)
     end
 
-    def return_ptime_employees_json
-        json_data filename: "all_ptime_employees"
+    def ptime_employees
+        fixture_data "all_ptime_employees"
+    end
+
+    def stub_ptime_request(return_body, path =nil)
+        path ||= "employees?per_page=1000"
+        url = "http://#{ENV["PTIME_BASE_URL"]}/api/v1/#{path}"
+        content_type = "application/vnd.api+json; charset=utf-8"
+
+        stub_request(:get, url)
+            .to_return(body: return_body, headers: { 'content-type': content_type }, status: 200)
+            .with(basic_auth: [ENV["PTIME_API_USERNAME"], ENV["PTIME_API_PASSWORD"]])
     end
 end

From d3bb9c3d28e93b5bf353cd74a3d5b337e0fbcf09 Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 12:15:17 +0200
Subject: [PATCH 27/37] use custom ptime stubbing method everywhere

---
 spec/domain/ptime/assign_employee_ids_spec.rb | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/spec/domain/ptime/assign_employee_ids_spec.rb b/spec/domain/ptime/assign_employee_ids_spec.rb
index 464260b13..3e8f01a6c 100644
--- a/spec/domain/ptime/assign_employee_ids_spec.rb
+++ b/spec/domain/ptime/assign_employee_ids_spec.rb
@@ -20,9 +20,7 @@
     parsed_employees_json[:data].second[:attributes][:firstname] = "Melchior"
 
 
-    stub_request(:get, "#{ENV["PTIME_BASE_URL"]}/api/v1/employees?per_page=1000").
-      to_return(body: parsed_employees_json.to_json, headers: { 'content-type': "application/vnd.api+json; charset=utf-8" }, status: 200)
-                                                                               .with(basic_auth: [ENV["PTIME_API_USERNAME"], ENV["PTIME_API_PASSWORD"]])
+    stub_ptime_request(parsed_employees_json.to_json)
 
     person_longmax = people(:longmax)
     person_alice = people(:alice)

From dd969005648b47201586af566c5a2058e1e50e1a Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 12:37:05 +0200
Subject: [PATCH 28/37] fix tests after always using [:data]

---
 app/domain/ptime/assign_employee_ids.rb      |  2 +-
 app/domain/ptime/client.rb                   |  2 +-
 app/domain/ptime/update_people_data.rb       |  2 +-
 app/helpers/person_helper.rb                 |  2 +-
 spec/domain/ptime/client_spec.rb             |  3 +--
 spec/domain/ptime/update_people_data_spec.rb | 15 +++++++--------
 spec/helpers/person_helper_spec.rb           |  3 +--
 spec/support/ptime_helpers.rb                |  6 +++++-
 8 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/app/domain/ptime/assign_employee_ids.rb b/app/domain/ptime/assign_employee_ids.rb
index c25518004..b916ad6fc 100644
--- a/app/domain/ptime/assign_employee_ids.rb
+++ b/app/domain/ptime/assign_employee_ids.rb
@@ -60,7 +60,7 @@ def map_employees(should_map)
 
     def fetch_data
       puts 'Fetching required data...'
-      @ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })[:data]
+      @ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })
       @skills_people = Person.all
       puts 'Successfully fetched data'
     end
diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index f9e528ff0..0dc50d13a 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -52,7 +52,7 @@ def send_request_and_parse_response(method, path, params)
       ENV['PTIME_API_ACCESSIBLE'] = 'true'
       path = "#{path}?#{URI.encode_www_form(params)}" if method == :get && params.present?
       response = RestClient.send(method, path, headers)
-      JSON.parse(response.body, symbolize_names: true)
+      JSON.parse(response.body, symbolize_names: true)[:data]
     end
 
     def skills_database_request
diff --git a/app/domain/ptime/update_people_data.rb b/app/domain/ptime/update_people_data.rb
index 1edcf2de5..b785f1f0e 100644
--- a/app/domain/ptime/update_people_data.rb
+++ b/app/domain/ptime/update_people_data.rb
@@ -7,7 +7,7 @@ class UpdatePeopleData
 
     # rubocop:disable Metrics
     def run
-      ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })[:data]
+      ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })
       ptime_employees.each do |ptime_employee|
         ptime_employee_firstname = ptime_employee[:attributes][:firstname]
         ptime_employee_lastname = ptime_employee[:attributes][:lastname]
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 23fc3456b..59dd9e568 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -78,7 +78,7 @@ def not_rated_default_skills(person)
   end
 
   def fetch_ptime_or_skills_data
-    ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })[:data]
+    ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })
     all_skills_people = Person.all
     return all_skills_people if ENV['PTIME_API_ACCESSIBLE'] == 'false'
 
diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index 98cedb545..4f0af30a2 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -3,7 +3,6 @@
 describe Ptime::Client do
     it 'should be able to fetch employee data' do
         fetched_employees = Ptime::Client.new.request(:get, "employees", { per_page: 1000 })
-        ptime_employee_json = fixture_data("all_ptime_employees")
-        expect(fetched_employees).to eq(ptime_employee_json)
+        expect(fetched_employees).to eq(ptime_employees_data)
     end
 end
diff --git a/spec/domain/ptime/update_people_data_spec.rb b/spec/domain/ptime/update_people_data_spec.rb
index fa345dfe9..8e3273f85 100644
--- a/spec/domain/ptime/update_people_data_spec.rb
+++ b/spec/domain/ptime/update_people_data_spec.rb
@@ -22,11 +22,10 @@
     person_wally = people(:wally)
 
     Ptime::AssignEmployeeIds.new.run(should_map: true)
-    employees_data = employees[:data]
-    employees_data.first[:attributes][:email] = "changedmax@example.com"
-    employees_data.second[:attributes][:graduation] = "MSc in some other field"
-    employees_data.third[:attributes][:firstname] = "Claudius"
-    employees_data.fourth[:attributes][:marital_status] = "single"
+    employees[:data].first[:attributes][:email] = "changedmax@example.com"
+    employees[:data].second[:attributes][:graduation] = "MSc in some other field"
+    employees[:data].third[:attributes][:firstname] = "Claudius"
+    employees[:data].fourth[:attributes][:marital_status] = "single"
 
 
     stub_ptime_request(employees.to_json)
@@ -41,17 +40,17 @@
 
   it 'should create new person when person does not exist' do
     new_employee =  fixture_data "new_ptime_employee"
-
+    new_employee_data = new_employee[:data]
     stub_ptime_request(new_employee.to_json)
 
     Ptime::AssignEmployeeIds.new.run(should_map: true)
     Ptime::UpdatePeopleData.new.run
 
-    new_employee_attributes = new_employee[:data].first[:attributes]
+    new_employee_attributes = new_employee_data.first[:attributes]
     new_employee_name = "#{new_employee_attributes[:firstname]} #{new_employee_attributes[:lastname]}"
     created_person = Person.find_by(name: new_employee_name)
     expect(created_person).not_to be_nil
-    expect(created_person.ptime_employee_id).to eq(new_employee[:data].first[:id])
+    expect(created_person.ptime_employee_id).to eq(new_employee_data.first[:id])
     expect(created_person.shortname).to eq(new_employee_attributes[:shortname])
     expect(created_person.name).to eq(new_employee_name)
     expect(created_person.email).to eq(new_employee_attributes[:email])
diff --git a/spec/helpers/person_helper_spec.rb b/spec/helpers/person_helper_spec.rb
index e5c2d93bb..1503d152c 100644
--- a/spec/helpers/person_helper_spec.rb
+++ b/spec/helpers/person_helper_spec.rb
@@ -24,8 +24,7 @@
       alice.update!(ptime_employee_id: 21)
       charlie.update!(ptime_employee_id: 45)
 
-      parsed_employees = ptime_employees
-      dropdown_data = build_dropdown_data(parsed_employees[:data], Person.all.pluck(:ptime_employee_id))
+      dropdown_data = build_dropdown_data(ptime_employees_data, Person.all.pluck(:ptime_employee_id))
 
       expect(dropdown_data[0][:id]).to eq(longmax.id)
       expect(dropdown_data[0][:ptime_employee_id]).to eq(longmax.ptime_employee_id)
diff --git a/spec/support/ptime_helpers.rb b/spec/support/ptime_helpers.rb
index 45aa3fd41..aee588ad5 100644
--- a/spec/support/ptime_helpers.rb
+++ b/spec/support/ptime_helpers.rb
@@ -11,7 +11,11 @@ def set_env_variables_and_stub_request
     end
 
     def ptime_employees
-        fixture_data "all_ptime_employees"
+        fixture_data("all_ptime_employees")
+    end
+
+    def ptime_employees_data
+        fixture_data("all_ptime_employees")[:data]
     end
 
     def stub_ptime_request(return_body, path =nil)

From 0d1c4ff67d160bd4ccb1f7b8468b9a84fdfcf5eb Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 13:16:08 +0200
Subject: [PATCH 29/37] clean up ptime client

---
 app/domain/ptime/client.rb | 28 +++++++++++++---------------
 1 file changed, 13 insertions(+), 15 deletions(-)

diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index 0dc50d13a..548133572 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -29,18 +29,6 @@ def response_error_message(exception)
       nil
     end
 
-    def headers
-      {
-        authorization: "Basic #{basic_token}",
-        content_type: :json,
-        accept: :json
-      }
-    end
-
-    def basic_token
-      Base64.encode64("#{ENV.fetch('PTIME_API_USERNAME')}:#{ENV.fetch('PTIME_API_PASSWORD')}")
-    end
-
     def last_ptime_api_request_more_than_5_minutes
       last_request_time = ENV.fetch('LAST_PTIME_API_REQUEST', nil)
       return true if last_request_time.nil?
@@ -48,10 +36,20 @@ def last_ptime_api_request_more_than_5_minutes
       last_request_time.to_datetime <= 5.minutes.ago
     end
 
-    def send_request_and_parse_response(method, path, params)
+    def ptime_request(method, url)
+      RestClient::Request.new(
+        :method => method,
+        :url => url,
+        :user => ENV.fetch('PTIME_API_USERNAME'),
+        :password => ENV.fetch('PTIME_API_PASSWORD'),
+        :headers => { :accept => :json, :content_type => :json }
+      )
+    end
+
+    def send_request_and_parse_response(method, url, params)
       ENV['PTIME_API_ACCESSIBLE'] = 'true'
-      path = "#{path}?#{URI.encode_www_form(params)}" if method == :get && params.present?
-      response = RestClient.send(method, path, headers)
+      url += "?#{params.to_query}" if method == :get && params.present?
+      response = ptime_request(method, url).execute
       JSON.parse(response.body, symbolize_names: true)[:data]
     end
 

From 7550234abbe0d586b94656f1e461ad7343df6711 Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 14:39:35 +0200
Subject: [PATCH 30/37] improve structure of people search

---
 app/helpers/auth_helper.rb                    |  4 ++--
 app/helpers/person_helper.rb                  | 24 ++++++++++++-------
 app/helpers/select_helper.rb                  |  4 ++--
 .../controllers/dropdown_controller.js        |  2 +-
 app/views/people/_search.html.haml            |  7 +-----
 5 files changed, 21 insertions(+), 20 deletions(-)

diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index b5e216197..c5cfbcdfc 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -22,7 +22,7 @@ def devise?
     AuthConfig.keycloak? || Rails.env.test?
   end
 
-  def ptime_broken?
-    !ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE', true))
+  def ptime_available?
+    ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE', true))
   end
 end
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 59dd9e568..215546a7c 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -77,12 +77,18 @@ def not_rated_default_skills(person)
     end
   end
 
+  def sorted_people
+    fetch_ptime_or_skills_data.sort_by(&:first)
+  end
+
   def fetch_ptime_or_skills_data
+    all_skills_people = Person.all.map { |p| [p.name, person_path(p)] }
+
+    return all_skills_people unless ptime_available?
+
     ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })
-    all_skills_people = Person.all
-    return all_skills_people if ENV['PTIME_API_ACCESSIBLE'] == 'false'
 
-    ptime_employee_ids = all_skills_people.pluck(:ptime_employee_id)
+    ptime_employee_ids = Person.pluck(:ptime_employee_id)
     build_dropdown_data(ptime_employees, ptime_employee_ids)
   end
 
@@ -90,12 +96,12 @@ def build_dropdown_data(ptime_employees, ptime_employee_ids)
     ptime_employees.map do |ptime_employee|
       ptime_employee_name = append_ptime_employee_name(ptime_employee)
       person_id = map_ptime_employee_id(ptime_employee)
-      {
-        id: person_id,
-        ptime_employee_id: ptime_employee[:id],
-        name: ptime_employee_name,
-        already_exists: ptime_employee[:id].in?(ptime_employee_ids)
-      }
+      ptime_employee_id = ptime_employee[:id]
+      already_exists = ptime_employee_id.in?(ptime_employee_ids)
+
+      path = person_path(person_id)
+      path = new_person_path(ptime_employee_id: ptime_employee_id) unless already_exists
+      [ptime_employee_name, path]
     end
   end
 
diff --git a/app/helpers/select_helper.rb b/app/helpers/select_helper.rb
index fa69b73b2..5183f0401 100644
--- a/app/helpers/select_helper.rb
+++ b/app/helpers/select_helper.rb
@@ -2,8 +2,8 @@
 
 module SelectHelper
   def select_when_availabale(obj)
-    selected = obj ? obj.id : ''
-    prompt = obj ? false : true
+    selected = obj.present? ? polymorphic_path(obj) : ''
+    prompt = obj.nil?
     { selected: selected, prompt: prompt, disabled: '' }
   end
 
diff --git a/app/javascript/controllers/dropdown_controller.js b/app/javascript/controllers/dropdown_controller.js
index 370e15e4c..e9a88982f 100644
--- a/app/javascript/controllers/dropdown_controller.js
+++ b/app/javascript/controllers/dropdown_controller.js
@@ -11,6 +11,6 @@ export default class extends Controller {
   }
 
   handleChange(event) {
-    window.location.href = event.target.dataset.value + event.target.value;
+    window.location.href = event.target.value;
   }
 }
diff --git a/app/views/people/_search.html.haml b/app/views/people/_search.html.haml
index 23ea31441..ebc4c114a 100644
--- a/app/views/people/_search.html.haml
+++ b/app/views/people/_search.html.haml
@@ -1,12 +1,7 @@
 %div.d-flex.align-items-center.justify-content-between
   %div.d-flex.col-9.gap-3
     %span.col-6{"data-controller": "dropdown"}
-      - people_data = fetch_ptime_or_skills_data
-      - if ptime_broken? # people_data == skills
-        = collection_select :person_id, :person, people_data.sort_by {|p| p.name.downcase }, :id, :name, select_when_availabale(person), {data:{"dropdown-target": "dropdown" , action: "change->dropdown#handleChange", value: "/people/"}}        
-      - elsif ENV['PTIME_API_ACCESSIBLE'] = 'true' # people_data == time
-        - sorted_employees = people_data.sort_by { |p| p[:name] }
-        = select :person_id, :person, options_for_select(sorted_employees.map { |p| [ p[:name], p[:already_exists] ? p[:id] : "new?ptime_employee_id=#{p[:ptime_employee_id]}" ] }), {}, {data: { "dropdown-target" => "dropdown", action: "change->dropdown#handleChange", value: "/people/" }}
+      = select :person_id, :person, options_for_select(sorted_people, select_when_availabale(person)), {prompt: person.nil?}, {data: { "dropdown-target" => "dropdown", action: "change->dropdown#handleChange"}}
     %div.d-flex.align-items-center.text-gray
       = "#{t 'profile.updated_at'}: #{@person&.last_updated_at.strftime("%d.%m.%Y")}" if @person&.last_updated_at
   %a.d-flex.justify-content-between#new-person-button{href: "/people/new"}

From 552cf7d92edf96717e9acf950db7ae7d2179113b Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 17:09:03 +0200
Subject: [PATCH 31/37] Client isn't longer controlled by random env vars

---
 app/domain/ptime/client.rb          | 11 ++-----
 app/exceptions/custom_exceptions.rb |  5 +++
 app/helpers/person_helper.rb        | 18 ++++++-----
 spec/helpers/person_helper_spec.rb  | 47 +++++++++++++++++------------
 4 files changed, 44 insertions(+), 37 deletions(-)
 create mode 100644 app/exceptions/custom_exceptions.rb

diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index 548133572..4ba7c9bbd 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -14,10 +14,10 @@ def request(method, endpoint, params = {})
       if last_ptime_api_request_more_than_5_minutes
         send_request_and_parse_response(method, path, params)
       else
-        skills_database_request
+        raise CustomExceptions::PTimeError, 'Error'
       end
     rescue RestClient::ExceptionWithResponse
-      skills_database_request
+      raise CustomExceptions::PTimeError, 'Error'
     end
 
     private
@@ -47,16 +47,9 @@ def ptime_request(method, url)
     end
 
     def send_request_and_parse_response(method, url, params)
-      ENV['PTIME_API_ACCESSIBLE'] = 'true'
       url += "?#{params.to_query}" if method == :get && params.present?
       response = ptime_request(method, url).execute
       JSON.parse(response.body, symbolize_names: true)[:data]
     end
-
-    def skills_database_request
-      ENV['PTIME_API_ACCESSIBLE'] = 'false'
-      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
-      {}
-    end
   end
 end
diff --git a/app/exceptions/custom_exceptions.rb b/app/exceptions/custom_exceptions.rb
new file mode 100644
index 000000000..fe50cc70b
--- /dev/null
+++ b/app/exceptions/custom_exceptions.rb
@@ -0,0 +1,5 @@
+module CustomExceptions
+
+  class PTimeError < StandardError; end
+
+end
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 215546a7c..200732d8e 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -78,18 +78,20 @@ def not_rated_default_skills(person)
   end
 
   def sorted_people
-    fetch_ptime_or_skills_data.sort_by(&:first)
+    fetch_ptime_or_skills_data.sort_by { |e| e.first.downcase }
   end
 
   def fetch_ptime_or_skills_data
     all_skills_people = Person.all.map { |p| [p.name, person_path(p)] }
-
     return all_skills_people unless ptime_available?
 
-    ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })
-
-    ptime_employee_ids = Person.pluck(:ptime_employee_id)
-    build_dropdown_data(ptime_employees, ptime_employee_ids)
+    begin
+      ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })
+      ptime_employee_ids = Person.pluck(:ptime_employee_id)
+      build_dropdown_data(ptime_employees, ptime_employee_ids)
+    rescue CustomExceptions::PTimeError
+      all_skills_people
+    end
   end
 
   def build_dropdown_data(ptime_employees, ptime_employee_ids)
@@ -98,9 +100,9 @@ def build_dropdown_data(ptime_employees, ptime_employee_ids)
       person_id = map_ptime_employee_id(ptime_employee)
       ptime_employee_id = ptime_employee[:id]
       already_exists = ptime_employee_id.in?(ptime_employee_ids)
+      path = new_person_path(ptime_employee_id: ptime_employee_id)
+      path = person_path(person_id) if already_exists
 
-      path = person_path(person_id)
-      path = new_person_path(ptime_employee_id: ptime_employee_id) unless already_exists
       [ptime_employee_name, path]
     end
   end
diff --git a/spec/helpers/person_helper_spec.rb b/spec/helpers/person_helper_spec.rb
index 1503d152c..76946558d 100644
--- a/spec/helpers/person_helper_spec.rb
+++ b/spec/helpers/person_helper_spec.rb
@@ -4,13 +4,30 @@
   describe '#fetch_ptime_or_skills_data' do
 
     it 'should send request to ptime api' do
-      fetch_ptime_or_skills_data
+      allow(helper).to receive(:ptime_available?).and_return(true)
+      expected = skills_people = helper.fetch_ptime_or_skills_data
+      [
+        ["Longmax Smith", "/people/new?ptime_employee_id=33"],
+        ["Alice Mante", "/people/new?ptime_employee_id=21"],
+        ["Charlie Ford", "/people/new?ptime_employee_id=45"]
+      ]
+      expect(skills_people).to eq(expected)
     end
 
     it 'should return people from skills database if last request was right now' do
-      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s
-      skills_people = fetch_ptime_or_skills_data
-      expect(skills_people).to eq(Person.all)
+      allow(helper).to receive(:ptime_available?).and_return(false)
+
+      skills_people = helper.fetch_ptime_or_skills_data
+      expected = [
+        ["Bob Anderson", "/people/902541635"],
+        ["Alice Mante", "/people/663665735"],
+        ["ken", "/people/155397742"],
+        ["Charlie Ford", "/people/786122151"],
+        ["Wally Allround", "/people/790004949"],
+        ["Hope Sunday", "/people/247095502"],
+        ["Longmax Smith", "/people/169654640"]
+      ]
+      expect(skills_people).to eq(expected)
     end
   end
 
@@ -18,28 +35,18 @@
     it 'should build correct dropdown data' do
       longmax = people(:longmax)
       alice = people(:alice)
-      charlie = people(:charlie)
+      people(:charlie)
 
       longmax.update!(ptime_employee_id: 33)
       alice.update!(ptime_employee_id: 21)
-      charlie.update!(ptime_employee_id: 45)
 
       dropdown_data = build_dropdown_data(ptime_employees_data, Person.all.pluck(:ptime_employee_id))
+      expected = [
+        ["Longmax Smith", "/people/169654640"],
+        ["Alice Mante", "/people/663665735"],
+        ["Charlie Ford", "/people/new?ptime_employee_id=45"]]
+      expect(dropdown_data).to eq(expected)
 
-      expect(dropdown_data[0][:id]).to eq(longmax.id)
-      expect(dropdown_data[0][:ptime_employee_id]).to eq(longmax.ptime_employee_id)
-      expect(dropdown_data[0][:name]).to eq("Longmax Smith")
-      expect(dropdown_data[0][:already_exists]).to be(true)
-
-      expect(dropdown_data[1][:id]).to eq(alice.id)
-      expect(dropdown_data[1][:ptime_employee_id]).to eq(alice.ptime_employee_id)
-      expect(dropdown_data[1][:name]).to eq("Alice Mante")
-      expect(dropdown_data[1][:already_exists]).to be(true)
-
-      expect(dropdown_data[2][:id]).to eq(charlie.id)
-      expect(dropdown_data[2][:ptime_employee_id]).to eq(charlie.ptime_employee_id)
-      expect(dropdown_data[2][:name]).to eq("Charlie Ford")
-      expect(dropdown_data[2][:already_exists]).to be(true)
     end
   end
 end

From d9c02be8a22e9ed4cee1b95f8b83ba04584a2c6d Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Thu, 25 Jul 2024 17:19:20 +0200
Subject: [PATCH 32/37] simplify ptime_helpers

---
 spec/support/ptime_helpers.rb | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/spec/support/ptime_helpers.rb b/spec/support/ptime_helpers.rb
index aee588ad5..ca40110a4 100644
--- a/spec/support/ptime_helpers.rb
+++ b/spec/support/ptime_helpers.rb
@@ -1,11 +1,8 @@
 module PtimeHelpers
     def set_env_variables_and_stub_request
-        ptime_base_test_url = "www.ptime.example.com"
-        ptime_api_test_username = "test username"
-        ptime_api_test_password = "test password"
-        ENV["PTIME_BASE_URL"] = ptime_base_test_url
-        ENV["PTIME_API_USERNAME"] = ptime_api_test_username
-        ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
+        ENV["PTIME_BASE_URL"] = "www.ptime.example.com"
+        ENV["PTIME_API_USERNAME"] = "test username"
+        ENV["PTIME_API_PASSWORD"] = "test password"
 
         stub_ptime_request(ptime_employees.to_json)
     end

From 44d76bd9549742ae33b7519c52d6e383ee81a657 Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Fri, 26 Jul 2024 09:41:25 +0200
Subject: [PATCH 33/37] clean up PR

---
 app/helpers/person_helper.rb                 | 10 +---------
 db/schema.rb                                 | 17 +----------------
 spec/domain/ptime/update_people_data_spec.rb | 10 ----------
 3 files changed, 2 insertions(+), 35 deletions(-)

diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 200732d8e..7f06697c5 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -97,7 +97,7 @@ def fetch_ptime_or_skills_data
   def build_dropdown_data(ptime_employees, ptime_employee_ids)
     ptime_employees.map do |ptime_employee|
       ptime_employee_name = append_ptime_employee_name(ptime_employee)
-      person_id = map_ptime_employee_id(ptime_employee)
+      person_id = Person.find_by(ptime_employee_id: ptime_employee[:id])
       ptime_employee_id = ptime_employee[:id]
       already_exists = ptime_employee_id.in?(ptime_employee_ids)
       path = new_person_path(ptime_employee_id: ptime_employee_id)
@@ -107,14 +107,6 @@ def build_dropdown_data(ptime_employees, ptime_employee_ids)
     end
   end
 
-  def map_ptime_employee_id(ptime_employee)
-    ptime_employee_id_map = Person.all.each_with_object({}) do |person, hash|
-      hash[person.ptime_employee_id.to_s] = person.id
-    end
-
-    ptime_employee_id_map[ptime_employee[:id].to_s]
-  end
-
   # Once https://github.com/puzzle/skills/issues/744 is merged there should be no need for this
   def append_ptime_employee_name(ptime_employee)
     "#{ptime_employee[:attributes][:firstname]} #{ptime_employee[:attributes][:lastname]}"
diff --git a/db/schema.rb b/db/schema.rb
index cfb6306f6..038f495fa 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[7.0].define(version: 2024_07_01_085558) do
+ActiveRecord::Schema[7.0].define(version: 2024_06_24_122411) do
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
 
@@ -77,21 +77,6 @@
     t.datetime "updated_at", precision: nil, null: false
   end
 
-  create_table "delayed_jobs", force: :cascade do |t|
-    t.integer "priority", default: 0, null: false
-    t.integer "attempts", default: 0, null: false
-    t.text "handler", null: false
-    t.text "last_error"
-    t.datetime "run_at"
-    t.datetime "locked_at"
-    t.datetime "failed_at"
-    t.string "locked_by"
-    t.string "queue"
-    t.datetime "created_at"
-    t.datetime "updated_at"
-    t.index ["priority", "run_at"], name: "delayed_jobs_priority"
-  end
-
   create_table "departments", force: :cascade do |t|
     t.string "name", null: false
     t.datetime "created_at", null: false
diff --git a/spec/domain/ptime/update_people_data_spec.rb b/spec/domain/ptime/update_people_data_spec.rb
index 8e3273f85..dd38186df 100644
--- a/spec/domain/ptime/update_people_data_spec.rb
+++ b/spec/domain/ptime/update_people_data_spec.rb
@@ -1,16 +1,6 @@
 require 'rails_helper'
 
-ptime_base_test_url = "www.ptime.example.com"
-ptime_api_test_username = "test username"
-ptime_api_test_password = "test password"
-ENV["PTIME_API_USERNAME"] = ptime_api_test_username
-ENV["PTIME_API_PASSWORD"] = ptime_api_test_password
-
 describe Ptime::UpdatePeopleData do
-  before(:each) do
-    ENV["PTIME_BASE_URL"] = ptime_base_test_url
-  end
-
   it 'should update the data of existing people after mapping' do
     employees = fixture_data "updating_ptime_employees"
 

From 71db98a099461e040720df0b0aa0a74a5f4a4daa Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Fri, 26 Jul 2024 11:54:03 +0200
Subject: [PATCH 34/37] Clean up Skills db fallback logic

---
 app/domain/ptime/client.rb          | 20 +++++++-------------
 app/exceptions/custom_exceptions.rb |  2 +-
 app/helpers/person_helper.rb        | 13 ++++++-------
 spec/domain/ptime/client_spec.rb    | 13 +++++++++++++
 spec/features/people_spec.rb        |  2 +-
 spec/helpers/person_helper_spec.rb  |  4 ++--
 6 files changed, 30 insertions(+), 24 deletions(-)

diff --git a/app/domain/ptime/client.rb b/app/domain/ptime/client.rb
index 4ba7c9bbd..254305b17 100644
--- a/app/domain/ptime/client.rb
+++ b/app/domain/ptime/client.rb
@@ -11,26 +11,18 @@ def initialize
 
     def request(method, endpoint, params = {})
       path = @base_url + endpoint
-      if last_ptime_api_request_more_than_5_minutes
+
+      if last_ptime_error_more_than_5_minutes_ago?
         send_request_and_parse_response(method, path, params)
       else
-        raise CustomExceptions::PTimeError, 'Error'
+        raise CustomExceptions::PTimeClientError, 'Error'
       end
-    rescue RestClient::ExceptionWithResponse
-      raise CustomExceptions::PTimeError, 'Error'
     end
 
     private
 
-    # Currently not in use can be removed
-    def response_error_message(exception)
-      JSON.parse(exception.response.body).dig('error', 'message')
-    rescue JSON::ParserError # rescue only JSON parsing errors
-      nil
-    end
-
-    def last_ptime_api_request_more_than_5_minutes
-      last_request_time = ENV.fetch('LAST_PTIME_API_REQUEST', nil)
+    def last_ptime_error_more_than_5_minutes_ago?
+      last_request_time = ENV.fetch('LAST_PTIME_ERROR', nil)
       return true if last_request_time.nil?
 
       last_request_time.to_datetime <= 5.minutes.ago
@@ -50,6 +42,8 @@ def send_request_and_parse_response(method, url, params)
       url += "?#{params.to_query}" if method == :get && params.present?
       response = ptime_request(method, url).execute
       JSON.parse(response.body, symbolize_names: true)[:data]
+    rescue RestClient::ExceptionWithResponse
+      raise CustomExceptions::PTimeClientError, 'Error'
     end
   end
 end
diff --git a/app/exceptions/custom_exceptions.rb b/app/exceptions/custom_exceptions.rb
index fe50cc70b..b59a9ef87 100644
--- a/app/exceptions/custom_exceptions.rb
+++ b/app/exceptions/custom_exceptions.rb
@@ -1,5 +1,5 @@
 module CustomExceptions
 
-  class PTimeError < StandardError; end
+  class PTimeClientError < StandardError; end
 
 end
diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 7f06697c5..10661b7b2 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -85,13 +85,12 @@ def fetch_ptime_or_skills_data
     all_skills_people = Person.all.map { |p| [p.name, person_path(p)] }
     return all_skills_people unless ptime_available?
 
-    begin
-      ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })
-      ptime_employee_ids = Person.pluck(:ptime_employee_id)
-      build_dropdown_data(ptime_employees, ptime_employee_ids)
-    rescue CustomExceptions::PTimeError
-      all_skills_people
-    end
+    ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })
+    ptime_employee_ids = Person.pluck(:ptime_employee_id)
+    build_dropdown_data(ptime_employees, ptime_employee_ids)
+  rescue CustomExceptions::PTimeClientError
+    ENV['LAST_PTIME_ERROR'] = DateTime.current.to_s
+    all_skills_people
   end
 
   def build_dropdown_data(ptime_employees, ptime_employee_ids)
diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index 4f0af30a2..2ac931ff7 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -5,4 +5,17 @@
         fetched_employees = Ptime::Client.new.request(:get, "employees", { per_page: 1000 })
         expect(fetched_employees).to eq(ptime_employees_data)
     end
+
+    it 'should not raise PTimeClientError if LAST_PTIME_ERROR is less than 5 minutes ago' do
+        ENV['LAST_PTIME_ERROR'] = 6.minutes.ago.to_s
+        fetched_employees = Ptime::Client.new.request(:get, "employees", { per_page: 1000 })
+        expect(fetched_employees).to eq(ptime_employees_data)
+    end
+
+    it 'should raise PTimeClientError if LAST_PTIME_ERROR is less than 5 minutes ago' do
+        ENV['LAST_PTIME_ERROR'] = 4.minutes.ago.to_s
+        expect {
+            Ptime::Client.new.request(:get, "employees", { per_page: 1000 })
+        }.to raise_error(CustomExceptions::PTimeClientError)
+    end
 end
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index 9cfda38c6..e1cb4d115 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -4,7 +4,7 @@
   describe 'People Search', type: :feature, js: true do
 
     before(:each) do
-      ENV['LAST_PTIME_API_REQUEST'] = DateTime.current.to_s # This is needed to activate the fallback and test for the skills people
+      ENV['LAST_PTIME_ERROR'] = DateTime.current.to_s # This is needed to activate the fallback and test for the skills people
       sign_in auth_users(:user), scope: :auth_user
     end
 
diff --git a/spec/helpers/person_helper_spec.rb b/spec/helpers/person_helper_spec.rb
index 76946558d..9d4af9c7c 100644
--- a/spec/helpers/person_helper_spec.rb
+++ b/spec/helpers/person_helper_spec.rb
@@ -5,8 +5,8 @@
 
     it 'should send request to ptime api' do
       allow(helper).to receive(:ptime_available?).and_return(true)
-      expected = skills_people = helper.fetch_ptime_or_skills_data
-      [
+      skills_people = helper.fetch_ptime_or_skills_data
+      expected = [
         ["Longmax Smith", "/people/new?ptime_employee_id=33"],
         ["Alice Mante", "/people/new?ptime_employee_id=21"],
         ["Charlie Ford", "/people/new?ptime_employee_id=45"]

From 3a7997a42a7bf4d43edfb10e1d269a4f0e01b7ca Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Fri, 26 Jul 2024 12:26:28 +0200
Subject: [PATCH 35/37] improve readability by using more helper

---
 app/helpers/person_helper.rb       |  7 +++----
 spec/features/people_spec.rb       |  2 +-
 spec/helpers/person_helper_spec.rb |  2 +-
 spec/rails_helper.rb               |  2 +-
 spec/support/ptime_helpers.rb      | 20 +++++++++++++++-----
 5 files changed, 21 insertions(+), 12 deletions(-)

diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index 10661b7b2..ac25c245b 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -86,19 +86,18 @@ def fetch_ptime_or_skills_data
     return all_skills_people unless ptime_available?
 
     ptime_employees = Ptime::Client.new.request(:get, 'employees', { per_page: 1000 })
-    ptime_employee_ids = Person.pluck(:ptime_employee_id)
-    build_dropdown_data(ptime_employees, ptime_employee_ids)
+    build_dropdown_data(ptime_employees)
   rescue CustomExceptions::PTimeClientError
     ENV['LAST_PTIME_ERROR'] = DateTime.current.to_s
     all_skills_people
   end
 
-  def build_dropdown_data(ptime_employees, ptime_employee_ids)
+  def build_dropdown_data(ptime_employees)
     ptime_employees.map do |ptime_employee|
       ptime_employee_name = append_ptime_employee_name(ptime_employee)
       person_id = Person.find_by(ptime_employee_id: ptime_employee[:id])
       ptime_employee_id = ptime_employee[:id]
-      already_exists = ptime_employee_id.in?(ptime_employee_ids)
+      already_exists = ptime_employee_id.in?(Person.pluck(:ptime_employee_id))
       path = new_person_path(ptime_employee_id: ptime_employee_id)
       path = person_path(person_id) if already_exists
 
diff --git a/spec/features/people_spec.rb b/spec/features/people_spec.rb
index e1cb4d115..77f7e4c6d 100644
--- a/spec/features/people_spec.rb
+++ b/spec/features/people_spec.rb
@@ -4,7 +4,7 @@
   describe 'People Search', type: :feature, js: true do
 
     before(:each) do
-      ENV['LAST_PTIME_ERROR'] = DateTime.current.to_s # This is needed to activate the fallback and test for the skills people
+      use_skills_db
       sign_in auth_users(:user), scope: :auth_user
     end
 
diff --git a/spec/helpers/person_helper_spec.rb b/spec/helpers/person_helper_spec.rb
index 9d4af9c7c..d832ed96b 100644
--- a/spec/helpers/person_helper_spec.rb
+++ b/spec/helpers/person_helper_spec.rb
@@ -40,7 +40,7 @@
       longmax.update!(ptime_employee_id: 33)
       alice.update!(ptime_employee_id: 21)
 
-      dropdown_data = build_dropdown_data(ptime_employees_data, Person.all.pluck(:ptime_employee_id))
+      dropdown_data = build_dropdown_data(ptime_employees_data)
       expected = [
         ["Longmax Smith", "/people/169654640"],
         ["Alice Mante", "/people/663665735"],
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 4e8d29fe8..196810e2f 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -63,7 +63,7 @@
   config.infer_spec_type_from_file_location!
 
   config.before(:each) do
-    set_env_variables_and_stub_request
+    stub_env_variables_and_request
   end
 
   config.before { allow($stdout).to receive(:puts) }
diff --git a/spec/support/ptime_helpers.rb b/spec/support/ptime_helpers.rb
index ca40110a4..cc339ee3c 100644
--- a/spec/support/ptime_helpers.rb
+++ b/spec/support/ptime_helpers.rb
@@ -1,9 +1,8 @@
 module PtimeHelpers
-    def set_env_variables_and_stub_request
-        ENV["PTIME_BASE_URL"] = "www.ptime.example.com"
-        ENV["PTIME_API_USERNAME"] = "test username"
-        ENV["PTIME_API_PASSWORD"] = "test password"
-
+    def stub_env_variables_and_request
+        stub_env_var("PTIME_BASE_URL", "www.ptime.example.com")
+        stub_env_var("PTIME_API_USERNAME", "test username")
+        stub_env_var("PTIME_API_PASSWORD", "test password")
         stub_ptime_request(ptime_employees.to_json)
     end
 
@@ -24,4 +23,15 @@ def stub_ptime_request(return_body, path =nil)
             .to_return(body: return_body, headers: { 'content-type': content_type }, status: 200)
             .with(basic_auth: [ENV["PTIME_API_USERNAME"], ENV["PTIME_API_PASSWORD"]])
     end
+
+    def use_skills_db
+        allow_any_instance_of(RestClient::Request)
+       .to receive(:execute)
+       .and_raise(RestClient::ExceptionWithResponse)
+    end
+
+    def stub_env_var(name, value)
+        allow(ENV).to receive(:fetch).with(name).and_return(value)
+        stub_const('ENV', ENV.to_hash.merge(name => value))
+    end
 end

From d19bd8e06abac33fcd533a4e72286f5481af1baa Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Fri, 26 Jul 2024 12:44:56 +0200
Subject: [PATCH 36/37] clean up

---
 app/helpers/person_helper.rb     | 4 ++--
 spec/domain/ptime/client_spec.rb | 4 ++--
 spec/rails_helper.rb             | 3 ++-
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb
index ac25c245b..3c6dde47b 100644
--- a/app/helpers/person_helper.rb
+++ b/app/helpers/person_helper.rb
@@ -95,11 +95,11 @@ def fetch_ptime_or_skills_data
   def build_dropdown_data(ptime_employees)
     ptime_employees.map do |ptime_employee|
       ptime_employee_name = append_ptime_employee_name(ptime_employee)
-      person_id = Person.find_by(ptime_employee_id: ptime_employee[:id])
+      skills_person = Person.find_by(ptime_employee_id: ptime_employee[:id])
       ptime_employee_id = ptime_employee[:id]
       already_exists = ptime_employee_id.in?(Person.pluck(:ptime_employee_id))
       path = new_person_path(ptime_employee_id: ptime_employee_id)
-      path = person_path(person_id) if already_exists
+      path = person_path(skills_person) if already_exists
 
       [ptime_employee_name, path]
     end
diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index 2ac931ff7..166499d4f 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -7,13 +7,13 @@
     end
 
     it 'should not raise PTimeClientError if LAST_PTIME_ERROR is less than 5 minutes ago' do
-        ENV['LAST_PTIME_ERROR'] = 6.minutes.ago.to_s
+        stub_env_var("LAST_PTIME_ERROR", 6.minutes.ago.to_s)
         fetched_employees = Ptime::Client.new.request(:get, "employees", { per_page: 1000 })
         expect(fetched_employees).to eq(ptime_employees_data)
     end
 
     it 'should raise PTimeClientError if LAST_PTIME_ERROR is less than 5 minutes ago' do
-        ENV['LAST_PTIME_ERROR'] = 4.minutes.ago.to_s
+        stub_env_var("LAST_PTIME_ERROR", 4.minutes.ago.to_s)
         expect {
             Ptime::Client.new.request(:get, "employees", { per_page: 1000 })
         }.to raise_error(CustomExceptions::PTimeClientError)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 196810e2f..d57d07b37 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -66,7 +66,8 @@
     stub_env_variables_and_request
   end
 
-  config.before { allow($stdout).to receive(:puts) }
+  show_logs = ENV.fetch('SHOW_LOGS', false)
+  config.before { allow($stdout).to receive(:puts) } unless show_logs
 
   # Controller helper
   config.include(JsonMacros, type: :controller)

From 57ad58af26df975ec57c904db0f8ce97450ad944 Mon Sep 17 00:00:00 2001
From: Yanick Minder <minder@puzzle.ch>
Date: Mon, 29 Jul 2024 10:41:58 +0200
Subject: [PATCH 37/37] add client test

---
 spec/domain/ptime/client_spec.rb | 10 +++++++++-
 spec/support/ptime_helpers.rb    |  4 ++--
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/spec/domain/ptime/client_spec.rb b/spec/domain/ptime/client_spec.rb
index 166499d4f..42d727b07 100644
--- a/spec/domain/ptime/client_spec.rb
+++ b/spec/domain/ptime/client_spec.rb
@@ -6,12 +6,20 @@
         expect(fetched_employees).to eq(ptime_employees_data)
     end
 
-    it 'should not raise PTimeClientError if LAST_PTIME_ERROR is less than 5 minutes ago' do
+    it 'should not raise PTimeClientError if LAST_PTIME_ERROR is more than 5 minutes ago' do
         stub_env_var("LAST_PTIME_ERROR", 6.minutes.ago.to_s)
         fetched_employees = Ptime::Client.new.request(:get, "employees", { per_page: 1000 })
         expect(fetched_employees).to eq(ptime_employees_data)
     end
 
+    it 'should raise PTimeClientError page is unreachable' do
+        stub_env_var("PTIME_BASE_URL", "irgend.oepp.is")
+        stub_ptime_request(ptime_employees.to_json, "employees?per_page=1000", 404)
+        expect {
+            Ptime::Client.new.request(:get, "employees", { per_page: 1000 })
+        }.to raise_error(CustomExceptions::PTimeClientError)
+    end
+
     it 'should raise PTimeClientError if LAST_PTIME_ERROR is less than 5 minutes ago' do
         stub_env_var("LAST_PTIME_ERROR", 4.minutes.ago.to_s)
         expect {
diff --git a/spec/support/ptime_helpers.rb b/spec/support/ptime_helpers.rb
index cc339ee3c..72da9c69b 100644
--- a/spec/support/ptime_helpers.rb
+++ b/spec/support/ptime_helpers.rb
@@ -14,13 +14,13 @@ def ptime_employees_data
         fixture_data("all_ptime_employees")[:data]
     end
 
-    def stub_ptime_request(return_body, path =nil)
+    def stub_ptime_request(return_body, path =nil, status = 200)
         path ||= "employees?per_page=1000"
         url = "http://#{ENV["PTIME_BASE_URL"]}/api/v1/#{path}"
         content_type = "application/vnd.api+json; charset=utf-8"
 
         stub_request(:get, url)
-            .to_return(body: return_body, headers: { 'content-type': content_type }, status: 200)
+            .to_return(body: return_body, headers: { 'content-type': content_type }, status: status)
             .with(basic_auth: [ENV["PTIME_API_USERNAME"], ENV["PTIME_API_PASSWORD"]])
     end