From 25cbf63b48070f53fc08fdb2f0351dfdb5ad450d Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 14 Mar 2023 16:45:01 +0100 Subject: [PATCH] (feat) notify member on limit reached --- CHANGELOG.md | 2 + app/models/reservation.rb | 11 ++++ app/services/reservation_limit_service.rb | 50 +++++++++++++++---- ...er_reservation_limit_reached.json.jbuilder | 7 +++ ..._member_reservation_limit_reached.html.erb | 9 ++++ config/locales/app.admin.de.yml | 12 ++--- config/locales/app.admin.en.yml | 13 +++-- config/locales/app.admin.es.yml | 12 ++--- config/locales/app.admin.fr.yml | 10 ++-- config/locales/app.admin.no.yml | 12 ++--- config/locales/app.admin.pt.yml | 12 ++--- config/locales/app.admin.zu.yml | 10 ++-- config/locales/de.yml | 2 + config/locales/en.yml | 2 + config/locales/es.yml | 2 + config/locales/fr.yml | 2 + config/locales/mails.de.yml | 4 ++ config/locales/mails.en.yml | 4 ++ config/locales/mails.es.yml | 4 ++ config/locales/mails.fr.yml | 4 ++ config/locales/mails.no.yml | 4 ++ config/locales/mails.pt.yml | 4 ++ config/locales/mails.zu.yml | 4 ++ config/locales/no.yml | 2 + config/locales/pt.yml | 2 + config/locales/zu.yml | 2 + db/seeds/notification_types.rb | 8 +++ .../reservation_limit_service_test.rb | 40 +++++++++++++-- 28 files changed, 196 insertions(+), 54 deletions(-) create mode 100644 app/views/api/notifications/_notify_member_reservation_limit_reached.json.jbuilder create mode 100644 app/views/notifications_mailer/notify_member_reservation_limit_reached.html.erb diff --git a/CHANGELOG.md b/CHANGELOG.md index 3296b7405..5eaf4fc0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog Fab-manager +- [TODO DEPLOY] `rails db:seed` + ## v5.8.2 2023 March 13 - Improved upgrade script diff --git a/app/models/reservation.rb b/app/models/reservation.rb index 50f240627..d0472354b 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -32,6 +32,7 @@ class Reservation < ApplicationRecord after_commit :notify_member_create_reservation, on: :create after_commit :notify_admin_member_create_reservation, on: :create after_commit :extend_subscription, on: :create + after_commit :notify_member_limitation_reached, on: :create delegate :user, to: :statistic_profile @@ -137,4 +138,14 @@ class Reservation < ApplicationRecord receiver: User.admins_and_managers, attached_object: self end + + def notify_member_limitation_reached + date = ReservationLimitService.reached_limit_date(self) + return if date.nil? + + NotificationCenter.call type: 'notify_member_reservation_limit_reached', + receiver: user, + attached_object: ReservationLimitService.limit(user.subscribed_plan, reservable), + meta_data: { date: date } + end end diff --git a/app/services/reservation_limit_service.rb b/app/services/reservation_limit_service.rb index d4cf57b51..64ae9b7dc 100644 --- a/app/services/reservation_limit_service.rb +++ b/app/services/reservation_limit_service.rb @@ -19,20 +19,39 @@ class ReservationLimitService reservation.cart_item_reservation_slots.group_by { |sr| sr.slot.start_at.to_date }.each_pair do |date, reservation_slots| daily_duration = reservations_duration(customer, date, reservation, cart_items) + (reservation_slots.map { |sr| sr.slot.duration }.reduce(:+) || 0) - return false if Rational(daily_duration / 3600).to_f > limit + return false if Rational(daily_duration / 3600).to_f > limit.limit end true end + # @param reservation [Reservation] + # @return [Date,NilClass] + def reached_limit_date(reservation) + user = reservation.user + plan = user.subscribed_plan + return nil if plan.nil? || !plan.limiting + + limit = limit(plan, reservation.reservable) + return nil if limit.nil? + + reservation.slots_reservations.group_by { |sr| sr.slot.start_at.to_date }.each_pair do |date, reservation_slots| + daily_duration = saved_reservations_durations(user, reservation.reservable, date, reservation) + + (reservation_slots.map { |sr| sr.slot.duration }.reduce(:+) || 0) + return date if Rational(daily_duration / 3600).to_f >= limit.limit + end + + nil + end + # @param plan [Plan,NilClass] # @param reservable [Machine,Event,Space,Training] - # @return [Integer,NilClass] in hours + # @return [PlanLimitation] in hours def limit(plan, reservable) return nil unless plan&.limiting limitations = plan&.plan_limitations&.filter { |limit| limit.reservables.include?(reservable) } - limitations&.find { |limit| limit.limitable_type != 'MachineCategory' }&.limit || limitations&.first&.limit + limitations&.find { |limit| limit.limitable_type != 'MachineCategory' } || limitations&.first end private @@ -43,11 +62,7 @@ class ReservationLimitService # @param cart_items [Array] # @return [Integer] in seconds def reservations_duration(customer, date, reservation, cart_items) - daily_reservations = customer.reservations - .includes(slots_reservations: :slot) - .where(reservable: reservation.reservable) - .where(slots_reservations: { canceled_at: nil }) - .where("date_trunc('day', slots.start_at) = :date", date: date) + daily_reservations_hours = saved_reservations_durations(customer, reservation.reservable, date) cart_daily_reservations = cart_items.filter do |item| item.is_a?(CartItem::Reservation) && @@ -58,8 +73,25 @@ class ReservationLimitService .where("date_trunc('day', slots.start_at) = :date", date: date) end - (daily_reservations.map { |r| r.slots_reservations.map { |sr| sr.slot.duration } }.flatten.reduce(:+) || 0) + + daily_reservations_hours + (cart_daily_reservations.map { |r| r.cart_item_reservation_slots.map { |sr| sr.slot.duration } }.flatten.reduce(:+) || 0) end + + # @param customer [User] + # @param reservable [Machine,Event,Space,Training] + # @param date [Date] + # @param reservation [Reservation] + # @return [Integer] in seconds + def saved_reservations_durations(customer, reservable, date, reservation = nil) + daily_reservations = customer.reservations + .includes(slots_reservations: :slot) + .where(reservable: reservable) + .where(slots_reservations: { canceled_at: nil }) + .where("date_trunc('day', slots.start_at) = :date", date: date) + + daily_reservations = daily_reservations.where.not(id: reservation.id) unless reservation.nil? + + (daily_reservations.map { |r| r.slots_reservations.map { |sr| sr.slot.duration } }.flatten.reduce(:+) || 0) + end end end diff --git a/app/views/api/notifications/_notify_member_reservation_limit_reached.json.jbuilder b/app/views/api/notifications/_notify_member_reservation_limit_reached.json.jbuilder new file mode 100644 index 000000000..f90b16e39 --- /dev/null +++ b/app/views/api/notifications/_notify_member_reservation_limit_reached.json.jbuilder @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +json.title notification.notification_type +json.description t('.limit_reached', + HOURS: notification.attached_object.limit, + ITEM: notification.attached_object.limitable.name, + DATE: I18n.l(notification.get_meta_data(:date).to_date)) diff --git a/app/views/notifications_mailer/notify_member_reservation_limit_reached.html.erb b/app/views/notifications_mailer/notify_member_reservation_limit_reached.html.erb new file mode 100644 index 000000000..e8949e66d --- /dev/null +++ b/app/views/notifications_mailer/notify_member_reservation_limit_reached.html.erb @@ -0,0 +1,9 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

