From 8f0f756acf471d818c5baa5ccde4e91f3ef73c75 Mon Sep 17 00:00:00 2001 From: Christoph Marzell Date: Mon, 1 Dec 2025 06:26:52 +0100 Subject: [PATCH] add variable kilometergeld --- app/controllers/mileage_rates_controller.rb | 47 +++++++++++++++++++ app/helpers/entries_helper.rb | 20 ++++++++ app/models/entry.rb | 19 ++++---- app/models/mileage_rate.rb | 14 ++++++ app/views/entries/index.html.erb | 3 ++ app/views/mileage_rates/_form.html.erb | 27 +++++++++++ app/views/mileage_rates/edit.html.erb | 2 + app/views/mileage_rates/index.html.erb | 24 ++++++++++ app/views/mileage_rates/new.html.erb | 2 + config/routes.rb | 3 ++ .../20251201050251_create_mileage_rates.rb | 15 ++++++ db/schema.rb | 11 ++++- 12 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 app/controllers/mileage_rates_controller.rb create mode 100644 app/models/mileage_rate.rb create mode 100644 app/views/mileage_rates/_form.html.erb create mode 100644 app/views/mileage_rates/edit.html.erb create mode 100644 app/views/mileage_rates/index.html.erb create mode 100644 app/views/mileage_rates/new.html.erb create mode 100644 db/migrate/20251201050251_create_mileage_rates.rb diff --git a/app/controllers/mileage_rates_controller.rb b/app/controllers/mileage_rates_controller.rb new file mode 100644 index 0000000..80ef373 --- /dev/null +++ b/app/controllers/mileage_rates_controller.rb @@ -0,0 +1,47 @@ +class MileageRatesController < ApplicationController + before_action :authenticate_user! + # Optional: nur Admins erlauben + # before_action :require_admin! + + before_action :set_mileage_rate, only: [:edit, :update] + + def index + @mileage_rates = MileageRate.order(year: :desc) + end + + def new + @mileage_rate = MileageRate.new + end + + def create + @mileage_rate = MileageRate.new(mileage_rate_params) + if @mileage_rate.save + redirect_to mileage_rates_path, notice: "Kilometersatz für #{@mileage_rate.year} gespeichert." + else + flash.now[:alert] = "Speichern fehlgeschlagen" + render :new, status: :unprocessable_entity + end + end + + def edit + end + + def update + if @mileage_rate.update(mileage_rate_params) + redirect_to mileage_rates_path, notice: "Kilometersatz für #{@mileage_rate.year} aktualisiert." + else + flash.now[:alert] = "Aktualisierung fehlgeschlagen" + render :edit, status: :unprocessable_entity + end + end + + private + + def set_mileage_rate + @mileage_rate = MileageRate.find(params[:id]) + end + + def mileage_rate_params + params.require(:mileage_rate).permit(:year, :rate_per_km) + end +end diff --git a/app/helpers/entries_helper.rb b/app/helpers/entries_helper.rb index ed8c595..e43e048 100644 --- a/app/helpers/entries_helper.rb +++ b/app/helpers/entries_helper.rb @@ -1,2 +1,22 @@ module EntriesHelper + # Berechnet Kilometerpauschale für eine Entry anhand der gespeicherten MileageRate + # entry: muss attribute `distance_km` und `date` haben + def kilometrergeld_for(entry) + rate = MileageRate.for_year(entry.date.year) + if rate + (entry.distance_km.to_f * rate).round(2) + else + # Fallback: z. B. Standardrate 0.42 + (entry.distance_km.to_f * 0.05).round(2) + end + end + + # Für Summen: sum_meilen ist Summe der km oder Array von entries + def total_km_cost_for_year(user, year) + rate = MileageRate.for_year(year) || 0.42 + total_km = user.entries + .where("EXTRACT(YEAR FROM date) = ?", year) + .sum(:distance_km).to_f + (total_km * rate).round(2) + end end diff --git a/app/models/entry.rb b/app/models/entry.rb index 9245653..9c6c91a 100644 --- a/app/models/entry.rb +++ b/app/models/entry.rb @@ -29,14 +29,17 @@ class Entry < ApplicationRecord end def self.total_kilometer_cost_by_year(user) - where(user: user) - .where.not(distance_km: nil) - .group(Arel.sql("DATE_PART('year', date)")) - .order(Arel.sql("DATE_PART('year', date) DESC")) - .pluck( - Arel.sql("DATE_PART('year', date)::int"), - Arel.sql("SUM(distance_km * 0.42)") - ).to_h + yearly_distances = where(user: user) + .where.not(distance_km: nil) + .group("EXTRACT(YEAR FROM date)::int") + .sum(:distance_km) + + # Für jedes Jahr die Pauschale holen und multiplizieren + yearly_distances.transform_keys(&:to_i).transform_values do |km_sum| + year = yearly_distances.key(km_sum).to_i + rate = MileageRate.find_by(year: year)&.rate_per_km || 0.42 + (km_sum * rate).round(2) + end end def self.total_fortbildungskosten_by_year(user) diff --git a/app/models/mileage_rate.rb b/app/models/mileage_rate.rb new file mode 100644 index 0000000..b424007 --- /dev/null +++ b/app/models/mileage_rate.rb @@ -0,0 +1,14 @@ +class MileageRate < ApplicationRecord + validates :year, presence: true, uniqueness: true + validates :rate_per_km, presence: true, numericality: { greater_than_or_equal_to: 0 } + + # Gibt die Rate für ein gegebenes Jahr — oder nil + def self.for_year(year) + find_by(year: year)&.rate_per_km + end + + # Gibt die Rate für heute bzw. aktuelles Jahr + def self.current + for_year(Date.today.year) + end +end diff --git a/app/views/entries/index.html.erb b/app/views/entries/index.html.erb index 2787081..4dceae7 100644 --- a/app/views/entries/index.html.erb +++ b/app/views/entries/index.html.erb @@ -7,8 +7,11 @@
🚗 Fahrtkosten (Kilometergeld)
<% @total_kilometer_costs_by_year.each do |year, sum| %> + <% rate = MileageRate.find_by(year: year)&.rate_per_km || 0.42 %> + <% km = rate > 0 ? (sum.to_f / rate).round(1) : "?" %>

