From b7f0c35378d84f2999eda3fcf3edf89422030c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20B=C3=B6hm?= Date: Thu, 21 May 2026 15:36:23 +0200 Subject: [PATCH] Added some models User Item Department Categorie AssignmentLog Room --- app/models/assignment_log.rb | 7 ++ app/models/assignment_log2.rb | 5 ++ app/models/category.rb | 2 + app/models/department.rb | 6 ++ app/models/item.rb | 36 ++++++++++ app/models/room.rb | 11 +++ app/models/user.rb | 21 +++++- .../20260521120102_create_categories.rb | 10 +++ .../20260521121137_create_departments.rb | 10 +++ db/migrate/20260521124041_create_items.rb | 19 ++++++ .../20260521124606_create_assignment_logs.rb | 13 ++++ .../20260521124755_add_details_to_users.rb | 9 +++ db/migrate/20260521125254_create_rooms.rb | 11 +++ db/schema.rb | 67 ++++++++++++++++++- db/seeds.rb | 38 +++++++++++ test/fixtures/assignment_log2s.yml | 15 +++++ test/fixtures/assignment_logs.yml | 15 +++++ test/fixtures/categories.yml | 9 +++ test/fixtures/departments.yml | 9 +++ test/fixtures/items.yml | 23 +++++++ test/fixtures/rooms.yml | 11 +++ test/models/assignment_log2_test.rb | 7 ++ test/models/assignment_log_test.rb | 7 ++ test/models/category_test.rb | 7 ++ test/models/department_test.rb | 7 ++ test/models/item_test.rb | 7 ++ test/models/room_test.rb | 7 ++ 27 files changed, 387 insertions(+), 2 deletions(-) create mode 100644 app/models/assignment_log.rb create mode 100644 app/models/assignment_log2.rb create mode 100644 app/models/category.rb create mode 100644 app/models/department.rb create mode 100644 app/models/item.rb create mode 100644 app/models/room.rb create mode 100644 db/migrate/20260521120102_create_categories.rb create mode 100644 db/migrate/20260521121137_create_departments.rb create mode 100644 db/migrate/20260521124041_create_items.rb create mode 100644 db/migrate/20260521124606_create_assignment_logs.rb create mode 100644 db/migrate/20260521124755_add_details_to_users.rb create mode 100644 db/migrate/20260521125254_create_rooms.rb create mode 100644 test/fixtures/assignment_log2s.yml create mode 100644 test/fixtures/assignment_logs.yml create mode 100644 test/fixtures/categories.yml create mode 100644 test/fixtures/departments.yml create mode 100644 test/fixtures/items.yml create mode 100644 test/fixtures/rooms.yml create mode 100644 test/models/assignment_log2_test.rb create mode 100644 test/models/assignment_log_test.rb create mode 100644 test/models/category_test.rb create mode 100644 test/models/department_test.rb create mode 100644 test/models/item_test.rb create mode 100644 test/models/room_test.rb diff --git a/app/models/assignment_log.rb b/app/models/assignment_log.rb new file mode 100644 index 0000000..f0ccd39 --- /dev/null +++ b/app/models/assignment_log.rb @@ -0,0 +1,7 @@ +class AssignmentLog < ApplicationRecord + belongs_to :item + belongs_to :user, optional: true + belongs_to :room, optional: true + + validates :assigned_at, presence: true +end diff --git a/app/models/assignment_log2.rb b/app/models/assignment_log2.rb new file mode 100644 index 0000000..98ea43b --- /dev/null +++ b/app/models/assignment_log2.rb @@ -0,0 +1,5 @@ +class AssignmentLog2 < ApplicationRecord + belongs_to :item + belongs_to :user + belongs_to :room +end diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 0000000..54cb6ae --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,2 @@ +class Category < ApplicationRecord +end diff --git a/app/models/department.rb b/app/models/department.rb new file mode 100644 index 0000000..0842e9b --- /dev/null +++ b/app/models/department.rb @@ -0,0 +1,6 @@ +class Department < ApplicationRecord + has_many :users, dependent: :nullify + has_many :items, through: :users + + validates :name, presence: true, uniqueness: true +end diff --git a/app/models/item.rb b/app/models/item.rb new file mode 100644 index 0000000..ccbeca3 --- /dev/null +++ b/app/models/item.rb @@ -0,0 +1,36 @@ +class Item < ApplicationRecord + 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 + has_many :assignment_logs, dependent: :destroy + + validates :name, :sku, presence: true + validates :sticker_id, :serial_number, presence: true, uniqueness: true + + # Validierung: Darf nicht gleichzeitig einem User UND einem Raum gehören + validate :either_user_or_room + + # Überwacht Besitzer- oder Raumwechsel für die Historie + before_save :track_assignment_changes, if: -> { will_save_change_to_user_id? || will_save_change_to_room_id? } + + private + + def either_user_or_room + if user_id.present? && room_id.present? + errors.add(:base, "Ein Artikel kann nicht gleichzeitig einem Benutzer und einem Raum zugewiesen sein.") + end + 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 + + # 2. Neues Log-Buch öffnen für den neuen Inhaber oder den neuen Raum + if user_id.present? || room_id.present? + assignment_logs.build(user_id: user_id, room_id: room_id, assigned_at: Time.current) + end + end +end diff --git a/app/models/room.rb b/app/models/room.rb new file mode 100644 index 0000000..0b3edcf --- /dev/null +++ b/app/models/room.rb @@ -0,0 +1,11 @@ +class Room < ApplicationRecord + has_many :items, dependent: :nullify + has_many :assignment_logs, dependent: :destroy + + validates :name, presence: true, uniqueness: true + + # Für das Raum-Auswahlfeld im Formular + def name_with_building + building.present? ? "#{name} (Gebäude #{building})" : name + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 805275e..16d3a0f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,13 +10,18 @@ class User < ApplicationRecord password_salt.last(10) end - has_many :sessions, dependent: :destroy has_many :events, dependent: :destroy validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP } validates :password, allow_nil: true, length: { minimum: @@min_length_password } + belongs_to :department, optional: true + has_many :items, dependent: :nullify + has_many :assignment_logs, dependent: :destroy + + validates :first_name, :last_name, presence: true, on: :update + normalizes :email, with: -> { _1.strip.downcase } before_validation if: :email_changed?, on: :update do @@ -39,6 +44,20 @@ class User < ApplicationRecord events.create! action: "email_verified" end + # Gibt den vollen Namen zurück + def name + if first_name.present? && last_name.present? + "#{first_name} #{last_name}" + else + email + end + end + + # Für das Besitzer-Auswahlfeld im Formular + def name_with_department + department.present? ? "#{name} (#{department.name})" : name + end + def self.min_length_password @@min_length_password end diff --git a/db/migrate/20260521120102_create_categories.rb b/db/migrate/20260521120102_create_categories.rb new file mode 100644 index 0000000..8b66c29 --- /dev/null +++ b/db/migrate/20260521120102_create_categories.rb @@ -0,0 +1,10 @@ +class CreateCategories < ActiveRecord::Migration[8.1] + def change + create_table :categories do |t| + t.string :name + t.text :description + + t.timestamps + end + end +end diff --git a/db/migrate/20260521121137_create_departments.rb b/db/migrate/20260521121137_create_departments.rb new file mode 100644 index 0000000..20894ae --- /dev/null +++ b/db/migrate/20260521121137_create_departments.rb @@ -0,0 +1,10 @@ +class CreateDepartments < ActiveRecord::Migration[8.1] + def change + create_table :departments do |t| + t.string :name + t.string :code + + t.timestamps + end + end +end diff --git a/db/migrate/20260521124041_create_items.rb b/db/migrate/20260521124041_create_items.rb new file mode 100644 index 0000000..9e7d3a2 --- /dev/null +++ b/db/migrate/20260521124041_create_items.rb @@ -0,0 +1,19 @@ +class CreateItems < ActiveRecord::Migration[8.1] + def change + create_table :items do |t| + t.string :name + t.string :sku + t.string :sticker_id + t.string :serial_number + t.decimal :price, precision: 8, scale: 2 + t.text :notes + t.references :category, foreign_key: true + t.references :user, foreign_key: true + t.references :room, foreign_key: true + + t.timestamps + end + add_index :items, :sticker_id, unique: true + add_index :items, :serial_number, unique: true + end +end diff --git a/db/migrate/20260521124606_create_assignment_logs.rb b/db/migrate/20260521124606_create_assignment_logs.rb new file mode 100644 index 0000000..0a0912e --- /dev/null +++ b/db/migrate/20260521124606_create_assignment_logs.rb @@ -0,0 +1,13 @@ +class CreateAssignmentLogs < ActiveRecord::Migration[8.1] + def change + create_table :assignment_logs do |t| + t.references :item, foreign_key: true + t.references :user, foreign_key: true + t.references :room, foreign_key: true + t.datetime :assigned_at + t.datetime :returned_at + + t.timestamps + end + end +end diff --git a/db/migrate/20260521124755_add_details_to_users.rb b/db/migrate/20260521124755_add_details_to_users.rb new file mode 100644 index 0000000..a6695c8 --- /dev/null +++ b/db/migrate/20260521124755_add_details_to_users.rb @@ -0,0 +1,9 @@ +class AddDetailsToUsers < ActiveRecord::Migration[7.1] + def change + add_column :users, :first_name, :string + add_column :users, :last_name, :string + + # WICHTIG: null: true erlaubt es, dass alte User erst einmal ohne Abteilung migriert werden + add_reference :users, :department, null: true, foreign_key: true + end +end diff --git a/db/migrate/20260521125254_create_rooms.rb b/db/migrate/20260521125254_create_rooms.rb new file mode 100644 index 0000000..bd30695 --- /dev/null +++ b/db/migrate/20260521125254_create_rooms.rb @@ -0,0 +1,11 @@ +class CreateRooms < ActiveRecord::Migration[8.1] + def change + create_table :rooms do |t| + t.string :name + t.string :building + t.string :floor + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 2e829d3..0e12cd1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,34 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_05_20_205436) do +ActiveRecord::Schema[8.1].define(version: 2026_05_21_125254) do + create_table "assignment_logs", force: :cascade do |t| + t.datetime "assigned_at" + t.datetime "created_at", null: false + t.integer "item_id" + t.datetime "returned_at" + t.integer "room_id" + t.datetime "updated_at", null: false + t.integer "user_id" + t.index ["item_id"], name: "index_assignment_logs_on_item_id" + t.index ["room_id"], name: "index_assignment_logs_on_room_id" + t.index ["user_id"], name: "index_assignment_logs_on_user_id" + end + + create_table "categories", force: :cascade do |t| + t.datetime "created_at", null: false + t.text "description" + t.string "name" + t.datetime "updated_at", null: false + end + + create_table "departments", force: :cascade do |t| + t.string "code" + t.datetime "created_at", null: false + t.string "name" + t.datetime "updated_at", null: false + end + create_table "events", force: :cascade do |t| t.string "action", null: false t.datetime "created_at", null: false @@ -21,6 +48,33 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_20_205436) do t.index ["user_id"], name: "index_events_on_user_id" end + create_table "items", force: :cascade do |t| + t.integer "category_id" + t.datetime "created_at", null: false + t.string "name" + t.text "notes" + t.decimal "price", precision: 8, scale: 2 + t.integer "room_id" + t.string "serial_number" + t.string "sku" + t.string "sticker_id" + t.datetime "updated_at", null: false + t.integer "user_id" + t.index ["category_id"], name: "index_items_on_category_id" + t.index ["room_id"], name: "index_items_on_room_id" + t.index ["serial_number"], name: "index_items_on_serial_number", unique: true + t.index ["sticker_id"], name: "index_items_on_sticker_id", unique: true + t.index ["user_id"], name: "index_items_on_user_id" + end + + create_table "rooms", force: :cascade do |t| + t.string "building" + t.datetime "created_at", null: false + t.string "floor" + t.string "name" + t.datetime "updated_at", null: false + end + create_table "sessions", force: :cascade do |t| t.datetime "created_at", null: false t.string "ip_address" @@ -32,13 +86,24 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_20_205436) do create_table "users", force: :cascade do |t| t.datetime "created_at", null: false + t.integer "department_id" t.string "email", null: false + t.string "first_name" + t.string "last_name" t.string "password_digest", null: false t.datetime "updated_at", null: false t.boolean "verified", default: false, null: false + t.index ["department_id"], name: "index_users_on_department_id" t.index ["email"], name: "index_users_on_email", unique: true end + add_foreign_key "assignment_logs", "items" + add_foreign_key "assignment_logs", "rooms" + add_foreign_key "assignment_logs", "users" add_foreign_key "events", "users" + add_foreign_key "items", "categories" + add_foreign_key "items", "rooms" + add_foreign_key "items", "users" add_foreign_key "sessions", "users" + add_foreign_key "users", "departments" end diff --git a/db/seeds.rb b/db/seeds.rb index 4fbd6ed..76ec1a7 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,3 +7,41 @@ # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| # MovieGenre.find_or_create_by!(name: genre_name) # end +puts "Bereinige Datenbank..." +AssignmentLog.destroy_all +Item.destroy_all +Room.destroy_all +User.destroy_all +Department.destroy_all +Category.destroy_all + +puts "Erstelle Abteilungen..." +dept_it = Department.create!(name: "IT & Infrastruktur", code: "IT") +dept_mkt = Department.create!(name: "Marketing", code: "MKT") + +puts "Erstelle Benutzer..." +u1 = User.create!(first_name: "Max", last_name: "Mustermann", email: "max@firma.de", password: "password123123", department: dept_it) +u2 = User.create!(first_name: "Erika", last_name: "Mustermann", email: "erika@firma.de", password: "password123123", department: dept_mkt) + +puts "Erstelle Kategorien..." +cat_hardware = Category.create!(name: "Hardware", description: "Laptops und Monitore") +cat_furniture = Category.create!(name: "Möbel", description: "Tische und Stühle") + +puts "Erstelle Räume..." +room_101 = Room.create!(name: "Raum 101", building: "Hauptgebäude", floor: "1. OG") +room_lab = Room.create!(name: "Technik-Labor", building: "Werkstatt", floor: "EG") + +puts "Erstelle Artikel..." +# Artikel fest an User vergeben +item_laptop = Item.create!( + name: "MacBook Pro 16\"", sku: "MBP16-M3", sticker_id: "10001", serial_number: "C02F1234XYZ", + price: 2500.00, notes: "Entwickler-Laptop", category: cat_hardware, user: u1 +) + +# Artikel fest an einen Raum vergeben +item_monitor = Item.create!( + name: "Dell 27\" Monitor", sku: "DELL-U27", sticker_id: "10002", serial_number: "CN-0708XX", + price: 450.00, notes: "Fest verbaut an der Wand", category: cat_hardware, room: room_101 +) + +puts "🎉 Datenbank erfolgreich aufgesetzt!" diff --git a/test/fixtures/assignment_log2s.yml b/test/fixtures/assignment_log2s.yml new file mode 100644 index 0000000..c903439 --- /dev/null +++ b/test/fixtures/assignment_log2s.yml @@ -0,0 +1,15 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + item: one + user: one + room: one + assigned_at: 2026-05-21 15:29:43 + returned_at: 2026-05-21 15:29:43 + +two: + item: two + user: two + room: two + assigned_at: 2026-05-21 15:29:43 + returned_at: 2026-05-21 15:29:43 diff --git a/test/fixtures/assignment_logs.yml b/test/fixtures/assignment_logs.yml new file mode 100644 index 0000000..cee1c0b --- /dev/null +++ b/test/fixtures/assignment_logs.yml @@ -0,0 +1,15 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + item: one + user: one + room: one + assigned_at: 2026-05-21 14:46:06 + returned_at: 2026-05-21 14:46:06 + +two: + item: two + user: two + room: two + assigned_at: 2026-05-21 14:46:06 + returned_at: 2026-05-21 14:46:06 diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml new file mode 100644 index 0000000..382f6d8 --- /dev/null +++ b/test/fixtures/categories.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + description: MyText + +two: + name: MyString + description: MyText diff --git a/test/fixtures/departments.yml b/test/fixtures/departments.yml new file mode 100644 index 0000000..ee775db --- /dev/null +++ b/test/fixtures/departments.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + code: MyString + +two: + name: MyString + code: MyString diff --git a/test/fixtures/items.yml b/test/fixtures/items.yml new file mode 100644 index 0000000..6d8fee6 --- /dev/null +++ b/test/fixtures/items.yml @@ -0,0 +1,23 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + serial_number: MyString + sku: MyString + sticker_id: MyString + price: 9.99 + notes: MyText + category: one + user: one + room: one + +two: + name: MyString + serial_number: MyString + sku: MyString + sticker_id: MyString + price: 9.99 + notes: MyText + category: two + user: two + room: two diff --git a/test/fixtures/rooms.yml b/test/fixtures/rooms.yml new file mode 100644 index 0000000..6f964a1 --- /dev/null +++ b/test/fixtures/rooms.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + building: MyString + floor: MyString + +two: + name: MyString + building: MyString + floor: MyString diff --git a/test/models/assignment_log2_test.rb b/test/models/assignment_log2_test.rb new file mode 100644 index 0000000..88cb875 --- /dev/null +++ b/test/models/assignment_log2_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AssignmentLog2Test < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/assignment_log_test.rb b/test/models/assignment_log_test.rb new file mode 100644 index 0000000..a6ef330 --- /dev/null +++ b/test/models/assignment_log_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AssignmentLogTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/category_test.rb b/test/models/category_test.rb new file mode 100644 index 0000000..869357c --- /dev/null +++ b/test/models/category_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class CategoryTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/department_test.rb b/test/models/department_test.rb new file mode 100644 index 0000000..19ff1b3 --- /dev/null +++ b/test/models/department_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class DepartmentTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/item_test.rb b/test/models/item_test.rb new file mode 100644 index 0000000..4bd69ff --- /dev/null +++ b/test/models/item_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class ItemTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/room_test.rb b/test/models/room_test.rb new file mode 100644 index 0000000..de6eca5 --- /dev/null +++ b/test/models/room_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class RoomTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end