From a19cc6984f1f2093b00d4e066e3498997b230ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20B=C3=B6hm?= Date: Sat, 23 May 2026 03:18:56 +0200 Subject: [PATCH] Updated/Fixed Item form and item show --- app/controllers/items_controller.rb | 61 +++++++--- app/models/item.rb | 20 +++- app/views/items/_form.html.erb | 173 +++++++++++++++++----------- app/views/items/show.html.erb | 105 ++++++++--------- 4 files changed, 216 insertions(+), 143 deletions(-) diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index ac9c618..98241e8 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -27,16 +27,23 @@ class ItemsController < ApplicationController # GET /items/new def new @item = Item.new + # Falls wir uns im Turbo-Frame befinden, rendern wir nur das Formular-Teilstück + render partial: "form", locals: { item: @item } if turbo_frame_request? end # GET /items/1/edit def edit + # @item wird bereits über vorab gesetztes set_item geladen + render partial: "form", locals: { item: @item } if turbo_frame_request? end # POST /items or /items.json def create @item = Item.new(item_params) + # Text-Eingaben in echte IDs auflösen + resolve_assignment_names + respond_to do |format| if @item.save format.html { redirect_to @item, notice: "Artikel '#{@item.name}' wurde erfolgreich im System registriert." } @@ -50,8 +57,13 @@ class ItemsController < ApplicationController # PATCH/PUT /items/1 or /items/1.json def update + @item.assign_attributes(item_params) + + # Text-Eingaben in echte IDs auflösen + resolve_assignment_names + respond_to do |format| - if @item.update(item_params) + if @item.save format.html { redirect_to @item, notice: "Artikel '#{@item.name} wurde erfolgreich aktualisiert.", status: :see_other } format.json { render :show, status: :ok, location: @item } else @@ -73,22 +85,37 @@ class ItemsController < ApplicationController private - def set_item - @item = Item.find(params.expect(:id)) - end + def set_item + @item = Item.find(params.expect(:id)) + end - # Strong Parameters: Schützt vor Mass-Assignment-Injections - def item_params - params.require(:item).permit( - :name, - :sku, - :sticker_id, - :serial_number, - :price, - :notes, - :category_id, - :user_id, # Für die flexible Zuweisung an Mitarbeiter - :room_id # Für die flexible Zuweisung an Räume - ) + # Sucht anhand des eingetippten Namens den passenden Datenbank-Eintrag + def resolve_assignment_names + if params[:item][:user_name].present? + # Wir splitten den Namen in Vor- und Nachname auf + parts = params[:item][:user_name].split(" ") + user = User.find_by(first_name: parts[0], last_name: parts[1]) + + if user + @item.user_id = user.id + @item.room_id = nil # Sicherstellen, dass der Raum geleert wird + else + @item.errors.add(:base, "Der eingegebene Mitarbeiter existiert nicht im System.") + end + elsif params[:item][:room_name].present? + room = Room.find_by(name: params[:item][:room_name]) + + if room + @item.room_id = room.id + @item.user_id = nil # Sicherstellen, dass der User geleert wird + else + @item.errors.add(:base, "Der eingegebene Raum existiert nicht im System.") + end end + end + + def item_params + # 'user_name' und 'room_name' müssen in die Strong Parameters aufgenommen werden! + params.require(:item).permit(:name, :sku, :sticker_id, :serial_number, :price, :notes, :category_id, :user_id, :room_id, :user_name, :room_name) + end end diff --git a/app/models/item.rb b/app/models/item.rb index c0e7a96..09c6909 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -2,6 +2,10 @@ require "rqrcode" require "csv" class Item < ApplicationRecord + # NEU: Erlaubt es Rails, diese Formularfelder temporär im Speicher zu halten, + # ohne dass dafür Spalten in der Datenbank existieren müssen. + attr_accessor :user_name, :room_name + belongs_to :category belongs_to :user, optional: true # Optional, falls im Raum oder Lager belongs_to :room, optional: true # Optional, falls beim User oder Lager @@ -76,15 +80,19 @@ class Item < ApplicationRecord end def track_assignment_changes - # 1. Altes Log-Buch schließen, falls es eine vorherige Zuweisung gab - if user_id_was.present? || room_id_was.present? - last_log = assignment_logs.find_by(user_id: user_id_was, room_id: room_id_was, returned_at: nil) - last_log&.update(returned_at: Time.current) - end + # 1. Altes Log-Buch schließen (Egal ob es bei einem User oder in einem Raum war) + # Wir suchen nach dem Log, das noch kein Rückgabedatum hat (returned_at: nil) + active_log = assignment_logs.find_by(returned_at: nil) + active_log&.update(returned_at: Time.current) - # 2. Neues Log-Buch öffnen für den neuen Inhaber oder den neuen Raum + # 2. Neues Log-Buch öffnen if user_id.present? || room_id.present? + # Zustand A: Zuweisung an Mitarbeiter oder Raum assignment_logs.build(user_id: user_id, room_id: room_id, assigned_at: Time.current) + else + # Zustand B: Weder User noch Raum hinterlegt -> Das Gerät wandert ins Hauptlager! + # Wir lassen user_id und room_id auf nil, was im Logbuch "Hauptlager" bedeutet + assignment_logs.build(assigned_at: Time.current) end end end diff --git a/app/views/items/_form.html.erb b/app/views/items/_form.html.erb index 8968858..af902c3 100644 --- a/app/views/items/_form.html.erb +++ b/app/views/items/_form.html.erb @@ -1,22 +1,3 @@ - - <%= form_with(model: item, class: "space-y-6 max-w-2xl mx-auto bg-white border border-gray-200 rounded-xl shadow-sm p-6 md:p-8") do |form| %> <% if item.errors.any? %> @@ -30,16 +11,14 @@ <% end %> -
- -

