diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index dd3d97a5..c0d7ee9b 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -22,7 +22,7 @@ jobs: code: ${{ steps.filter.outputs.code }} steps: - uses: actions/checkout@v6 - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v4 id: filter with: filters: | diff --git a/.github/workflows/ci-docker-cache.yml b/.github/workflows/ci-docker-cache.yml index cfa0b8dc..1ef8047f 100644 --- a/.github/workflows/ci-docker-cache.yml +++ b/.github/workflows/ci-docker-cache.yml @@ -23,7 +23,7 @@ jobs: code: ${{ steps.filter.outputs.code }} steps: - uses: actions/checkout@v6 - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v4 id: filter with: filters: | diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 3502ea47..5af84492 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -21,13 +21,13 @@ jobs: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - uses: actions/checkout@v6 - - uses: docker/setup-buildx-action@v3 - - uses: docker/login-action@v3 + - uses: docker/setup-buildx-action@v4 + - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: docker/build-push-action@v6 + - uses: docker/build-push-action@v7 with: context: . push: true diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index f5956a00..4de1feef 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -7,7 +7,7 @@ class DashboardController < BaseController def index @notepad = Rails.cache.fetch("notepad") || default_notepad - @opening_hours = Rails.cache.fetch("opening_hours") || default_opening_hours + @opening_hours = current_opening_hours @stats = build_dashboard_stats end diff --git a/app/controllers/admin/opening_hours_controller.rb b/app/controllers/admin/opening_hours_controller.rb index 3e46067d..fab63d34 100644 --- a/app/controllers/admin/opening_hours_controller.rb +++ b/app/controllers/admin/opening_hours_controller.rb @@ -16,39 +16,41 @@ def edit end def update - # Reconstruire les horaires à partir des sélecteurs individuels - updated_hours = {} - days = %w[lundi mardi mercredi jeudi vendredi samedi dimanche] - - days.each do |day| - # Vérifier si le jour est fermé - closed_param = params["closed_#{day}"] - if closed_param == "1" - updated_hours[day] = "Fermé" - else - # Reconstruire les horaires à partir des sélecteurs - open_hour = params["open_hour_#{day}"].to_i - open_minute = params["open_minute_#{day}"].to_i - close_hour = params["close_hour_#{day}"].to_i - close_minute = params["close_minute_#{day}"].to_i - - updated_hours[day] = "#{open_hour.to_s.rjust(2, '0')}:#{open_minute.to_s.rjust(2, '0')} - #{close_hour.to_s.rjust(2, '0')}:#{close_minute.to_s.rjust(2, '0')}" - end - end - - # Persist via cache for now (can be moved to a Setting model later) - Rails.cache.write("opening_hours", updated_hours) + updated_hours = build_schedule_params + OpeningHour.replace_schedule!(schedule_hash: updated_hours, updated_by_user: current_user) redirect_to admin_opening_hours_path, notice: t(".success") + rescue ActiveRecord::RecordInvalid, ArgumentError => e + @opening_hours = updated_hours || current_opening_hours + @error_message = e.record&.errors&.full_messages&.to_sentence || e.message + render :edit, status: :unprocessable_content end private def set_opening_hours - @opening_hours = Rails.cache.fetch("opening_hours") || default_opening_hours + @opening_hours = current_opening_hours end def set_breadcrumbs # No need to add dashboard breadcrumb as it's already in the partial end + + def build_schedule_params + OpeningHour::DAYS.keys.each_with_object({}) do |day, updated_hours| + day_name = day.to_s + + updated_hours[day_name] = + if params["closed_#{day_name}"] == "1" + "Fermé" + else + open_hour = params["open_hour_#{day_name}"].to_i + open_minute = params["open_minute_#{day_name}"].to_i + close_hour = params["close_hour_#{day_name}"].to_i + close_minute = params["close_minute_#{day_name}"].to_i + + "#{open_hour.to_s.rjust(2, '0')}:#{open_minute.to_s.rjust(2, '0')} - #{close_hour.to_s.rjust(2, '0')}:#{close_minute.to_s.rjust(2, '0')}" + end + end + end end end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index fd57c5aa..0205a11a 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -7,7 +7,7 @@ class HomeController < ApplicationController def index @upcoming_events = Event.upcoming.by_date.limit(1) - @opening_hours = Rails.cache.fetch("opening_hours") || default_opening_hours + @opening_hours = current_opening_hours end def dashboard; end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 4b2c0be4..d12447c5 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -23,7 +23,7 @@ def show return redirect_to page_path(target[:page], anchor: target[:anchor]), status: :moved_permanently end - @opening_hours = Rails.cache.fetch("opening_hours") || default_opening_hours + @opening_hours = current_opening_hours @notepad = Rails.cache.fetch("notepad") || default_notepad @blogs = Blog.order(created_at: :desc).limit(3) diff --git a/app/helpers/opening_hours_helper.rb b/app/helpers/opening_hours_helper.rb index 41a5814a..feb2d97c 100644 --- a/app/helpers/opening_hours_helper.rb +++ b/app/helpers/opening_hours_helper.rb @@ -6,14 +6,12 @@ module OpeningHoursHelper # Méthode de module accessible directement def self.default_opening_hours - { - lundi: "Fermé", - mardi: "14:00 - 22:00", - mercredi: "14:00 - 22:00", - jeudi: "14:00 - 22:00", - vendredi: "14:00 - 22:00", - samedi: "14:00 - 22:00", - dimanche: "14:00 - 22:00" - }.freeze + OpeningHour::DEFAULT_SCHEDULE.dup + end + + def current_opening_hours + OpeningHour.schedule_hash + rescue ActiveModel::MissingAttributeError, NoMethodError + default_opening_hours end end diff --git a/app/models/opening_hour.rb b/app/models/opening_hour.rb new file mode 100644 index 00000000..d74d781c --- /dev/null +++ b/app/models/opening_hour.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +class OpeningHour < ApplicationRecord + DAYS = { + lundi: 0, + mardi: 1, + mercredi: 2, + jeudi: 3, + vendredi: 4, + samedi: 5, + dimanche: 6 + }.freeze + + DEFAULT_SCHEDULE = { + "lundi" => "Fermé", + "mardi" => "14:00 - 22:00", + "mercredi" => "14:00 - 22:00", + "jeudi" => "14:00 - 22:00", + "vendredi" => "14:00 - 22:00", + "samedi" => "14:00 - 22:00", + "dimanche" => "14:00 - 22:00" + }.freeze + + belongs_to :updated_by_user, class_name: "User", optional: true + + enum :day, DAYS + + validates :day, presence: true, uniqueness: true + validates :open_at, presence: true, unless: :closed? + validates :close_at, presence: true, unless: :closed? + validate :close_after_open, unless: :closed? + + scope :ordered, -> { order(:day) } + + def self.schedule_hash + schedule = DEFAULT_SCHEDULE.dup + + ordered.each do |record| + schedule[record.day.to_s] = record.formatted_range + end + + schedule + end + + def self.latest_update_entry + ordered.where.not(updated_at: nil).order(updated_at: :desc, id: :desc).first + end + + def self.replace_schedule!(schedule_hash:, updated_by_user:) + transaction do + DAYS.each_key do |day_name| + raw_value = schedule_hash.fetch(day_name.to_s) { schedule_hash.fetch(day_name.to_sym, "Fermé") } + record = find_or_initialize_by(day: day_name) + record.updated_by_user = updated_by_user + + if raw_value.to_s.strip.casecmp("fermé").zero? + record.closed = true + record.open_at = nil + record.close_at = nil + else + open_at, close_at = parse_range!(raw_value) + record.closed = false + record.open_at = open_at + record.close_at = close_at + end + + record.save! + end + end + end + + def self.parse_range!(value) + match = value.to_s.strip.match(/\A(\d{2}:\d{2}) - (\d{2}:\d{2})\z/) + raise ArgumentError, "invalid time range" if match.nil? + + [ parse_time!(match[1]), parse_time!(match[2]) ] + end + + def self.parse_time!(value) + Time.zone.parse(value) || raise(ArgumentError, "invalid time") + end + + def formatted_range + return "Fermé" if closed? + + "#{open_at.strftime("%H:%M")} - #{close_at.strftime("%H:%M")}" + end + + private + + def close_after_open + return if open_at.blank? || close_at.blank? + return if close_at > open_at + + errors.add(:close_at, "doit être après l'heure d'ouverture") + end +end diff --git a/app/services/opening_hours_management/opening_hours_updater.rb b/app/services/opening_hours_management/opening_hours_updater.rb index add90fc5..00a4e12f 100644 --- a/app/services/opening_hours_management/opening_hours_updater.rb +++ b/app/services/opening_hours_management/opening_hours_updater.rb @@ -20,9 +20,7 @@ def call # Valider les horaires return failure("Erreur : l'heure de fermeture doit être après l'heure d'ouverture pour tous les jours ouverts.") unless valid_hours?(opening_hours) - # Sauvegarder dans le cache - Rails.cache.write("opening_hours", opening_hours) - Rails.cache.write("opening_hours_updated_at", Time.current) + OpeningHour.replace_schedule!(schedule_hash: opening_hours, updated_by_user: updated_by) # Instrumentation pour audit ActiveSupport::Notifications.instrument( diff --git a/app/views/pages/become_member.html.erb b/app/views/pages/become_member.html.erb index c0144507..04a58755 100644 --- a/app/views/pages/become_member.html.erb +++ b/app/views/pages/become_member.html.erb @@ -176,7 +176,7 @@ <%# Panneau latéral : Horaires + Accès dans une seule carte %> <% schedule = @opening_hours - updated_at = Rails.cache.fetch("opening_hours_updated_at") + updated_at = OpeningHour.latest_update_entry&.updated_at %>