Browse Source

add variable kilometergeld

main
Christoph Marzell 2 weeks ago
parent
commit
8f0f756acf
  1. 47
      app/controllers/mileage_rates_controller.rb
  2. 20
      app/helpers/entries_helper.rb
  3. 19
      app/models/entry.rb
  4. 14
      app/models/mileage_rate.rb
  5. 3
      app/views/entries/index.html.erb
  6. 27
      app/views/mileage_rates/_form.html.erb
  7. 2
      app/views/mileage_rates/edit.html.erb
  8. 24
      app/views/mileage_rates/index.html.erb
  9. 2
      app/views/mileage_rates/new.html.erb
  10. 3
      config/routes.rb
  11. 15
      db/migrate/20251201050251_create_mileage_rates.rb
  12. 11
      db/schema.rb

47
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

20
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

19
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)

14
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

3
app/views/entries/index.html.erb

@ -7,8 +7,11 @@
<div class="col rounded border shadow-sm p-3">
<h5>🚗 Fahrtkosten (Kilometergeld)</h5>
<% @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) : "?" %>
<p><strong><%= year %>:</strong>
<%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %>
<i>(<%= km %> km bei <%= number_to_currency(rate, unit: "€", separator: ",", delimiter: ".", precision: 2) %>/km)</i>
</p>
<% end %>
</div>

27
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? %>
<div class="alert alert-danger">
<h4><%= pluralize(mileage_rate.errors.count, "Fehler") %> verhinderten das Speichern:</h4>
<ul>
<% mileage_rate.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="mb-3">
<%= form.label :year, "Jahr", class: "form-label" %>
<%= form.number_field :year, class: "form-control", min: 2000 %>
</div>
<div class="mb-3">
<%= form.label :rate_per_km, "Kilometersatz (€/km)", class: "form-label" %>
<%= form.number_field :rate_per_km, class: "form-control", step: 0.01, min: 0 %>
</div>
<div class="mb-3">
<%= form.submit class: "btn btn-success" %>
<%= link_to "Abbrechen", mileage_rates_path, class: "btn btn-secondary ms-2" %>
</div>
<% end %>

2
app/views/mileage_rates/edit.html.erb

@ -0,0 +1,2 @@
<h1>Satz für Jahr <%= @mileage_rate.year %> bearbeiten</h1>
<%= render "form", mileage_rate: @mileage_rate %>

24
app/views/mileage_rates/index.html.erb

@ -0,0 +1,24 @@
<h1>Kilometersätze je Jahr</h1>
<%= link_to "Neuen Satz anlegen", new_mileage_rate_path, class: "btn btn-primary mb-3" %>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Jahr</th>
<th>Satz (€/km)</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% @mileage_rates.each do |rate| %>
<tr>
<td><%= rate.year %></td>
<td><%= number_to_currency(rate.rate_per_km, unit: "€", separator: ",", delimiter: ".", precision: 2) %></td>
<td>
<%= link_to "Bearbeiten", edit_mileage_rate_path(rate), class: "btn btn-sm btn-outline-secondary" %>
</td>
</tr>
<% end %>
</tbody>
</table>

2
app/views/mileage_rates/new.html.erb

@ -0,0 +1,2 @@
<h1>Neuen Kilometersatz anlegen</h1>
<%= render "form", mileage_rate: @mileage_rate %>

3
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

15
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

11
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

Loading…
Cancel
Save