- <%= item.new_record? ? "Artikel registrieren" : "Artikel bearbeiten" %> -

- -

- <%= item.new_record? ? "Trage hier die Unikat-Daten wie Seriennummer und Sticker-ID ein." : "Aktualisiere die Daten oder verändere den aktuellen Standort des Geräts." %> -

-
+
+

+ <%= item.new_record? ? "Artikel registrieren" : "Artikel bearbeiten" %> +

+

+ <%= item.new_record? ? "Trage hier die Unikat-Daten wie Seriennummer und Sticker-ID ein." : "Aktualisiere die Daten oder verändere den aktuellen Standort des Geräts." %> +

+

@@ -47,37 +26,37 @@
<%= form.label :name, "Artikelname / Modell", class: "block text-sm font-medium mb-1.5 text-gray-700" %> - <%= form.text_field :name, class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50" %> + <%= form.text_field :name, class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50 focus:border-blue-500 focus:ring-blue-500" %>
<%= form.label :sku, "SKU (Artikelnummer)", class: "block text-sm font-medium mb-1.5 text-gray-700" %> - <%= form.text_field :sku, class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50" %> + <%= form.text_field :sku, class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50 focus:border-blue-500 focus:ring-blue-500" %>
- +
<%= form.label :serial_number, "Seriennummer (Hersteller)", class: "block text-sm font-medium mb-1.5 text-gray-700" %> - <%= form.text_field :serial_number, class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50" %> + <%= form.text_field :serial_number, class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50 focus:border-blue-500 focus:ring-blue-500" %>
<%= form.label :price, "Einkaufspreis (€)", class: "block text-sm font-medium mb-1.5 text-gray-700" %> - <%= form.text_field :price, placeholder: "0.00", class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50" %> + <%= form.text_field :price, placeholder: "0.00", class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50 focus:border-blue-500 focus:ring-blue-500" %>
<%= form.label :category_id, "Kategorie", class: "block text-sm font-medium mb-1.5 text-gray-700" %> - <%= form.collection_select :category_id, Category.all, :id, :name, { prompt: "Bitte wählen..." }, { class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-white" } %> + <%= form.collection_select :category_id, Category.all, :id, :name, { prompt: "Bitte wählen..." }, { class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-white focus:border-blue-500 focus:ring-blue-500" } %>
- +
<%= form.label :sticker_id, "Vorgedruckte Sticker-ID / QR-Nummer", class: "block text-sm font-medium mb-1.5 text-gray-700" %>
- <%= form.text_field :sticker_id, data: { scanner_target: "input" }, class: "py-2 px-3 block w-full border border-gray-300 rounded-l-lg text-sm bg-gray-50/50" %> + <%= form.text_field :sticker_id, data: { scanner_target: "input" }, class: "py-2 px-3 block w-full border border-gray-300 rounded-l-lg text-sm bg-gray-50/50 focus:border-blue-500 focus:ring-blue-500" %> @@ -86,44 +65,108 @@
- -
+ + + +
-
- - - -
+ <%= turbo_frame_tag "item_assignment_frame" do %> + <% + current_type = if item.user_id.present? + "user" + elsif item.room_id.present? + "room" + else + "storage" + end + type = params[:assignment_type] || current_type + %> - - + +
+ <%= link_to item.new_record? ? new_item_path(assignment_type: "storage") : edit_item_path(item, assignment_type: "storage"), + class: "py-2 px-3 text-xs font-medium rounded-md text-center transition flex items-center justify-center gap-1 #{type == 'storage' ? 'bg-white text-blue-600 shadow-sm font-semibold' : 'text-gray-600 hover:text-gray-900'}" do %> + 📦 Lager + <% end %> + + <%= link_to item.new_record? ? new_item_path(assignment_type: "user") : edit_item_path(item, assignment_type: "user"), + class: "py-2 px-3 text-xs font-medium rounded-md text-center transition flex items-center justify-center gap-1 #{type == 'user' ? 'bg-white text-blue-600 shadow-sm font-semibold' : 'text-gray-600 hover:text-gray-900'}" do %> + 👤 Mitarbeiter + <% end %> + + <%= link_to item.new_record? ? new_item_path(assignment_type: "room") : edit_item_path(item, assignment_type: "room"), + class: "py-2 px-3 text-xs font-medium rounded-md text-center transition flex items-center justify-center gap-1 #{type == 'room' ? 'bg-white text-blue-600 shadow-sm font-semibold' : 'text-gray-600 hover:text-gray-900'}" do %> + 📍 Raum + <% end %> +
- - + +
+ <% if type == "user" %> + + + + +
+ + + + + <% User.all.each do |user| %> + + + <% end %> + +
+ + <% elsif type == "room" %> + + + + +
+ + + + + <% Room.all.each do |room| %> + + <% end %> + +
+ + <% else %> + + + +
+ Das Gerät wird nach dem Speichern als „Verfügbar im Hauptlager“ eingebucht. +
+ <% end %> +
+ <% end %>
+
<%= form.label :notes, "Zusätzliche Notizen / Details", class: "block text-sm font-medium mb-1.5 text-gray-700" %> - <%= form.text_area :notes, rows: 3, class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50" %> + <%= form.text_area :notes, rows: 3, class: "py-2 px-3 block w-full border border-gray-300 rounded-lg text-sm bg-gray-50/50 focus:border-blue-500 focus:ring-blue-500" %>

