Browse Source

fix display

main
Christoph Marzell 4 weeks ago
parent
commit
52ffb6a2d8
  1. 394
      app/controllers/entries_controller.rb

394
app/controllers/entries_controller.rb

@ -3,155 +3,128 @@ class EntriesController < ApplicationController
before_action :set_entry, only: %i[edit update destroy stop_timer] before_action :set_entry, only: %i[edit update destroy stop_timer]
def index 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 }
@entries = current_user.entries.order(date: :desc)
# Nur Einträge bis einschließlich heute für Zeitberechnungen verwenden
@calculation_entries = current_user.entries
.where("date <= ?", Date.current)
.order(date: :desc)
@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 }
@running_entry = current_user.entries.find_by(
end_time: nil,
beschreibung: "Timer"
)
@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 }
required_hours_matrix = current_user.required_hours_matrix.to_h
weekly_target_matrix = current_user.weekly_target_matrix.to_h
@total_minutes = sum_minutes(@calculation_entries)
@total_minutes_praktikum_prop = sum_minutes(
@calculation_entries.where(
praktikums_typ: Entry::PRAKTIKUMSTYPEN[0],
entry_art: Entry::ENTRY_ARTEN[0]
)
)
@total_minutes_praktikum_fach = sum_minutes(
@calculation_entries.where(
praktikums_typ: Entry::PRAKTIKUMSTYPEN[1],
entry_art: Entry::ENTRY_ARTEN[0]
)
)
# Gesamtbetrag der Kilometerpauschale
@total_kilometer_pauschale = @entries.sum(&:kilometer_pauschale) @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
# Bereits geleistete Zeit je Kombination aus Typ und Art
@time_by_typ_art = @calculation_entries
.group_by(&:praktikums_typ)
.transform_values do |type_entries|
type_entries
.group_by(&:entry_art)
.transform_values { |entries| sum_minutes(entries) }
end end
# Verbleibende Minuten je Kombination
@remaining_minutes_matrix = {} @remaining_minutes_matrix = {}
@spent_minutes_matrix = {} @spent_minutes_matrix = {}
@time_by_typ_art.each do |typ, arts| @time_by_typ_art.each do |typ, arts|
@remaining_minutes_matrix[typ] ||= {} @remaining_minutes_matrix[typ] ||= {}
@spent_minutes_matrix[typ] ||= {}
@spent_minutes_matrix[typ] ||= {}
arts.each do |art, spent_minutes| 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
required_hours = required_hours_matrix.dig(typ, art).to_f
# Erst nach der Multiplikation runden, damit auch Dezimalstunden
# wie 0.5 korrekt als 30 Minuten berechnet werden.
required_minutes = (required_hours * 60).round
remaining_minutes = [
required_minutes - spent_minutes,
0
].max
@remaining_minutes_matrix[typ][art] = remaining_minutes
@spent_minutes_matrix[typ][art] = spent_minutes @spent_minutes_matrix[typ][art] = spent_minutes
end end
end end
@completion_percent_by_typ_art = {} @completion_percent_by_typ_art = {}
@progress_chart_data = {} @progress_chart_data = {}
@progress_colors = {}
@progress_colors = {}
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?
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
next if percent.zero?
@completion_percent_by_typ_art[[typ, art]] = percent
@progress_chart_data["#{typ.capitalize}#{art}"] = percent
else
percent = 0
next if percent.zero?
@completion_percent_by_typ_art[[typ, art]] = percent
@progress_chart_data["#{typ.capitalize}#{art}"] = percent
@spent_minutes_matrix.each do |typ, arts|
arts.each do |art, spent_minutes|
required_hours = required_hours_matrix.dig(typ, art).to_f
required_minutes = (required_hours * 60).round
next unless required_minutes.positive?
next unless spent_minutes.positive?
completed_minutes = [spent_minutes, required_minutes].min
percent = (
completed_minutes / required_minutes.to_f * 100
).round
percent = [percent, 100].min
key = "#{typ.capitalize}#{art}"
@completion_percent_by_typ_art[[typ, art]] = percent
@progress_chart_data[key] = percent
@progress_colors[key] =
if percent < 50
"#007bff"
elsif percent < 90
"#28a745"
else
"#dc3545"
end
end end
@progress_colors["#{typ.capitalize}#{art}"] = if percent < 50
'#007bff' # Blau
elsif percent < 90
'#28a745' # Grün
else
'#dc3545' # Rot
end
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)
@gesamtkosten_by_year = Entry.total_gesamtkosten_by_year(current_user)
@total_kilometer_costs_by_year =
Entry.total_kilometer_cost_by_year(current_user)
# Voraussichtliches Ende je Kombination basierend auf weekly_target_matrix
#
@estimated_end_by_typ_art = {}
@fortbildungskosten_by_year =
Entry.total_fortbildungskosten_by_year(current_user)
@selbstsupervision_by_year =
Entry.total_supervision_by_year(current_user)
@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
last_entry_date = current_user.entries
.where(praktikums_typ: typ, entry_art: art)
.maximum(:date)
#start_date = [last_entry_date, Date.today].compact.max
start_date = Date.current
if weekly_hours > 0 && hours_remaining > 0
weeks_remaining = (hours_remaining / weekly_hours).ceil
@estimated_end_by_typ_art[typ][art] = start_date + weeks_remaining.weeks
else
@estimated_end_by_typ_art[typ][art] = nil
end
end
end
@selbsterfahrungskosten_by_year =
Entry.total_selbsterfahrungskosten_by_year(current_user)
@allekosten_by_year =
Entry.total_semesterkosten_by_year(current_user)
@actual_hours_per_week = {}
@gesamtkosten_by_year =
Entry.total_gesamtkosten_by_year(current_user)
User::PRAKTIKUMSTYPEN.product(User::ENTRY_ARTEN).each do |typ, art|
required_hours = current_user.required_hours_matrix
.to_h
.dig(typ, art)
.to_f
next unless required_hours.positive?
entries = @calculation_entries
.where(praktikums_typ: typ, entry_art: art)
.where(
"COALESCE(hours, 0) > 0 OR COALESCE(minutes, 0) > 0"
)
.order(:date)
next if entries.empty?
spent_minutes = entries.sum do |entry|
entry.hours.to_i * 60 + entry.minutes.to_i
end
first_entry_date = entries.first.date
required_minutes = (required_hours * 60).to_i
completed = spent_minutes >= required_minutes
period_end =
if completed
entries.last.date
else
Date.current
end
total_days = (period_end - first_entry_date).to_i + 1
total_days = 7 if total_days < 7
weeks = total_days / 7.0
@actual_hours_per_week[[typ, art]] =
((spent_minutes / 60.0) / weeks).round(2)
end
calculate_estimated_end_dates(weekly_target_matrix)
calculate_actual_hours_per_week(required_hours_matrix)
end end
def new def new
@ -159,60 +132,70 @@ class EntriesController < ApplicationController
end end
def start_timer def start_timer
typ = params[:typ] typ = params[:typ]
art = params[:art] art = params[:art]
@entry = current_user.entries.create!(start_time: Time.current, beschreibung: "Timer", praktikums_typ: typ, entry_art: art)
current_user.entries.create!(
start_time: Time.current,
beschreibung: "Timer",
praktikums_typ: typ,
entry_art: art
)
redirect_to entries_path, notice: "Timer gestartet" redirect_to entries_path, notice: "Timer gestartet"
end end
# neue Aktion: Timer stoppen
def stop_timer 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"
@entry.end_time = Time.current
if ActiveModel::Type::Boolean.new.cast(params[:lunch_break])
@entry.lunch_break_minutes = 30
end end
redirect_to entries_path, notice: notice
total_minutes = @entry.total_minutes_including_break
@entry.hours = total_minutes / 60
@entry.minutes = total_minutes % 60
@entry.save!
redirect_to entries_path,
notice: "Eintrag gestoppt – Gesamtzeit: #{@entry.hours} h #{@entry.minutes} min"
end end
def create def create
@entry = current_user.entries.new(entry_params) @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."
if current_user.praepedeutikum_abgeschlossen? &&
@entry.praktikums_typ == Entry::PRAKTIKUMSTYPEN[0]
redirect_to entries_path,
alert: "Propädeutikum ist bereits abgeschlossen – Neuer Eintrag dieses Typs ist nicht erlaubt."
return return
end end
if @entry.save if @entry.save
redirect_to entries_path, notice: "Eintrag gespeichert" redirect_to entries_path, notice: "Eintrag gespeichert"
else else
render :new
render :new, status: :unprocessable_entity
end end
end end
def edit def edit
@entry
end end
def update def update
if @entry.update(entry_params) if @entry.update(entry_params)
redirect_to entries_path, notice: "Eintrag aktualisiert" redirect_to entries_path, notice: "Eintrag aktualisiert"
else else
render :edit
render :edit, status: :unprocessable_entity
end end
end end
def destroy def destroy
@entry.destroy
@entry.destroy!
redirect_to entries_path, notice: "Eintrag gelöscht" redirect_to entries_path, notice: "Eintrag gelöscht"
end end
@ -220,12 +203,16 @@ class EntriesController < ApplicationController
@entries = current_user.entries.order(date: :desc) @entries = current_user.entries.order(date: :desc)
respond_to do |format| respond_to do |format|
format.csv { send_data @entries.to_csv, filename: "eintraege-#{Date.today}.csv" }
format.csv do
send_data(
@entries.to_csv,
filename: "eintraege-#{Date.current}.csv"
)
end
end end
end end
def monthly_report def monthly_report
# Nur die wirklich benötigten Spalten abfragen
rows = current_user.entries rows = current_user.entries
.select( .select(
Arel.sql("DATE_TRUNC('month', date) AS month"), Arel.sql("DATE_TRUNC('month', date) AS month"),
@ -238,21 +225,29 @@ class EntriesController < ApplicationController
:praktikums_typ, :praktikums_typ,
:entry_art :entry_art
) )
.order(Arel.sql("DATE_TRUNC('month', date) DESC"))
.order(
Arel.sql("DATE_TRUNC('month', date) DESC")
)
# In Hash-Struktur für die View umwandeln
@report = {} @report = {}
rows.each do |r|
month = r.attributes["month"].to_date
total = r.attributes["total_minutes"].to_i
next if total.zero?
typ_art = [r.praktikums_typ, r.entry_art]
rows.each do |row|
month = row.attributes["month"].to_date
total_minutes = row.attributes["total_minutes"].to_i
next if total_minutes.zero?
typ_art = [
row.praktikums_typ,
row.entry_art
]
@report[month] ||= {}
@report[month] ||= {}
@report[month][typ_art] = { @report[month][typ_art] = {
total_minutes: total,
hours: total / 60,
minutes: total % 60
total_minutes: total_minutes,
hours: total_minutes / 60,
minutes: total_minutes % 60
} }
end end
end end
@ -263,6 +258,88 @@ class EntriesController < ApplicationController
@entry = current_user.entries.find(params[:id]) @entry = current_user.entries.find(params[:id])
end end
def sum_minutes(entries)
entries.sum do |entry|
entry.hours.to_i * 60 + entry.minutes.to_i
end
end
def calculate_estimated_end_dates(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 = weekly_target_matrix.dig(typ, art).to_f
if weekly_hours.positive? && hours_remaining.positive?
weeks_remaining = (
hours_remaining / weekly_hours
).ceil
@estimated_end_by_typ_art[typ][art] =
Date.current + weeks_remaining.weeks
else
@estimated_end_by_typ_art[typ][art] = nil
end
end
end
end
def calculate_actual_hours_per_week(required_hours_matrix)
@actual_hours_per_week = {}
Entry::PRAKTIKUMSTYPEN
.product(Entry::ENTRY_ARTEN)
.each do |typ, art|
required_hours = required_hours_matrix.dig(typ, art).to_f
next unless required_hours.positive?
entries = @calculation_entries
.where(
praktikums_typ: typ,
entry_art: art
)
.where(
"COALESCE(hours, 0) > 0 OR COALESCE(minutes, 0) > 0"
)
.order(:date)
.to_a
next if entries.empty?
spent_minutes = sum_minutes(entries)
first_entry_date = entries.first.date
last_entry_date = entries.last.date
required_minutes = (required_hours * 60).round
completed = spent_minutes >= required_minutes
period_end =
if completed
last_entry_date
else
Date.current
end
total_days = (period_end - first_entry_date).to_i + 1
total_days = [total_days, 7].max
weeks = total_days / 7.0
actual_hours = spent_minutes / 60.0
@actual_hours_per_week[[typ, art]] = (
actual_hours / weeks
).round(2)
end
end
def entry_params def entry_params
params.require(:entry).permit( params.require(:entry).permit(
:date, :date,
@ -278,5 +355,4 @@ class EntriesController < ApplicationController
:end_time :end_time
) )
end end
end
end
Loading…
Cancel
Save