class EntriesController < ApplicationController before_action :authenticate_user! before_action :set_entry, only: %i[edit update destroy stop_timer] def index @entries = current_user.entries.order(date: :desc) @running_entry = current_user.entries.find_by(end_time: nil, beschreibung: "Timer") # Gesamtzeit in Minuten @total_minutes = @entries.sum { |e| e.hours.to_i * 60 + e.minutes.to_i } @total_minutes_praktikum_prop = @entries.where(praktikums_typ: Entry::PRAKTIKUMSTYPEN[0], entry_art: Entry::ENTRY_ARTEN[0]).sum { |e| e.hours.to_i * 60 + e.minutes.to_i } @total_minutes_praktikum_fach = @entries.where(praktikums_typ: Entry::PRAKTIKUMSTYPEN[1], entry_art: Entry::ENTRY_ARTEN[0]).sum { |e| e.hours.to_i * 60 + e.minutes.to_i } # Gesamtbetrag der Kilometerpauschale @total_kilometer_pauschale = @entries.sum(&:kilometer_pauschale) # Zeitverbrauch je Kombination (typ + art) @time_by_typ_art = @entries.group_by(&:praktikums_typ).transform_values do |group| group.group_by(&:entry_art).transform_values do |entries| entries.sum { |e| e.hours.to_i * 60 + e.minutes.to_i } end end @completion_percent_by_typ_art = {} # Verbleibende Minuten je Kombination @remaining_minutes_matrix = {} @time_by_typ_art.each do |typ, arts| @remaining_minutes_matrix[typ] ||= {} arts.each do |art, spent_minutes| target = current_user.required_hours_matrix.dig(typ, art).to_i * 60 remaining = [target - spent_minutes, 0].max @remaining_minutes_matrix[typ][art] = remaining end end User::PRAKTIKUMSTYPEN.product(User::ENTRY_ARTEN).each do |typ, art| total_required = current_user.required_hours_matrix.to_h.dig(typ, art).to_f remaining_minutes = @remaining_minutes_matrix.dig(typ, art).to_i required_minutes = (total_required * 60).to_i entries_exist = current_user.entries.where(praktikums_typ: typ, entry_art: art).exists? Rails.logger.info "=== #{art} #{typ} ===" Rails.logger.info "Erforderlich (min): #{required_minutes}, Verbleibend: #{remaining_minutes}, Einträge vorhanden? #{entries_exist}" if total_required > 0 && entries_exist done_minutes = required_minutes - remaining_minutes percent = (done_minutes / required_minutes.to_f * 100).round percent = 100 if percent > 100 @completion_percent_by_typ_art[[typ, art]] = percent else @completion_percent_by_typ_art[[typ, art]] = 0 end end @total_kilometer_costs_by_year = Entry.total_kilometer_cost_by_year(current_user) @fortbildungskosten_by_year = Entry.total_fortbildungskosten_by_year(current_user) @selbstsupervision_by_year = Entry.total_supervision_by_year(current_user) @selbsterfahrungskosten_by_year = Entry.total_selbsterfahrungskosten_by_year(current_user) @allekosten_by_year = Entry.total_semesterkosten_by_year(current_user) # Voraussichtliches Ende je Kombination basierend auf weekly_target_matrix # @estimated_end_by_typ_art = {} @remaining_minutes_matrix.each do |typ, arts| @estimated_end_by_typ_art[typ] ||= {} arts.each do |art, remaining_minutes| hours_remaining = remaining_minutes / 60.0 weekly_hours = current_user.weekly_target_matrix.dig(typ, art).to_f if weekly_hours > 0 weeks_remaining = (hours_remaining / weekly_hours).ceil @estimated_end_by_typ_art[typ][art] = Date.today + weeks_remaining.weeks else @estimated_end_by_typ_art[typ][art] = nil end end end end def new @entry = current_user.entries.build end def start_timer typ = params[:typ] art = params[:art] @entry = current_user.entries.create!(start_time: Time.current, beschreibung: "Timer", praktikums_typ: typ, entry_art: art) redirect_to entries_path, notice: "Timer gestartet" end # neue Aktion: Timer stoppen def stop_timer #@entry = current_user.entries.where(end_time: nil).order(start_time: :desc).first if @entry @entry.end_time = Time.current @entry.lunch_break_minutes = 30 if ActiveModel::Type::Boolean.new.cast(params[:lunch_break]) total_minutes = @entry.total_minutes_including_break @entry.hours = total_minutes / 60 @entry.minutes = total_minutes % 60 @entry.save! notice = "Eintrag gestoppt – Gesamtzeit: #{@entry.hours} h #{@entry.minutes} min" else notice = "Kein laufender Eintrag gefunden" end redirect_to entries_path, notice: notice end def create @entry = current_user.entries.new(entry_params) if current_user.praepedeutikum_abgeschlossen? && @entry.praktikums_typ == 'propädeutikum' redirect_to entries_path, alert: "Propädeutikum ist bereits abgeschlossen – Neuer Eintrag dieses Typs ist nicht erlaubt." return end if @entry.save redirect_to entries_path, notice: "Eintrag gespeichert" else render :new end end def edit @entry end def update if @entry.update(entry_params) redirect_to entries_path, notice: "Eintrag aktualisiert" else render :edit end end def destroy @entry.destroy redirect_to entries_path, notice: "Eintrag gelöscht" end def export_csv @entries = current_user.entries.order(date: :desc) respond_to do |format| format.csv { send_data @entries.to_csv, filename: "eintraege-#{Date.today}.csv" } end end def monthly_report # Nur die wirklich benötigten Spalten abfragen rows = current_user.entries .select( Arel.sql("DATE_TRUNC('month', date) AS month"), :praktikums_typ, :entry_art, Arel.sql("SUM(hours * 60 + minutes) AS total_minutes") ) .group( Arel.sql("DATE_TRUNC('month', date)"), :praktikums_typ, :entry_art ) .order(Arel.sql("DATE_TRUNC('month', date) DESC")) # In Hash-Struktur für die View umwandeln @report = {} rows.each do |r| month = r.attributes["month"].to_date total = r.attributes["total_minutes"].to_i typ_art = [r.praktikums_typ, r.entry_art] @report[month] ||= {} @report[month][typ_art] = { total_minutes: total, hours: total / 60, minutes: total % 60 } end end private def set_entry @entry = current_user.entries.find(params[:id]) end def entry_params params.require(:entry).permit( :date, :hours, :minutes, :praktikums_typ, :entry_art, :distance_km, :beschreibung, :kosten, :zaehlt_als_fortbildung ) end end