@@ -131,7 +174,7 @@
<%= link_to "Abbrechen", items_path, class: "py-2.5 px-4 inline-flex items-center text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition shadow-sm" %> - <%= form.submit "Artikel speichern", class: "py-2.5 px-4 inline-flex items-center text-sm font-semibold rounded-lg bg-blue-600 text-white hover:bg-blue-700 shadow-sm transition cursor-pointer" %> + <%= form.submit item.new_record? ? "Artikel registrieren" : "Änderungen speichern", class: "py-2.5 px-4 inline-flex items-center text-sm font-semibold rounded-lg bg-blue-600 text-white hover:bg-blue-700 shadow-sm transition cursor-pointer" %>
<% end %> diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb index 008cb24..4bc3822 100644 --- a/app/views/items/show.html.erb +++ b/app/views/items/show.html.erb @@ -31,90 +31,90 @@
<% end %> -
+
- -
+
- -
+ +
-
-
- +
+
+

<%= @item.name %>

+ <%= @item.category.name %> -

<%= @item.name %>


+
-
-

Artikelnummer (SKU)

-

<%= @item.sku %>

+
+

Artikelnummer (SKU)

+

<%= @item.sku %>

+
+
+

Seriennummer (SN)

+

<%= @item.serial_number %>

-

Seriennummer (Hersteller)

-

<%= @item.serial_number %>

-
-
-

Anschaffungspreis

-

+

Anschaffungspreis

+

<%= number_to_currency(@item.price, unit: "€", separator: ",", delimiter: ".", format: "%n %u") %>

-

Registriert am

-

<%= l(@item.created_at, format: :short) %>

+

Erfasst am

+

<%= l(@item.created_at, format: "%d.%m.%Y") %>

<% if @item.notes.present? %>
-

Beschreibung / Notizen

-

<%= @item.notes %>

+

Beschreibung / Notizen

+

<%= @item.notes %>

<% end %>
-
-

Aktueller Status & Aufenthaltsort

+
+

Aktueller Status & Aufenthaltsort

<% if @item.user.present? %>
-
+
-
-

In Benutzung (Mitarbeiter)

-

<%= @item.user.name %>

-

<%= @item.user.department&.name || "Keine Abteilung hinterlegt" %>

+
+

In Benutzung (Mitarbeiter)

+

<%= @item.user.name %>

+

<%= @item.user.department&.name || "Keine Abteilung" %>

<% elsif @item.room.present? %>
-
+
-
-

Fest verbaut / Zugewiesener Raum

-

<%= @item.room.name %>

-

<%= @item.room.building %> • <%= @item.room.floor %>

+
+

Zugewiesener Raum / Standort

+

<%= @item.room.name %>

+

<%= @item.room.building %> • <%= @item.room.floor %>

<% else %>
-
+
-

Verfügbar

-

Im Hauptlager

-

Bereit zur Zuweisung an Mitarbeiter oder Räume.

+

Status

+

Verfügbar im Hauptlager

+

Bereit zur Ausgabe oder Zuweisung.

<% end %> @@ -122,26 +122,23 @@
- -
-
-

Interne Kennzeichnung

- - -
+
+

Interne Kennzeichnung

+
+
<%= @item.generate_qr_code %>
- -
-

Inventarnummer

-

#<%= @item.sticker_id %>

-
- -

Vorgedrucktes Etikett vom A4-Bogen. Scan im System führt direkt hierher.

-
+
+

Inventarnummer

+ + #<%= @item.sticker_id %> + +
+

Vorgedrucktes Etikett vom A4-Bogen. Scan im System führt direkt hierher.

+
@@ -237,5 +234,3 @@ <% end %>
- -