Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Public holidays #65

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
39 changes: 39 additions & 0 deletions app/models/bank_holiday.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

# == Schema Information
#
# Table name: bank_holidays
#
# id :uuid not null, primary key
# year :integer not null
# dates :date default([]), not null, is an Array
# created_at :datetime not null
# updated_at :datetime not null
#
class BankHoliday < ApplicationRecord
validates :year, presence: true, uniqueness: true # rubocop:disable Rails/UniqueValidationWithoutIndex
after_create :fetch_bank_holidays

class << self
def in_year(year)
find_or_create_by(year:).dates
end

def weekday_dates_in_years(years)
years.map { |year| find_or_create_by!(year:).dates.reject { |date| date.saturday? || date.sunday? } }.flatten
end
end

def dates_during_weekdays
dates.reject { |date| date.saturday? || date.sunday? }
end

private

def fetch_bank_holidays
return unless dates.empty?

dates = BankHoliday::FeiertageApi.instance.retrieve_bank_holidays(year:)
update!(dates:)
end
end
23 changes: 23 additions & 0 deletions app/models/bank_holiday/feiertage_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

class BankHoliday
class FeiertageApi
class NetworkError < StandardError; end
include Singleton

def retrieve_bank_holidays(year:)
response = request(year:)
response.values.map { |e| e["datum"] }
end

private

def request(year:)
headers = {"Content-Type": "application/json"}
response = HTTParty.get("https://feiertage-api.de/api/?jahr=#{year}&nur_land=BE", headers: headers) # Always gets the holidays for Berlin
raise NetworkError, response["error"].humanize unless response.ok?

response
end
end
end
12 changes: 12 additions & 0 deletions app/models/leave.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Leave < ApplicationRecord
range_accessor_methods :leave

validates :title, :days, presence: true
validate :there_are_no_bank_holidays_in_the_leave_range

before_validation do
start_on, end_on = days.minmax
Expand Down Expand Up @@ -93,4 +94,15 @@ def notify_user_on_slack_about_status_change
def set_slack_status!
user.slack_profile.set_status(type: type, emoji: slack_emoji, until_date: leave_during.max)
end

private

def there_are_no_bank_holidays_in_the_leave_range
return if days.blank?

bank_holidays = BankHoliday.weekday_dates_in_years(days.map(&:year).uniq)
bank_holidays_in_leave_range = bank_holidays.select { |bank_holiday| days.include?(bank_holiday) }
# i18n-tasks-use t("activerecord.errors.models.leave.attributes.days.bank_holiday_in_leave_range")
errors.add(:days, :bank_holiday_in_leave_range, bank_holidays: bank_holidays_in_leave_range.map { |date| date.to_formatted_s(:short) }.join(", ")) if bank_holidays_in_leave_range.present?
end
end
4 changes: 4 additions & 0 deletions app/models/sprint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ def total_holidays
sprint_feedbacks.sum(&:holiday_count)
end

def bank_holidays
BankHoliday.weekday_dates_in_years(sprint_during.map(&:year).uniq).select { |date| sprint_during.cover?(date) }
end