<%= year %>: <%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %> + (<%= km %> km bei <%= number_to_currency(rate, unit: "€", separator: ",", delimiter: ".", precision: 2) %>/km)

<% end %>
diff --git a/app/views/mileage_rates/_form.html.erb b/app/views/mileage_rates/_form.html.erb new file mode 100644 index 0000000..4bd76a4 --- /dev/null +++ b/app/views/mileage_rates/_form.html.erb @@ -0,0 +1,27 @@ +<%= form_with(model: mileage_rate, local: true) do |form| %> + <% if mileage_rate.errors.any? %> +
+

<%= pluralize(mileage_rate.errors.count, "Fehler") %> verhinderten das Speichern:

+ +
+ <% end %> + +
+ <%= form.label :year, "Jahr", class: "form-label" %> + <%= form.number_field :year, class: "form-control", min: 2000 %> +
+ +
+ <%= form.label :rate_per_km, "Kilometersatz (€/km)", class: "form-label" %> + <%= form.number_field :rate_per_km, class: "form-control", step: 0.01, min: 0 %> +
+ +
+ <%= form.submit class: "btn btn-success" %> + <%= link_to "Abbrechen", mileage_rates_path, class: "btn btn-secondary ms-2" %> +
+<% end %> diff --git a/app/views/mileage_rates/edit.html.erb b/app/views/mileage_rates/edit.html.erb new file mode 100644 index 0000000..c5ae999 --- /dev/null +++ b/app/views/mileage_rates/edit.html.erb @@ -0,0 +1,2 @@ +

Satz für Jahr <%= @mileage_rate.year %> bearbeiten

+<%= render "form", mileage_rate: @mileage_rate %> diff --git a/app/views/mileage_rates/index.html.erb b/app/views/mileage_rates/index.html.erb new file mode 100644 index 0000000..10f9306 --- /dev/null +++ b/app/views/mileage_rates/index.html.erb @@ -0,0 +1,24 @@ +

Kilometersätze je Jahr

+ +<%= link_to "Neuen Satz anlegen", new_mileage_rate_path, class: "btn btn-primary mb-3" %> + + + + + + + + + + + <% @mileage_rates.each do |rate| %> + + + + + + <% end %> + +
JahrSatz (€/km)Aktionen
<%= rate.year %><%= number_to_currency(rate.rate_per_km, unit: "€", separator: ",", delimiter: ".", precision: 2) %> + <%= link_to "Bearbeiten", edit_mileage_rate_path(rate), class: "btn btn-sm btn-outline-secondary" %> +
diff --git a/app/views/mileage_rates/new.html.erb b/app/views/mileage_rates/new.html.erb new file mode 100644 index 0000000..7aa3726 --- /dev/null +++ b/app/views/mileage_rates/new.html.erb @@ -0,0 +1,2 @@ +

Neuen Kilometersatz anlegen

+<%= render "form", mileage_rate: @mileage_rate %> diff --git a/config/routes.rb b/config/routes.rb index 1c9f7a6..8f7267f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,6 +14,9 @@ Rails.application.routes.draw do end end + resources :mileage_rates, only: [:index, :new, :create, :edit, :update] + + resources :entries do post :start_timer, on: :collection member do diff --git a/db/migrate/20251201050251_create_mileage_rates.rb b/db/migrate/20251201050251_create_mileage_rates.rb new file mode 100644 index 0000000..6355191 --- /dev/null +++ b/db/migrate/20251201050251_create_mileage_rates.rb @@ -0,0 +1,15 @@ +class CreateMileageRates < ActiveRecord::Migration[7.1] + def change + create_table :mileage_rates do |t| + t.integer :year, null: false, index: { unique: true } + t.decimal :rate_per_km, precision: 5, scale: 2, null: false + t.timestamps + end + [2024, 2025, 2026].each do |year| + MileageRate.create_or_find_by(year: year) do |rate| + rate.rate_per_km = 0.42 + end + end + + end +end diff --git a/db/schema.rb b/db/schema.rb index cf96734..7ca2c79 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,9 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_11_20_105001) do +ActiveRecord::Schema[7.1].define(version: 2025_12_01_050251) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_stat_statements" enable_extension "plpgsql" create_table "entries", force: :cascade do |t| @@ -42,6 +43,14 @@ ActiveRecord::Schema[7.1].define(version: 2025_11_20_105001) do t.index ["user_id"], name: "index_entries_on_user_id" end + create_table "mileage_rates", force: :cascade do |t| + t.integer "year", null: false + t.decimal "rate_per_km", precision: 5, scale: 2, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["year"], name: "index_mileage_rates_on_year", unique: true + end + create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false