+ <%= t('.body.limit_reached', + HOURS: @attached_object.limit, + ITEM: @attached_object.limitable.name, + DATE: I18n.l(@notification.get_meta_data(:date).to_date)) %> +

+ diff --git a/config/locales/app.admin.de.yml b/config/locales/app.admin.de.yml index b793ae586..3bf5b5482 100644 --- a/config/locales/app.admin.de.yml +++ b/config/locales/app.admin.de.yml @@ -230,7 +230,7 @@ de: email: "Email address" plan_pricing_form: prices: "Prices" - about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces." + about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces. All prices are per hour." copy_prices_from: "Copy prices from" copy_prices_from_help: "This will replace all the prices of this plan with the prices of the selected plan" machines: "Machines" @@ -321,7 +321,7 @@ de: manage_trainings: "Klicke hier, um Schulungen hinzuzufügen oder zu entfernen." number_of_tickets: "Anzahl der Tickets: " adjust_the_opening_hours: "Öffnungszeiten anpassen" - to_time: "bis" #eg. from 18:00 to 21:00 + to_time: "bis" #e.g. from 18:00 to 21:00 restrict_options: "Einschränkungsoptionen" restrict_with_labels: "Diesen Slot mit Labels einschränken" restrict_for_subscriptions: "Diesen Slot auf Abonnenten einschränken" @@ -543,8 +543,8 @@ de: on_DATE: "am {DATE}" from_DATE: "von {DATE}" from_TIME: "ab {TIME}" - to_date: "bis" #eg: from 01/01 to 01/05 - to_time: "bis" #eg. from 18:00 to 21:00 + to_date: "bis" #e.g.: from 01/01 to 01/05 + to_time: "bis" #e.g. from 18:00 to 21:00 title: "Titel" dates: "Datum" booking: "Buchung" @@ -1488,8 +1488,8 @@ de: statistics: "Statistiken" evolution: "Entwicklung" age_filter: "Altersfilter" - from_age: "Von" #eg. from 8 to 40 years old - to_age: "bis" #eg. from 8 to 40 years old + from_age: "Von" #e.g. from 8 to 40 years old + to_age: "bis" #e.g. from 8 to 40 years old start: "Start:" end: "Ende:" custom_filter: "Benutzerderfinierter Filter" diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 4cc346e05..fd3c7234a 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -180,7 +180,6 @@ en: rolling_subscription_help: "A rolling subscription will begin the day of the first trainings. Otherwise, it will begin as soon as it is bought." monthly_payment: "Monthly payment?" monthly_payment_help: "If monthly payment is enabled, the members will be able to choose between a one-time payment or a payment schedule staged each months." - description: "Description" information_sheet: "Information sheet" notified_partner: "Notified partner" new_user: "New user" @@ -231,7 +230,7 @@ en: email: "Email address" plan_pricing_form: prices: "Prices" - about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces." + about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces. All prices are per hour." copy_prices_from: "Copy prices from" copy_prices_from_help: "This will replace all the prices of this plan with the prices of the selected plan" machines: "Machines" @@ -322,7 +321,7 @@ en: manage_trainings: "Click here to add or remove trainings." number_of_tickets: "Number of tickets: " adjust_the_opening_hours: "Adjust the opening hours" - to_time: "to" #eg. from 18:00 to 21:00 + to_time: "to" #e.g. from 18:00 to 21:00 restrict_options: "Restriction options" restrict_with_labels: "Restrict this slot with labels" restrict_for_subscriptions: "Restrict this slot for subscription users" @@ -544,8 +543,8 @@ en: on_DATE: "on {DATE}" from_DATE: "from {DATE}" from_TIME: "from {TIME}" - to_date: "to" #eg: from 01/01 to 01/05 - to_time: "to" #eg. from 18:00 to 21:00 + to_date: "to" #e.g.: from 01/01 to 01/05 + to_time: "to" #e.g. from 18:00 to 21:00 title: "Title" dates: "Dates" booking: "Booking" @@ -1489,8 +1488,8 @@ en: statistics: "Statistics" evolution: "Evolution" age_filter: "Age filter" - from_age: "From" #eg. from 8 to 40 years old - to_age: "to" #eg. from 8 to 40 years old + from_age: "From" #e.g. from 8 to 40 years old + to_age: "to" #e.g. from 8 to 40 years old start: "Start:" end: "End:" custom_filter: "Custom filter" diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 63bff355b..709f3535a 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -230,7 +230,7 @@ es: email: "Email address" plan_pricing_form: prices: "Prices" - about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces." + about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces. All prices are per hour." copy_prices_from: "Copy prices from" copy_prices_from_help: "This will replace all the prices of this plan with the prices of the selected plan" machines: "Machines" @@ -321,7 +321,7 @@ es: manage_trainings: "Click here to add or remove trainings." number_of_tickets: "Número de tickets: " adjust_the_opening_hours: "Ajustar el horario de apertura" - to_time: "a" #eg. from 18:00 to 21:00 + to_time: "a" #e.g. from 18:00 to 21:00 restrict_options: "Restriction options" restrict_with_labels: "Restringir este horario con etiquetas" restrict_for_subscriptions: "Restrict this slot for subscription users" @@ -543,8 +543,8 @@ es: on_DATE: "on {DATE}" from_DATE: "Desde {DATE}" from_TIME: "Desde {TIME}" - to_date: "to" #eg: from 01/01 to 01/05 - to_time: "to" #eg. from 18:00 to 21:00 + to_date: "to" #e.g.: from 01/01 to 01/05 + to_time: "to" #e.g. from 18:00 to 21:00 title: "Title" dates: "Dates" booking: "Booking" @@ -1488,8 +1488,8 @@ es: statistics: "Statistics" evolution: "Evolución" age_filter: "Filtro de edad" - from_age: "Desde" #eg. from 8 to 40 years old - to_age: "a" #eg. from 8 to 40 years old + from_age: "Desde" #e.g. from 8 to 40 years old + to_age: "a" #e.g. from 8 to 40 years old start: "Principio:" end: "Final:" custom_filter: "Filtro personalizado" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index dc08d80fc..ef93f982c 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -321,7 +321,7 @@ fr: manage_trainings: "Cliquez-ici pour ajouter ou supprimer des formations." number_of_tickets: "Nombre de places : " adjust_the_opening_hours: "Ajuster l'horaire" - to_time: "à" #eg. from 18:00 to 21:00 + to_time: "à" #e.g. from 18:00 to 21:00 restrict_options: "Options de restriction" restrict_with_labels: "Restreindre ce créneau avec des étiquettes" restrict_for_subscriptions: "Restreindre ce créneau pour les abonnements" @@ -543,8 +543,8 @@ fr: on_DATE: "le {DATE}" from_DATE: "du {DATE}" from_TIME: "de {TIME}" - to_date: "au" #eg: from 01/01 to 01/05 - to_time: "à" #eg. from 18:00 to 21:00 + to_date: "au" #e.g.: from 01/01 to 01/05 + to_time: "à" #e.g. from 18:00 to 21:00 title: "Titre" dates: "Dates" booking: "Réservations" @@ -1488,8 +1488,8 @@ fr: statistics: "Statistiques" evolution: "Évolution" age_filter: "Filtre d'âge" - from_age: "De" #eg. from 8 to 40 years old - to_age: "à" #eg. from 8 to 40 years old + from_age: "De" #e.g. from 8 to 40 years old + to_age: "à" #e.g. from 8 to 40 years old start: "Début :" end: "Fin :" custom_filter: "Filtre personnalisé" diff --git a/config/locales/app.admin.no.yml b/config/locales/app.admin.no.yml index 7b44369c1..f8a9e446a 100644 --- a/config/locales/app.admin.no.yml +++ b/config/locales/app.admin.no.yml @@ -230,7 +230,7 @@ email: "Email address" plan_pricing_form: prices: "Prices" - about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces." + about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces. All prices are per hour." copy_prices_from: "Copy prices from" copy_prices_from_help: "This will replace all the prices of this plan with the prices of the selected plan" machines: "Machines" @@ -321,7 +321,7 @@ manage_trainings: "Klikk her for å legge til eller endre opplæring." number_of_tickets: "Antall billetter: " adjust_the_opening_hours: "Endre åpningstid" - to_time: "til" #eg. from 18:00 to 21:00 + to_time: "til" #e.g. from 18:00 to 21:00 restrict_options: "Alternativer for begrensning" restrict_with_labels: "Begrens denne reservasjonen med etiketter" restrict_for_subscriptions: "Begrens denne reservasjoen til medlemmer" @@ -543,8 +543,8 @@ on_DATE: "{DATE}" from_DATE: "fra {DATE}" from_TIME: "fra {TIME}" - to_date: "til" #eg: from 01/01 to 01/05 - to_time: "til" #eg. from 18:00 to 21:00 + to_date: "til" #e.g.: from 01/01 to 01/05 + to_time: "til" #e.g. from 18:00 to 21:00 title: "Tittel" dates: "Datoer" booking: "Reservasjon" @@ -1488,8 +1488,8 @@ statistics: "Statistikk" evolution: "Utvikling" age_filter: "Aldersfilter" - from_age: "Fra" #eg. from 8 to 40 years old - to_age: "til" #eg. from 8 to 40 years old + from_age: "Fra" #e.g. from 8 to 40 years old + to_age: "til" #e.g. from 8 to 40 years old start: "Start:" end: "Slutt:" custom_filter: "Egendefinerte filtre" diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index 8edf494f5..35c786b86 100644 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -230,7 +230,7 @@ pt: email: "Email address" plan_pricing_form: prices: "Prices" - about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces." + about_prices: "The prices defined here will apply to members subscribing to this plan, for machines and spaces. All prices are per hour." copy_prices_from: "Copy prices from" copy_prices_from_help: "This will replace all the prices of this plan with the prices of the selected plan" machines: "Machines" @@ -321,7 +321,7 @@ pt: manage_trainings: "Clique aqui para adicionar ou remover treinamentos." number_of_tickets: "Número de vagas: " adjust_the_opening_hours: "Ajustar o horário de funcionamento" - to_time: "ás" #eg. from 18:00 to 21:00 + to_time: "ás" #e.g. from 18:00 to 21:00 restrict_options: "Opções de restrição" restrict_with_labels: "Restrinja este slot com etiquetas" restrict_for_subscriptions: "Restringir este slot para os usuários da assinatura" @@ -543,8 +543,8 @@ pt: on_DATE: "No {DATE}" from_DATE: "Em {DATE}" from_TIME: "Ás {TIME}" - to_date: "ás" #eg: from 01/01 to 01/05 - to_time: "ás" #eg. from 18:00 to 21:00 + to_date: "ás" #e.g.: from 01/01 to 01/05 + to_time: "ás" #e.g. from 18:00 to 21:00 title: "Título" dates: "Datas" booking: "Reserva" @@ -1488,8 +1488,8 @@ pt: statistics: "Estatísticas" evolution: "Evolução" age_filter: "Filtro de idade" - from_age: "Dos" #eg. from 8 to 40 years old - to_age: "aos" #eg. from 8 to 40 years old + from_age: "Dos" #e.g. from 8 to 40 years old + to_age: "aos" #e.g. from 8 to 40 years old start: "Início:" end: "Fim:" custom_filter: "Filtro customizado" diff --git a/config/locales/app.admin.zu.yml b/config/locales/app.admin.zu.yml index edc8acae7..9e785700c 100644 --- a/config/locales/app.admin.zu.yml +++ b/config/locales/app.admin.zu.yml @@ -321,7 +321,7 @@ zu: manage_trainings: "crwdns24132:0crwdne24132:0" number_of_tickets: "crwdns24134:0crwdne24134:0" adjust_the_opening_hours: "crwdns24136:0crwdne24136:0" - to_time: "crwdns24138:0crwdne24138:0" #eg. from 18:00 to 21:00 + to_time: "crwdns24138:0crwdne24138:0" #e.g. from 18:00 to 21:00 restrict_options: "crwdns24140:0crwdne24140:0" restrict_with_labels: "crwdns24142:0crwdne24142:0" restrict_for_subscriptions: "crwdns24144:0crwdne24144:0" @@ -543,8 +543,8 @@ zu: on_DATE: "crwdns24452:0{DATE}crwdne24452:0" from_DATE: "crwdns24454:0{DATE}crwdne24454:0" from_TIME: "crwdns24456:0{TIME}crwdne24456:0" - to_date: "crwdns24458:0crwdne24458:0" #eg: from 01/01 to 01/05 - to_time: "crwdns24460:0crwdne24460:0" #eg. from 18:00 to 21:00 + to_date: "crwdns24458:0crwdne24458:0" #e.g.: from 01/01 to 01/05 + to_time: "crwdns24460:0crwdne24460:0" #e.g. from 18:00 to 21:00 title: "crwdns24462:0crwdne24462:0" dates: "crwdns24464:0crwdne24464:0" booking: "crwdns24466:0crwdne24466:0" @@ -1488,8 +1488,8 @@ zu: statistics: "crwdns26224:0crwdne26224:0" evolution: "crwdns26226:0crwdne26226:0" age_filter: "crwdns26228:0crwdne26228:0" - from_age: "crwdns26230:0crwdne26230:0" #eg. from 8 to 40 years old - to_age: "crwdns26232:0crwdne26232:0" #eg. from 8 to 40 years old + from_age: "crwdns26230:0crwdne26230:0" #e.g. from 8 to 40 years old + to_age: "crwdns26232:0crwdne26232:0" #e.g. from 8 to 40 years old start: "crwdns26234:0crwdne26234:0" end: "crwdns26236:0crwdne26236:0" custom_filter: "crwdns26238:0crwdne26238:0" diff --git a/config/locales/de.yml b/config/locales/de.yml index 7d5dc76af..c5fc2ebb7 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -433,6 +433,8 @@ de: schedule_deadline: "Sie müssen den Scheck zur %{DATE} -Frist einlösen, für den Zeitplan %{REFERENCE}" notify_admin_payment_schedule_transfer_deadline: schedule_deadline: "Sie müssen das Lastschriftverfahren für die %{DATE} -Frist bestätigen, für Zeitplan %{REFERENCE}" + notify_member_reservation_limit_reached: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: supporting_document_files_uploaded: "Supporting document uploaded by member %{NAME}." notify_admin_user_supporting_document_files_updated: diff --git a/config/locales/en.yml b/config/locales/en.yml index d8a3b9064..7efcadac9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -433,6 +433,8 @@ en: schedule_deadline: "You must cash the check for the %{DATE} deadline, for schedule %{REFERENCE}" notify_admin_payment_schedule_transfer_deadline: schedule_deadline: "You must confirm the bank direct debit for the %{DATE} deadline, for schedule %{REFERENCE}" + notify_member_reservation_limit_reached: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: supporting_document_files_uploaded: "Supporting document uploaded by member %{NAME}." notify_admin_user_supporting_document_files_updated: diff --git a/config/locales/es.yml b/config/locales/es.yml index 758835ce4..5814ffea2 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -433,6 +433,8 @@ es: schedule_deadline: "You must cash the check for the %{DATE} deadline, for schedule %{REFERENCE}" notify_admin_payment_schedule_transfer_deadline: schedule_deadline: "You must confirm the bank direct debit for the %{DATE} deadline, for schedule %{REFERENCE}" + notify_member_reservation_limit_reached: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: supporting_document_files_uploaded: "Supporting document uploaded by member %{NAME}." notify_admin_user_supporting_document_files_updated: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 23862982e..268f297da 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -433,6 +433,8 @@ fr: schedule_deadline: "Vous devez encaisser le chèque de l'échéance du %{DATE}, pour l'échéancier %{REFERENCE}" notify_admin_payment_schedule_transfer_deadline: schedule_deadline: "Vous devez confirmer le prélèvement bancaire pour l'échéance du %{DATE} , pour l'échéancier %{REFERENCE}" + notify_member_reservation_limit_reached: + limit_reached: "Pour le %{DATE}, vous avez atteint votre limite quotidienne de %{HOURS} heures de réservation de la %{ITEM}." notify_admin_user_supporting_document_files_created: supporting_document_files_uploaded: "Le membre %{NAME} a téléversé un nouveau justificatif." notify_admin_user_supporting_document_files_updated: diff --git a/config/locales/mails.de.yml b/config/locales/mails.de.yml index bf6cd16b9..1de187d70 100644 --- a/config/locales/mails.de.yml +++ b/config/locales/mails.de.yml @@ -375,6 +375,10 @@ de: remember: "Gemäß Ihrem Zahlungsplan von %{REFERENCE} wurde zum %{DATE} eine Belastung der Karte in Höhe von %{AMOUNT} geplant." date: "Dies ist eine Erinnerung zur Prüfung, ob das Bankkonto erfolgreich belastet werden konnte." confirm: "Bitte bestätigen Sie den Erhalt des Guthabens in Ihrer Zahlungsverwaltung, damit die entsprechende Rechnung generiert werden kann." + notify_member_reservation_limit_reached: + subject: "Daily reservation limit reached" + body: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: subject: "Supporting documents uploaded by a member" body: diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index 9cf5f2f1e..6151da3c6 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -375,6 +375,10 @@ en: remember: "In accordance with your %{REFERENCE} payment schedule, %{AMOUNT} was due to be debited on %{DATE}." date: "This is a reminder to verify that the direct bank debit was successfull." confirm: "Please confirm the receipt of funds in your payment schedule management interface, so that the corresponding invoice will be generated." + notify_member_reservation_limit_reached: + subject: "Daily reservation limit reached" + body: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: subject: "Supporting documents uploaded by a member" body: diff --git a/config/locales/mails.es.yml b/config/locales/mails.es.yml index de3a39772..338ae9812 100644 --- a/config/locales/mails.es.yml +++ b/config/locales/mails.es.yml @@ -375,6 +375,10 @@ es: remember: "In accordance with your %{REFERENCE} payment schedule, %{AMOUNT} was due to be debited on %{DATE}." date: "This is a reminder to verify that the direct bank debit was successfull." confirm: "Please confirm the receipt of funds in your payment schedule management interface, so that the corresponding invoice will be generated." + notify_member_reservation_limit_reached: + subject: "Daily reservation limit reached" + body: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: subject: "Supporting documents uploaded by a member" body: diff --git a/config/locales/mails.fr.yml b/config/locales/mails.fr.yml index b6d51198b..0517ad9e2 100644 --- a/config/locales/mails.fr.yml +++ b/config/locales/mails.fr.yml @@ -375,6 +375,10 @@ fr: remember: "Conformément à l'échéancier de paiement %{REFERENCE}, une échéance de %{AMOUNT} était prévu pour être prélevée le %{DATE}." date: "Ceci est un rappel pour vérifier que le prélèvement bancaire a bien été effectué." confirm: "Veuillez confirmer la réception des fonds dans votre interface de gestion des échéanciers de paiement, afin que la facture correspondante soit générée." + notify_member_reservation_limit_reached: + subject: "Limite de réservation quotidienne atteinte" + body: + limit_reached: "Pour le %{DATE}, vous avez atteint votre limite quotidienne de %{HOURS} heures de réservation de la %{ITEM}." notify_admin_user_supporting_document_files_created: subject: "Justificatif téléversé par un membre" body: diff --git a/config/locales/mails.no.yml b/config/locales/mails.no.yml index dfa2d7078..a4ed1ec5e 100644 --- a/config/locales/mails.no.yml +++ b/config/locales/mails.no.yml @@ -375,6 +375,10 @@ remember: "In accordance with your %{REFERENCE} payment schedule, %{AMOUNT} was due to be debited on %{DATE}." date: "This is a reminder to verify that the direct bank debit was successfull." confirm: "Please confirm the receipt of funds in your payment schedule management interface, so that the corresponding invoice will be generated." + notify_member_reservation_limit_reached: + subject: "Daily reservation limit reached" + body: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: subject: "Supporting documents uploaded by a member" body: diff --git a/config/locales/mails.pt.yml b/config/locales/mails.pt.yml index 2440db3c6..2462370ec 100644 --- a/config/locales/mails.pt.yml +++ b/config/locales/mails.pt.yml @@ -375,6 +375,10 @@ pt: remember: "De acordo com a agenda de pagamento %{REFERENCE}, %{AMOUNT} deveria ser debitado em %{DATE}." date: "Este é um lembrete para verificar se o débito bancário foi bem sucedido." confirm: "Não se esqueça de confirmar o recibo na interface de gestão de pagamento, para que a fatura correspondente seja gerada." + notify_member_reservation_limit_reached: + subject: "Daily reservation limit reached" + body: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: subject: "Supporting documents uploaded by a member" body: diff --git a/config/locales/mails.zu.yml b/config/locales/mails.zu.yml index 5b0661463..6aca672d6 100644 --- a/config/locales/mails.zu.yml +++ b/config/locales/mails.zu.yml @@ -375,6 +375,10 @@ zu: remember: "crwdns29938:0%{REFERENCE}crwdnd29938:0%{AMOUNT}crwdnd29938:0%{DATE}crwdne29938:0" date: "crwdns29940:0crwdne29940:0" confirm: "crwdns29942:0crwdne29942:0" + notify_member_reservation_limit_reached: + subject: "crwdns37483:0crwdne37483:0" + body: + limit_reached: "crwdns37485:0%{DATE}crwdnd37485:0%{HOURS}crwdnd37485:0%{ITEM}crwdne37485:0" notify_admin_user_supporting_document_files_created: subject: "crwdns37349:0crwdne37349:0" body: diff --git a/config/locales/no.yml b/config/locales/no.yml index d35e5fca3..88c958a75 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -433,6 +433,8 @@ schedule_deadline: "You must cash the check for the %{DATE} deadline, for schedule %{REFERENCE}" notify_admin_payment_schedule_transfer_deadline: schedule_deadline: "You must confirm the bank direct debit for the %{DATE} deadline, for schedule %{REFERENCE}" + notify_member_reservation_limit_reached: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: supporting_document_files_uploaded: "Supporting document uploaded by member %{NAME}." notify_admin_user_supporting_document_files_updated: diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 70a9519a3..a191da464 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -433,6 +433,8 @@ pt: schedule_deadline: "Você deve realizar a verificação para a data limite de %{DATE} para agendar %{REFERENCE}" notify_admin_payment_schedule_transfer_deadline: schedule_deadline: "Você deve realizar a verificação do débito para a data limite de %{DATE}, para o agendamento %{REFERENCE}" + notify_member_reservation_limit_reached: + limit_reached: "For %{DATE}, you have reached your daily limit of %{HOURS} hours of %{ITEM} reservation." notify_admin_user_supporting_document_files_created: supporting_document_files_uploaded: "Supporting document uploaded by member %{NAME}." notify_admin_user_supporting_document_files_updated: diff --git a/config/locales/zu.yml b/config/locales/zu.yml index f4d695292..d85098d04 100644 --- a/config/locales/zu.yml +++ b/config/locales/zu.yml @@ -433,6 +433,8 @@ zu: schedule_deadline: "crwdns21120:0%{DATE}crwdnd21120:0%{REFERENCE}crwdne21120:0" notify_admin_payment_schedule_transfer_deadline: schedule_deadline: "crwdns22305:0%{DATE}crwdnd22305:0%{REFERENCE}crwdne22305:0" + notify_member_reservation_limit_reached: + limit_reached: "crwdns37481:0%{DATE}crwdnd37481:0%{HOURS}crwdnd37481:0%{ITEM}crwdne37481:0" notify_admin_user_supporting_document_files_created: supporting_document_files_uploaded: "crwdns37341:0%{NAME}crwdne37341:0" notify_admin_user_supporting_document_files_updated: diff --git a/db/seeds/notification_types.rb b/db/seeds/notification_types.rb index 43d213005..8dbe8dc68 100644 --- a/db/seeds/notification_types.rb +++ b/db/seeds/notification_types.rb @@ -23,3 +23,11 @@ unless NotificationType.find_by(name: 'notify_admin_order_is_paid') is_configurable: true ) end + +unless NotificationType.find_by(name: 'notify_member_reservation_limit_reached') + NotificationType.create!( + name: 'notify_member_reservation_limit_reached', + category: 'agenda', + is_configurable: false + ) +end diff --git a/test/services/reservation_limit_service_test.rb b/test/services/reservation_limit_service_test.rb index 8976f0515..5c9f432cf 100644 --- a/test/services/reservation_limit_service_test.rb +++ b/test/services/reservation_limit_service_test.rb @@ -50,7 +50,7 @@ class ReservationLimitServiceTest < ActiveSupport::TestCase customer_profile: @acamus.invoicing_profile, operator_profile: @acamus.invoicing_profile, reservable: @machine, - cart_item_reservation_slots_attributes: [{ slot: slots[0] }, { slot: slots[1] }, { slot: slots[2] }] + cart_item_reservation_slots_attributes: [{ slot: slots[2] }, { slot: slots[3] }, { slot: slots[4] }] ) assert_not ReservationLimitService.authorized?(@plan, @acamus, reservation, []) end @@ -118,7 +118,7 @@ class ReservationLimitServiceTest < ActiveSupport::TestCase test 'get plan limit' do @plan.update(limiting: true, plan_limitations_attributes: [{ limitable_id: @machine.id, limitable_type: 'Machine', limit: 2 }]) - assert_equal 2, ReservationLimitService.limit(@plan, @machine) + assert_equal 2, ReservationLimitService.limit(@plan, @machine).limit end test 'get plan without limit' do @@ -129,13 +129,45 @@ class ReservationLimitServiceTest < ActiveSupport::TestCase category = MachineCategory.find(1) category.update(machine_ids: [@machine.id]) @plan.update(limiting: true, plan_limitations_attributes: [{ limitable: category, limit: 4 }]) - assert_equal 4, ReservationLimitService.limit(@plan, @machine) + assert_equal 4, ReservationLimitService.limit(@plan, @machine).limit end test 'machine limit should override the category limit' do category = MachineCategory.find(1) category.update(machine_ids: [@machine.id]) @plan.update(limiting: true, plan_limitations_attributes: [{ limitable: @machine, limit: 2 }, { limitable: category, limit: 4 }]) - assert_equal 2, ReservationLimitService.limit(@plan, @machine) + limit = ReservationLimitService.limit(@plan, @machine) + assert_equal 2, limit.limit + assert_equal @machine, limit.limitable + end + + test 'reservation reaches the limit' do + user = User.find_by(username: 'kdumas') + plan = user.subscribed_plan + plan.update(limiting: true, plan_limitations_attributes: [{ limitable: @machine, limit: 1 }]) + slots = Availabilities::AvailabilitiesService.new(user) + .machines([@machine], user, { start: Time.current, end: 10.days.from_now }) + reservation = Reservation.create!( + statistic_profile: user.statistic_profile, + reservable: @machine, + slots_reservations_attributes: [{ slot: slots.last }] + ) + reservation.reload + assert_equal slots.last.start_at.to_date, ReservationLimitService.reached_limit_date(reservation) + end + + test 'reservation does not reaches the limit' do + user = User.find_by(username: 'kdumas') + plan = user.subscribed_plan + plan.update(limiting: true, plan_limitations_attributes: [{ limitable: @machine, limit: 2 }]) + slots = Availabilities::AvailabilitiesService.new(user) + .machines([@machine], user, { start: Time.current, end: 10.days.from_now }) + reservation = Reservation.create!( + statistic_profile: user.statistic_profile, + reservable: @machine, + slots_reservations_attributes: [{ slot: slots.last }] + ) + reservation.reload + assert_nil ReservationLimitService.reached_limit_date(reservation) end end