def total_sick_days
sprint_feedbacks.sum(&:sick_day_count)
end
Expand Down
1 change: 1 addition & 0 deletions app/models/sprint/notification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def message
sprint_during: ApplicationController.helpers.date_range(sprint.sprint_during.min, sprint.sprint_during.max,
format: :long),
working_days: sprint.working_days,
bank_holidays: sprint.bank_holidays.any? ? sprint.bank_holidays.map { |date| I18n.l(date, format: :short) }.to_sentence : sprint.bank_holidays.size,
leaves: leaves_text_lines.join("\n"),
count: leaves_text_lines.size)
[
Expand Down
13 changes: 10 additions & 3 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
---
en:
activerecord:
errors:
models:
leave:
attributes:
days:
bank_holiday_in_leave_range: 'The leave includes bank holidays: %{bank_holidays}'
date:
formats:
month_long: "%B"
Expand Down Expand Up @@ -67,9 +74,9 @@ en:

- %{user} is away for %{days_count} days: (%{dates})
sprint_start_content:
one: "🏃 *Sprint %{title} starts today!*\nDuration: %{sprint_during}\nWorking days: %{working_days}\n\n🏖️ *On leave:*\n%{leaves}"
other: "🏃 *Sprint %{title} starts today!*\nDuration: %{sprint_during}\nWorking days: %{working_days}\n\n🏖️ *On leave:*\n%{leaves}"
zero: "🏃 *Sprint %{title} starts today!*\nDuration: %{sprint_during}\nWorking days: %{working_days}"
one: "🏃 *Sprint %{title} starts today!*\nDuration: %{sprint_during}\nWorking days: %{working_days}\nBank holidays: %{bank_holidays}\n\n🏖️ *On leave:*\n%{leaves}"
other: "🏃 *Sprint %{title} starts today!*\nDuration: %{sprint_during}\nWorking days: %{working_days}\nBank holidays: %{bank_holidays}\n\n🏖️ *On leave:*\n%{leaves}"
zero: "🏃 *Sprint %{title} starts today!*\nDuration: %{sprint_during}\nWorking days: %{working_days}\nBank holidays: %{bank_holidays}"
time_will_tell:
date_range:
different_months_same_year: "%{from_month} %{from_day} %{sep} %{to_month} %{to_day}"
Expand Down
14 changes: 14 additions & 0 deletions db/migrate/20231020163216_create_bank_holidays.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class CreateBankHolidays < ActiveRecord::Migration[7.0]
def change
create_table :bank_holidays, id: :uuid do |t|
t.integer :year, null: false
t.date :dates, array: true, null: false, default: []

t.timestamps
end

add_index :bank_holidays, :year, unique: true
end
end
14 changes: 11 additions & 3 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions spec/models/bank_holiday_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

# == Schema Information
#
# Table name: bank_holidays
#
# id :uuid not null, primary key
# year :integer not null
# dates :date default([]), not null, is an Array
# created_at :datetime not null
# updated_at :datetime not null
#
# spec/models/bank_holiday_spec.rb
require "rails_helper"

RSpec.describe BankHoliday, type: :model do
describe ".in_year" do
let(:year) { 2022 }
let(:dates) { [Date.new(2022, 1, 1), Date.new(2022, 12, 25)] }
let(:bank_holiday) { BankHoliday.create! year:, dates: }

before do
allow(BankHoliday::FeiertageApi.instance).to receive(:retrieve_bank_holidays).with(year: year).and_return(dates)
end

it "returns bank holidays for the specified year" do
expect(BankHoliday.in_year(year)).to eq(dates)
end
end

describe ".weekday_dates_in_years" do
let(:years) { [2021, 2022] }
let(:dates_2021) { [Date.new(2021, 1, 1), Date.new(2021, 12, 25)] }
let(:dates_2022) { [Date.new(2022, 1, 1), Date.new(2022, 12, 25)] }

before do
BankHoliday.create! year: 2021, dates: dates_2021
BankHoliday.create! year: 2022, dates: dates_2022
end

it "returns weekday bank holidays for the specified years" do
expect(BankHoliday.weekday_dates_in_years(years)).to eq([Date.new(2021, 1, 1)])
end
end

describe "#dates_during_weekdays" do
let(:bank_holiday) { BankHoliday.create! year: 2023, dates: [Date.new(2022, 1, 1), Date.new(2022, 12, 25)] }

it "returns only weekday dates" do
expect(bank_holiday.dates_during_weekdays).to be_empty
end
end

describe "#fetch_bank_holidays" do
let(:year) { 2023 }
let(:dates) { [Date.new(2023, 1, 1), Date.new(2023, 12, 25)] }
let(:holiday) { BankHoliday.create! year: year, dates: [] }

it "updates the bank_holiday with dates from the API" do
allow(BankHoliday::FeiertageApi.instance).to receive(:retrieve_bank_holidays).with(year:).and_return(dates)

holiday.send(:fetch_bank_holidays)

expect(holiday.reload.dates).to eq(dates)
end
end
end
4 changes: 4 additions & 0 deletions spec/models/leave_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
let(:holiday) { user.leaves.create! type: :paid, title: "Holidays", days: ["2023-01-02", "2023-01-03"] }
let(:single_day_sick_leave) { user.leaves.create! type: :sick, title: "Sick", days: ["2023-01-02"] }

before do
allow(BankHoliday::FeiertageApi.instance).to receive(:retrieve_bank_holidays).and_return(["2023-05-01", "2023-10-03"])
end

it "single day sick leaves are automatically approved" do
expect(holiday).to be_pending_approval
expect(single_day_sick_leave).to be_approved
Expand Down
25 changes: 25 additions & 0 deletions spec/models/sprint_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
RSpec.describe Sprint do
fixtures :all

before do
allow(BankHoliday::FeiertageApi.instance).to receive(:retrieve_bank_holidays).and_return(["2023-05-01", "2023-10-03"])
end

context "sending the start notification" do
let(:sprint) { sprints(:empty) }
let(:john) { users(:john) }
Expand All @@ -27,6 +31,7 @@
🏃 *Sprint S2023-02 starts today!*
Duration: January 23 — February 3, 2023
Working days: 10
Bank holidays: 0
TEXT
expect(Slack.instance.last_message.text).to eq text.strip
end
Expand All @@ -38,6 +43,7 @@
🏃 *Sprint S2023-02 starts today!*
Duration: January 23 — February 3, 2023
Working days: 10
Bank holidays: 0

🏖️ *On leave:*

Expand All @@ -53,6 +59,7 @@
🏃 *Sprint S2023-02 starts today!*
Duration: January 23 — February 3, 2023
Working days: 10
Bank holidays: 0

🏖️ *On leave:*

Expand All @@ -68,6 +75,7 @@
🏃 *Sprint S2023-02 starts today!*
Duration: January 23 — February 3, 2023
Working days: 10
Bank holidays: 0

🏖️ *On leave:*

Expand All @@ -84,6 +92,7 @@
🏃 *Sprint S2023-02 starts today!*
Duration: January 23 — February 3, 2023
Working days: 10
Bank holidays: 0

🏖️ *On leave:*

Expand All @@ -99,6 +108,7 @@
🏃 *Sprint S2023-02 starts today!*
Duration: January 23 — February 3, 2023
Working days: 10
Bank holidays: 0

🎂 *John celebrates their birthday on Feb 01!*
TEXT
Expand All @@ -113,10 +123,25 @@
🏃 *Sprint S2023-02 starts today!*
Duration: January 23 — February 3, 2023
Working days: 10
Bank holidays: 0

🎈 *John celebrates their nerdgeschoss anniversary on Jan 25!*
TEXT
expect(Slack.instance.last_message.text).to eq text.strip
end

it "mentions bank holidays" do
allow(BankHoliday::FeiertageApi.instance).to receive(:retrieve_bank_holidays).and_return(["2023-01-24"])

travel_to "2023-01-23"
sprint.reload.send_sprint_start_notification
text = <<~TEXT
🏃 *Sprint S2023-02 starts today!*
Duration: January 23 — February 3, 2023
Working days: 10
Bank holidays: Jan 24
TEXT
expect(Slack.instance.last_message.text).to eq text.strip
end
end
end
4 changes: 4 additions & 0 deletions spec/system/leaves_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
RSpec.describe "Leaves" do
fixtures :all

before do
allow(BankHoliday::FeiertageApi.instance).to receive(:retrieve_bank_holidays).and_return(["2023-05-01", "2023-10-03"])
end

# TODO: fix this test as it will fail beginning 2024. The reason is that the datepicker is not getting the year from 'travel_to'

it "requests a leave and notifies hr" do
Expand Down