From b1a258f36d64536211d2c79065fe50f98808fa2b Mon Sep 17 00:00:00 2001 From: Daniel Stock Date: Fri, 21 Nov 2025 13:23:13 +0100 Subject: [PATCH] Add Notes model, controller and database table. Implement it in Customer Dialog, #6 --- app/Http/Controllers/CustomerController.php | 2 + app/Http/Controllers/NoteController.php | 114 ++++++++++++ app/Models/Customer.php | 9 +- app/Models/Note.php | 29 +++ database/factories/NoteFactory.php | 23 +++ ...25_09_17_000000_create_customers_table.php | 1 - .../2025_11_21_081606_create_notes_table.php | 36 ++++ database/seeders/DatabaseSeeder.php | 12 +- resources/js/components/CustomerDialog.vue | 172 ++++++++++++++---- resources/js/types/index.d.ts | 29 ++- routes/api.php | 16 +- 11 files changed, 396 insertions(+), 47 deletions(-) create mode 100644 app/Http/Controllers/NoteController.php create mode 100644 app/Models/Note.php create mode 100644 database/factories/NoteFactory.php create mode 100644 database/migrations/2025_11_21_081606_create_notes_table.php diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index 5757770..a91b9c7 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -4,7 +4,9 @@ use Inertia\Inertia; use App\Models\Customer; +use App\Models\Note; use App\Support\ApiDataTransformer; +use Illuminate\Http\Request; class CustomerController extends Controller { diff --git a/app/Http/Controllers/NoteController.php b/app/Http/Controllers/NoteController.php new file mode 100644 index 0000000..fcf5a6d --- /dev/null +++ b/app/Http/Controllers/NoteController.php @@ -0,0 +1,114 @@ +route()->parameter('modelType'); + if (!$modelType) { + $modelType = $request->route()->getAction('modelType'); + } + $model = app("App\\Models\\" . ucfirst($modelType))::findOrFail($modelId); + + // Lade alle Notizen des Modells mit der Benutzerbeziehung + $notes = $model->notes()->with('user')->orderBy('created_at', 'desc')->get(); + + // Transformiere die Daten in camelCase + $notesArray = $notes->map(function ($note) { + return ApiDataTransformer::snakeToCamel($note->toArray()); + }); + + return response()->json($notesArray); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request, $modelId) + { + $modelType = $request->route()->parameter('modelType'); + if (!$modelType) { + $modelType = $request->route()->getAction('modelType'); + } + + $validatedData = $request->validate([ + 'text' => 'required|string', + 'userId' => 'required|integer' + ]); + + // Convert camelCase to snake_case + $snakeCaseData = ApiDataTransformer::camelToSnake($validatedData); + + $model = app("App\\Models\\" . ucfirst($modelType))::findOrFail($modelId); + $note = new Note($validatedData); + $note->user_id = $validatedData['userId']; + $model->notes()->save($note); + + return response()->json($this->single($note->id), 201); + } + + public static function single($id) + { + $note = Note::with('user')->findOrFail($id); + return ApiDataTransformer::snakeToCamel($note->toArray()); + } + + + /** + * Display the specified resource. + */ + public function show(Note $note) + { + // + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Note $note) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Note $note) + { + // + } + + /** + * Remove the specified resource from storage. + */ + public function delete(int $id) + { + DB::beginTransaction(); + + try { + $note = note::findOrFail($id); + $note->delete(); + DB::commit(); + + return response()->json([ + 'message' => 'Notiz gelöscht' + ], 200); + } catch (\Exception $e) { + DB::rollBack(); + return response()->json([ + 'message' => 'Notiz konnte nicht gelöscht werden', + 'error' => $e->getMessage() + ], 500); + } + } +} diff --git a/app/Models/Customer.php b/app/Models/Customer.php index c743f00..38f4ff8 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -27,7 +27,6 @@ class Customer extends Model 'billing_address', 'payment_terms_id', 'status', - 'notes', 'logo', ]; @@ -96,4 +95,12 @@ public function paymentTerms() { return $this->belongsTo(PaymentTerms::class); } + + /** + * Get the notes for the customer + */ + public function notes() + { + return $this->morphMany(Note::class, 'notable'); + } } diff --git a/app/Models/Note.php b/app/Models/Note.php new file mode 100644 index 0000000..6ccac40 --- /dev/null +++ b/app/Models/Note.php @@ -0,0 +1,29 @@ + */ + use HasFactory; + + protected $fillable = [ + 'user_id', + 'text', + 'notable_id', + 'notable_type' + ]; + + public function user() + { + return $this->belongsTo(User::class); + } + + public function notable() + { + return $this->morphTo(); + } +} diff --git a/database/factories/NoteFactory.php b/database/factories/NoteFactory.php new file mode 100644 index 0000000..e91f909 --- /dev/null +++ b/database/factories/NoteFactory.php @@ -0,0 +1,23 @@ + + */ +class NoteFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'text' => $this->faker->sentences(2, true), + ]; + } +} diff --git a/database/migrations/2025_09_17_000000_create_customers_table.php b/database/migrations/2025_09_17_000000_create_customers_table.php index 84b18c4..0cbb299 100644 --- a/database/migrations/2025_09_17_000000_create_customers_table.php +++ b/database/migrations/2025_09_17_000000_create_customers_table.php @@ -22,7 +22,6 @@ public function up() $table->json('billing_address')->nullable(); $table->foreignId('payment_terms_id')->constrained()->default(3); // Standard-Zahlungsziel: 14 Tage $table->enum('status', ['active', 'inactive', 'prospect'])->default('active'); - $table->text('notes')->nullable(); $table->string('logo')->nullable(); $table->timestamps(); }); diff --git a/database/migrations/2025_11_21_081606_create_notes_table.php b/database/migrations/2025_11_21_081606_create_notes_table.php new file mode 100644 index 0000000..e363873 --- /dev/null +++ b/database/migrations/2025_11_21_081606_create_notes_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('user_id')->constrained()->nullOnDelete(); + $table->text('text'); + + // Polymorphische Beziehung + $table->unsignedBigInteger('notable_id'); + $table->string('notable_type'); + + $table->timestamps(); + + $table->index(['user_id', 'notable_id', 'notable_type']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('notes'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index f808f38..e3d86cb 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -6,6 +6,7 @@ use App\Models\User; use App\Models\Customer; use App\Models\Contact; +use App\Models\Note; use App\Models\Invoice; use App\Models\LineItem; @@ -21,14 +22,14 @@ public function run(): void SettingsTableSeeder::class ]); - User::factory()->create([ + $user = User::factory()->create([ 'name' => 'Daniel Stock', 'email' => 'daniel.stock@tooloop.de', 'password' => bcrypt('6T0az2JGO5oA'), ]); // Create customers with contacts - Customer::factory(20)->create()->each(function ($customer) { + Customer::factory(20)->create()->each(function ($customer) use ($user) { // Create a primary contact for each customer Contact::factory()->create([ 'customer_id' => $customer->id, @@ -39,6 +40,13 @@ public function run(): void Contact::factory(rand(1, 3))->create([ 'customer_id' => $customer->id, ]); + + + // Create some notes for each customer + Note::factory(rand(0, 5))->create([ + 'user_id' => $user->id, + 'customer_id' => $customer->id, + ]); }); // Create invoices diff --git a/resources/js/components/CustomerDialog.vue b/resources/js/components/CustomerDialog.vue index cf85844..c96d97e 100644 --- a/resources/js/components/CustomerDialog.vue +++ b/resources/js/components/CustomerDialog.vue @@ -1,41 +1,44 @@ @@ -361,7 +454,7 @@ const handleLogoUpload = (event: Event) => {