diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c67c7a68..ef3c67645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Updated typescript to v4.6.3 - Updated react-select to v5.2.2 - Updated sidekiq-scheduler to v4.0.0 +- Updated icalendar to 2.7.1 - Webpack overlay will now report eslint issues - Linted all code according to eslint rules - when generating an avoir, the option "by_wallet" is not present anymore if wallet module is off diff --git a/Gemfile.lock b/Gemfile.lock index c83a48458..45a8fb4e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -169,9 +169,9 @@ GEM httpclient (2.8.3) i18n (1.10.0) concurrent-ruby (~> 1.0) - icalendar (2.5.3) + icalendar (2.7.1) ice_cube (~> 0.16) - ice_cube (0.16.3) + ice_cube (0.16.4) ice_nine (0.11.2) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) diff --git a/app/frontend/src/javascript/lib/user.ts b/app/frontend/src/javascript/lib/user.ts index c9b3a6b57..0cf3d13bd 100644 --- a/app/frontend/src/javascript/lib/user.ts +++ b/app/frontend/src/javascript/lib/user.ts @@ -3,7 +3,7 @@ import { User } from '../models/user'; import { supportedNetworks, SupportedSocialNetwork } from '../models/social-network'; export default class UserLib { - private user: User; + private readonly user: User; constructor (user: User) { this.user = user; diff --git a/app/mailers/notifications_mailer.rb b/app/mailers/notifications_mailer.rb index 0dc51d356..e0e3e49c9 100644 --- a/app/mailers/notifications_mailer.rb +++ b/app/mailers/notifications_mailer.rb @@ -52,4 +52,11 @@ class NotificationsMailer < NotifyWith::NotificationsMailer subject: t('notifications_mailer.notify_member_payment_schedule_ready.subject'), template_name: 'notify_member_payment_schedule_ready') end + + def notify_member_create_reservation + attachments[@attached_object] + mail(to: @recipient.email, + subject: t('notifications_mailer.notify_member_create_reservation.subject'), + template_name: 'notify_member_create_reservation') + end end diff --git a/app/models/reservation.rb b/app/models/reservation.rb index 7480aa5dd..de38630ae 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -67,6 +67,32 @@ class Reservation < ApplicationRecord .first end + def to_ics + ReservationService.build_ics(self) + end + + # Group all slots related to this reservation by dates and by continuous time ranges + def grouped_slots + slots_by_date = slots.group_by { |slot| slot[:start_at].to_date }.transform_values { |slots| slots.sort_by { |slot| slot[:start_at] } } + result = {} + slots_by_date.each do |date, daily_slots| + result[date] = { daily_slots.first[:start_at] => [daily_slots.first] } + + daily_slots[1..].each do |slot| + found = false + result[date].each do |group_start, group_slots| + if slot[:start_at] === group_slots.last[:end_at] + result[date][group_start].push(slot) + found = true + break + end + end + result[date][slot[:start_at]] = [slot] unless found + end + end + result + end + private def machine_not_already_reserved diff --git a/app/services/reservation_service.rb b/app/services/reservation_service.rb new file mode 100644 index 000000000..a3a6ae2c9 --- /dev/null +++ b/app/services/reservation_service.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Provides methods around the Reservation objects +class ReservationService + class << self + def build_ics(reservation) + require 'icalendar' + + cal = Icalendar::Calendar.new + reservation.grouped_slots.each do |date, daily_groups| + daily_groups.each do |start_time, group_slots| + cal.event do |e| + e.dtstart = start_time + e.dtend = group_slots.last[:end_at] + e.summary = I18n.t('reservation_ics.summary', TYPE: I18n.t("reservation_ics.type.#{reservation.reservable.class.name}")) + e.description = I18n.t('reservation_ics.description', COUNT: group_slots.count, ITEM: reservation.reservable.name) + e.ip_class = "PRIVATE" + end + end + end + + cal.to_ical + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 297fce1b0..c14c5af68 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -244,6 +244,14 @@ en: event: "Event" reservations: "Reservations" available_seats: "Available seats" + reservation_ics: + summary: "%{TYPE} reservation" + type: + Machine: "Machine" + Space: "Space" + Event: "Event" + Training: "Training" + description: "You have reserved %{COUNT} slots of %{ITEM}" roles: member: "Member" manager: "Manager"