|
|
|
@ -1,111 +1,91 @@ |
|
|
|
class EntriesController < ApplicationController |
|
|
|
before_action :authenticate_user! |
|
|
|
before_action :set_entry, only: %i[edit update destroy stop_timer] |
|
|
|
before_action :set_entry, only: %i[ |
|
|
|
edit |
|
|
|
update |
|
|
|
destroy |
|
|
|
stop_timer |
|
|
|
] |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# INDEX |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def index |
|
|
|
@entries = current_user.entries.order(date: :desc) |
|
|
|
|
|
|
|
# Für Berechnungen nur Einträge bis einschließlich heute verwenden. |
|
|
|
# Keine Sortierung setzen, da diese Relation später unterschiedlich |
|
|
|
# ausgewertet wird. |
|
|
|
@calculation_entries = current_user.entries |
|
|
|
.where("date <= ?", Date.current) |
|
|
|
# Für sämtliche Fortschrittsberechnungen nur Einträge |
|
|
|
# bis einschließlich heute berücksichtigen. |
|
|
|
# |
|
|
|
# Zukünftige geplante Einträge erscheinen weiterhin in |
|
|
|
# @entries, verändern aber noch nicht den Fortschritt. |
|
|
|
@calculation_entries = |
|
|
|
current_user.entries.where("date <= ?", Date.current) |
|
|
|
|
|
|
|
@running_entry = current_user.entries.find_by( |
|
|
|
@running_entry = |
|
|
|
current_user.entries.find_by( |
|
|
|
end_time: nil, |
|
|
|
beschreibung: "Timer" |
|
|
|
) |
|
|
|
|
|
|
|
# ---------------------------------------------------------- |
|
|
|
# Gesamtzeiten |
|
|
|
# ---------------------------------------------------------- |
|
|
|
|
|
|
|
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 = |
|
|
|
sum_minutes(@calculation_entries) |
|
|
|
|
|
|
|
@total_minutes_praktikum_prop = sum_minutes( |
|
|
|
@total_minutes_praktikum_prop = |
|
|
|
sum_minutes( |
|
|
|
@calculation_entries.where( |
|
|
|
praktikums_typ: Entry::PRAKTIKUMSTYPEN[0], |
|
|
|
entry_art: Entry::ENTRY_ARTEN[0] |
|
|
|
praktikums_typ: "propädeutikum", |
|
|
|
entry_art: "Praktikum" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
@total_minutes_praktikum_fach = sum_minutes( |
|
|
|
@total_minutes_praktikum_fach = |
|
|
|
sum_minutes( |
|
|
|
@calculation_entries.where( |
|
|
|
praktikums_typ: Entry::PRAKTIKUMSTYPEN[1], |
|
|
|
entry_art: Entry::ENTRY_ARTEN[0] |
|
|
|
praktikums_typ: "fachspezifikum", |
|
|
|
entry_art: "Praktikum" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
@total_kilometer_pauschale = @entries.sum(&:kilometer_pauschale) |
|
|
|
|
|
|
|
# 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 |
|
|
|
|
|
|
|
@remaining_minutes_matrix = {} |
|
|
|
@spent_minutes_matrix = {} |
|
|
|
|
|
|
|
@time_by_typ_art.each do |typ, arts| |
|
|
|
@remaining_minutes_matrix[typ] ||= {} |
|
|
|
@spent_minutes_matrix[typ] ||= {} |
|
|
|
|
|
|
|
arts.each do |art, spent_minutes| |
|
|
|
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 |
|
|
|
@total_minutes_praktikum_mediation = |
|
|
|
sum_minutes( |
|
|
|
@calculation_entries.where( |
|
|
|
praktikums_typ: "mediation" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
@remaining_minutes_matrix[typ][art] = remaining_minutes |
|
|
|
@spent_minutes_matrix[typ][art] = spent_minutes |
|
|
|
end |
|
|
|
end |
|
|
|
# ---------------------------------------------------------- |
|
|
|
# Kilometer |
|
|
|
# ---------------------------------------------------------- |
|
|
|
|
|
|
|
@completion_percent_by_typ_art = {} |
|
|
|
@progress_chart_data = {} |
|
|
|
@progress_colors = {} |
|
|
|
@total_kilometer_pauschale = |
|
|
|
@entries.sum(&:kilometer_pauschale) |
|
|
|
|
|
|
|
@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 |
|
|
|
# ---------------------------------------------------------- |
|
|
|
# Tatsächlich geleistete Zeit nach Typ / Art |
|
|
|
# ---------------------------------------------------------- |
|
|
|
|
|
|
|
next unless required_minutes.positive? |
|
|
|
next unless spent_minutes.positive? |
|
|
|
build_time_by_typ_art |
|
|
|
|
|
|
|
completed_minutes = [spent_minutes, required_minutes].min |
|
|
|
# ---------------------------------------------------------- |
|
|
|
# Soll / Erledigt / Verbleibend |
|
|
|
# ---------------------------------------------------------- |
|
|
|
|
|
|
|
percent = ( |
|
|
|
completed_minutes / required_minutes.to_f * 100 |
|
|
|
).round |
|
|
|
build_progress_matrices |
|
|
|
|
|
|
|
percent = [percent, 100].min |
|
|
|
# ---------------------------------------------------------- |
|
|
|
# Prozentwerte + Diagramm |
|
|
|
# ---------------------------------------------------------- |
|
|
|
|
|
|
|
key = "#{typ.capitalize} – #{art}" |
|
|
|
build_completion_data |
|
|
|
|
|
|
|
@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 |
|
|
|
# ---------------------------------------------------------- |
|
|
|
# Kosten |
|
|
|
# ---------------------------------------------------------- |
|
|
|
|
|
|
|
@total_kilometer_costs_by_year = |
|
|
|
Entry.total_kilometer_cost_by_year(current_user) |
|
|
|
@ -125,39 +105,99 @@ class EntriesController < ApplicationController |
|
|
|
@gesamtkosten_by_year = |
|
|
|
Entry.total_gesamtkosten_by_year(current_user) |
|
|
|
|
|
|
|
calculate_estimated_end_dates(weekly_target_matrix) |
|
|
|
calculate_actual_hours_per_week(required_hours_matrix) |
|
|
|
# ---------------------------------------------------------- |
|
|
|
# Hochrechnungen |
|
|
|
# ---------------------------------------------------------- |
|
|
|
|
|
|
|
calculate_estimated_end_dates |
|
|
|
calculate_actual_hours_per_week |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# NEW |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def new |
|
|
|
@entry = current_user.entries.build |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# TIMER START |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def start_timer |
|
|
|
typ = params[:typ] |
|
|
|
art = params[:art] |
|
|
|
|
|
|
|
unless Entry::PRAKTIKUMSTYPEN.include?(typ) |
|
|
|
redirect_to entries_path, |
|
|
|
alert: "Ungültiger Ausbildungstyp." |
|
|
|
return |
|
|
|
end |
|
|
|
|
|
|
|
unless Entry::ENTRY_ARTEN.include?(art) |
|
|
|
redirect_to entries_path, |
|
|
|
alert: "Ungültige Eintragsart." |
|
|
|
return |
|
|
|
end |
|
|
|
|
|
|
|
if current_user.praepedeutikum_abgeschlossen? && |
|
|
|
typ == "propädeutikum" |
|
|
|
|
|
|
|
redirect_to entries_path, |
|
|
|
alert: "Propädeutikum ist bereits abgeschlossen – Neuer Eintrag dieses Typs ist nicht erlaubt." |
|
|
|
|
|
|
|
return |
|
|
|
end |
|
|
|
|
|
|
|
if current_user.entries.exists?( |
|
|
|
end_time: nil, |
|
|
|
beschreibung: "Timer" |
|
|
|
) |
|
|
|
redirect_to entries_path, |
|
|
|
alert: "Es läuft bereits ein Timer." |
|
|
|
return |
|
|
|
end |
|
|
|
|
|
|
|
current_user.entries.create!( |
|
|
|
date: Date.current, |
|
|
|
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 |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# TIMER STOP |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def stop_timer |
|
|
|
unless @entry.start_time.present? |
|
|
|
redirect_to entries_path, |
|
|
|
alert: "Dieser Eintrag besitzt keine Startzeit." |
|
|
|
return |
|
|
|
end |
|
|
|
|
|
|
|
@entry.end_time = Time.current |
|
|
|
|
|
|
|
if ActiveModel::Type::Boolean.new.cast(params[:lunch_break]) |
|
|
|
@entry.lunch_break_minutes = 30 |
|
|
|
else |
|
|
|
@entry.lunch_break_minutes ||= 0 |
|
|
|
end |
|
|
|
|
|
|
|
total_minutes = @entry.total_minutes_including_break |
|
|
|
total_minutes = |
|
|
|
@entry.total_minutes_including_break.to_i |
|
|
|
|
|
|
|
@entry.hours = total_minutes / 60 |
|
|
|
@entry.minutes = total_minutes % 60 |
|
|
|
@entry.hours = |
|
|
|
total_minutes / 60 |
|
|
|
|
|
|
|
@entry.minutes = |
|
|
|
total_minutes % 60 |
|
|
|
|
|
|
|
@entry.save! |
|
|
|
|
|
|
|
@ -165,11 +205,16 @@ class EntriesController < ApplicationController |
|
|
|
notice: "Eintrag gestoppt – Gesamtzeit: #{@entry.hours} h #{@entry.minutes} min" |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# 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 == Entry::PRAKTIKUMSTYPEN[0] |
|
|
|
@entry.praktikums_typ == "propädeutikum" |
|
|
|
|
|
|
|
redirect_to entries_path, |
|
|
|
alert: "Propädeutikum ist bereits abgeschlossen – Neuer Eintrag dieses Typs ist nicht erlaubt." |
|
|
|
@ -178,31 +223,53 @@ class EntriesController < ApplicationController |
|
|
|
end |
|
|
|
|
|
|
|
if @entry.save |
|
|
|
redirect_to entries_path, notice: "Eintrag gespeichert" |
|
|
|
redirect_to entries_path, |
|
|
|
notice: "Eintrag gespeichert" |
|
|
|
else |
|
|
|
render :new, status: :unprocessable_entity |
|
|
|
render :new, |
|
|
|
status: :unprocessable_entity |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# EDIT |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def edit |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# UPDATE |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def update |
|
|
|
if @entry.update(entry_params) |
|
|
|
redirect_to entries_path, notice: "Eintrag aktualisiert" |
|
|
|
redirect_to entries_path, |
|
|
|
notice: "Eintrag aktualisiert" |
|
|
|
else |
|
|
|
render :edit, status: :unprocessable_entity |
|
|
|
render :edit, |
|
|
|
status: :unprocessable_entity |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# DESTROY |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def destroy |
|
|
|
@entry.destroy! |
|
|
|
|
|
|
|
redirect_to entries_path, notice: "Eintrag gelöscht" |
|
|
|
redirect_to entries_path, |
|
|
|
notice: "Eintrag gelöscht" |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# CSV EXPORT |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def export_csv |
|
|
|
@entries = current_user.entries.order(date: :desc) |
|
|
|
@entries = |
|
|
|
current_user.entries.order(date: :desc) |
|
|
|
|
|
|
|
respond_to do |format| |
|
|
|
format.csv do |
|
|
|
@ -214,28 +281,44 @@ class EntriesController < ApplicationController |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# MONATSBERICHT |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def monthly_report |
|
|
|
rows = current_user.entries |
|
|
|
rows = |
|
|
|
current_user.entries |
|
|
|
.select( |
|
|
|
Arel.sql("DATE_TRUNC('month', date) AS month"), |
|
|
|
Arel.sql( |
|
|
|
"DATE_TRUNC('month', date) AS month" |
|
|
|
), |
|
|
|
:praktikums_typ, |
|
|
|
:entry_art, |
|
|
|
Arel.sql("SUM(hours * 60 + minutes) AS total_minutes") |
|
|
|
Arel.sql( |
|
|
|
"SUM(COALESCE(hours, 0) * 60 + COALESCE(minutes, 0)) AS total_minutes" |
|
|
|
) |
|
|
|
) |
|
|
|
.group( |
|
|
|
Arel.sql("DATE_TRUNC('month', date)"), |
|
|
|
Arel.sql( |
|
|
|
"DATE_TRUNC('month', date)" |
|
|
|
), |
|
|
|
:praktikums_typ, |
|
|
|
:entry_art |
|
|
|
) |
|
|
|
.order( |
|
|
|
Arel.sql("DATE_TRUNC('month', date) DESC") |
|
|
|
Arel.sql( |
|
|
|
"DATE_TRUNC('month', date) DESC" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
@report = {} |
|
|
|
|
|
|
|
rows.each do |row| |
|
|
|
month = row.attributes["month"].to_date |
|
|
|
total_minutes = row.attributes["total_minutes"].to_i |
|
|
|
month = |
|
|
|
row.attributes["month"].to_date |
|
|
|
|
|
|
|
total_minutes = |
|
|
|
row.attributes["total_minutes"].to_i |
|
|
|
|
|
|
|
next if total_minutes.zero? |
|
|
|
|
|
|
|
@ -256,52 +339,237 @@ class EntriesController < ApplicationController |
|
|
|
|
|
|
|
private |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# ENTRY LADEN |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def set_entry |
|
|
|
@entry = current_user.entries.find(params[:id]) |
|
|
|
@entry = |
|
|
|
current_user.entries.find(params[:id]) |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# MINUTEN SUMMIEREN |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def sum_minutes(entries) |
|
|
|
entries.sum do |entry| |
|
|
|
entry.hours.to_i * 60 + entry.minutes.to_i |
|
|
|
entry.hours.to_i * 60 + |
|
|
|
entry.minutes.to_i |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# GELEISTETE ZEIT NACH TYP / ART |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def build_time_by_typ_art |
|
|
|
@time_by_typ_art = |
|
|
|
@calculation_entries |
|
|
|
.group_by(&:praktikums_typ) |
|
|
|
.transform_values do |type_entries| |
|
|
|
|
|
|
|
type_entries |
|
|
|
.group_by(&:entry_art) |
|
|
|
.transform_values do |entries| |
|
|
|
sum_minutes(entries) |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
def calculate_estimated_end_dates(weekly_target_matrix) |
|
|
|
# ============================================================ |
|
|
|
# FORTSCHRITTSMATRIZEN |
|
|
|
# |
|
|
|
# Wichtig: |
|
|
|
# |
|
|
|
# Nicht nur Kombinationen mit bestehenden Entries erzeugen, |
|
|
|
# sondern ALLE bekannten Kombinationen. |
|
|
|
# |
|
|
|
# Dadurch existiert z. B. |
|
|
|
# |
|
|
|
# Mediation / Peergruppenarbeit |
|
|
|
# 0 h erledigt / 24 h offen |
|
|
|
# |
|
|
|
# auch bevor der erste Entry angelegt wurde. |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def build_progress_matrices |
|
|
|
@remaining_minutes_matrix = {} |
|
|
|
@spent_minutes_matrix = {} |
|
|
|
|
|
|
|
Entry::PRAKTIKUMSTYPEN.each do |typ| |
|
|
|
@remaining_minutes_matrix[typ] = {} |
|
|
|
@spent_minutes_matrix[typ] = {} |
|
|
|
|
|
|
|
Entry::ENTRY_ARTEN.each do |art| |
|
|
|
spent_minutes = |
|
|
|
@time_by_typ_art |
|
|
|
.dig(typ, art) |
|
|
|
.to_i |
|
|
|
|
|
|
|
required_hours = |
|
|
|
current_user |
|
|
|
.required_hours_for(typ, art) |
|
|
|
.to_f |
|
|
|
|
|
|
|
required_minutes = |
|
|
|
(required_hours * 60).round |
|
|
|
|
|
|
|
remaining_minutes = [ |
|
|
|
required_minutes - spent_minutes, |
|
|
|
0 |
|
|
|
].max |
|
|
|
|
|
|
|
@spent_minutes_matrix[typ][art] = |
|
|
|
spent_minutes |
|
|
|
|
|
|
|
@remaining_minutes_matrix[typ][art] = |
|
|
|
remaining_minutes |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# FORTSCHRITT IN PROZENT |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def build_completion_data |
|
|
|
@completion_percent_by_typ_art = {} |
|
|
|
@progress_chart_data = {} |
|
|
|
@progress_colors = {} |
|
|
|
|
|
|
|
@spent_minutes_matrix.each do |typ, arts| |
|
|
|
arts.each do |art, spent_minutes| |
|
|
|
required_hours = |
|
|
|
current_user |
|
|
|
.required_hours_for(typ, art) |
|
|
|
.to_f |
|
|
|
|
|
|
|
required_minutes = |
|
|
|
(required_hours * 60).round |
|
|
|
|
|
|
|
# Kategorien ohne Sollwert wie Fortbildung oder |
|
|
|
# Semesterkosten gehören nicht in die Fortschrittsrechnung. |
|
|
|
next unless required_minutes.positive? |
|
|
|
|
|
|
|
completed_minutes = [ |
|
|
|
spent_minutes, |
|
|
|
required_minutes |
|
|
|
].min |
|
|
|
|
|
|
|
percent = |
|
|
|
if required_minutes.positive? |
|
|
|
( |
|
|
|
completed_minutes / |
|
|
|
required_minutes.to_f * |
|
|
|
100 |
|
|
|
).round |
|
|
|
else |
|
|
|
0 |
|
|
|
end |
|
|
|
|
|
|
|
percent = [ |
|
|
|
percent, |
|
|
|
100 |
|
|
|
].min |
|
|
|
|
|
|
|
@completion_percent_by_typ_art[ |
|
|
|
[typ, art] |
|
|
|
] = percent |
|
|
|
|
|
|
|
# Im Chart nur Kategorien anzeigen, bei denen tatsächlich |
|
|
|
# bereits etwas erledigt wurde. |
|
|
|
# |
|
|
|
# Die Tabelle kennt trotzdem auch 0-%-Anforderungen. |
|
|
|
next unless spent_minutes.positive? |
|
|
|
|
|
|
|
key = |
|
|
|
"#{typ.capitalize} – #{art}" |
|
|
|
|
|
|
|
@progress_chart_data[key] = |
|
|
|
percent |
|
|
|
|
|
|
|
@progress_colors[key] = |
|
|
|
progress_color_for(percent) |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# FARBE FÜR FORTSCHRITT |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def progress_color_for(percent) |
|
|
|
if percent < 50 |
|
|
|
"#007bff" |
|
|
|
elsif percent < 90 |
|
|
|
"#28a745" |
|
|
|
else |
|
|
|
"#dc3545" |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# VORAUSSICHTLICHES ENDDATUM |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def calculate_estimated_end_dates |
|
|
|
@estimated_end_by_typ_art = {} |
|
|
|
|
|
|
|
@remaining_minutes_matrix.each do |typ, arts| |
|
|
|
@estimated_end_by_typ_art[typ] ||= {} |
|
|
|
@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 |
|
|
|
hours_remaining = |
|
|
|
remaining_minutes / 60.0 |
|
|
|
|
|
|
|
if weekly_hours.positive? && hours_remaining.positive? |
|
|
|
weeks_remaining = ( |
|
|
|
hours_remaining / weekly_hours |
|
|
|
weekly_hours = |
|
|
|
current_user |
|
|
|
.weekly_target_for(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 |
|
|
|
Date.current + |
|
|
|
weeks_remaining.weeks |
|
|
|
else |
|
|
|
@estimated_end_by_typ_art[typ][art] = nil |
|
|
|
@estimated_end_by_typ_art[typ][art] = |
|
|
|
nil |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
def calculate_actual_hours_per_week(required_hours_matrix) |
|
|
|
# ============================================================ |
|
|
|
# TATSÄCHLICHE STUNDEN PRO WOCHE |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def calculate_actual_hours_per_week |
|
|
|
@actual_hours_per_week = {} |
|
|
|
|
|
|
|
Entry::PRAKTIKUMSTYPEN |
|
|
|
.product(Entry::ENTRY_ARTEN) |
|
|
|
.each do |typ, art| |
|
|
|
|
|
|
|
required_hours = required_hours_matrix.dig(typ, art).to_f |
|
|
|
required_hours = |
|
|
|
current_user |
|
|
|
.required_hours_for(typ, art) |
|
|
|
.to_f |
|
|
|
|
|
|
|
# Ohne Sollwert ergibt eine Fortschrittsgeschwindigkeit |
|
|
|
# keinen sinnvollen Wert. |
|
|
|
next unless required_hours.positive? |
|
|
|
|
|
|
|
entries = @calculation_entries |
|
|
|
entries = |
|
|
|
@calculation_entries |
|
|
|
.where( |
|
|
|
praktikums_typ: typ, |
|
|
|
entry_art: art |
|
|
|
@ -312,17 +580,22 @@ class EntriesController < ApplicationController |
|
|
|
|
|
|
|
next unless entries.exists? |
|
|
|
|
|
|
|
spent_minutes = entries.sum do |entry| |
|
|
|
entry.hours.to_i * 60 + entry.minutes.to_i |
|
|
|
end |
|
|
|
spent_minutes = |
|
|
|
sum_minutes(entries) |
|
|
|
|
|
|
|
first_entry_date = entries.minimum(:date) |
|
|
|
last_entry_date = entries.maximum(:date) |
|
|
|
first_entry_date = |
|
|
|
entries.minimum(:date) |
|
|
|
|
|
|
|
last_entry_date = |
|
|
|
entries.maximum(:date) |
|
|
|
|
|
|
|
next unless first_entry_date |
|
|
|
|
|
|
|
required_minutes = (required_hours * 60).round |
|
|
|
completed = spent_minutes >= required_minutes |
|
|
|
required_minutes = |
|
|
|
(required_hours * 60).round |
|
|
|
|
|
|
|
completed = |
|
|
|
spent_minutes >= required_minutes |
|
|
|
|
|
|
|
period_end = |
|
|
|
if completed |
|
|
|
@ -331,19 +604,34 @@ class EntriesController < ApplicationController |
|
|
|
Date.current |
|
|
|
end |
|
|
|
|
|
|
|
total_days = (period_end - first_entry_date).to_i + 1 |
|
|
|
total_days = [total_days, 7].max |
|
|
|
total_days = |
|
|
|
(period_end - first_entry_date).to_i + 1 |
|
|
|
|
|
|
|
weeks = total_days / 7.0 |
|
|
|
# Mindestens eine Woche als Berechnungsbasis, |
|
|
|
# damit einzelne Tagesblöcke keine künstlich extrem |
|
|
|
# hohen Wochenwerte erzeugen. |
|
|
|
total_days = [ |
|
|
|
total_days, |
|
|
|
7 |
|
|
|
].max |
|
|
|
|
|
|
|
weeks = |
|
|
|
total_days / 7.0 |
|
|
|
|
|
|
|
actual_hours_per_week = |
|
|
|
(spent_minutes / 60.0) / weeks |
|
|
|
(spent_minutes / 60.0) / |
|
|
|
weeks |
|
|
|
|
|
|
|
@actual_hours_per_week[[typ, art]] = |
|
|
|
actual_hours_per_week.round(2) |
|
|
|
@actual_hours_per_week[ |
|
|
|
[typ, art] |
|
|
|
] = actual_hours_per_week.round(2) |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
# ============================================================ |
|
|
|
# STRONG PARAMS |
|
|
|
# ============================================================ |
|
|
|
|
|
|
|
def entry_params |
|
|
|
params.require(:entry).permit( |
|
|
|
:date, |
|
|
|
|