Skip to content

Weitere Seeder#560

Merged
dspangenberg merged 5 commits into
mainfrom
develop
Jun 30, 2026
Merged

Weitere Seeder#560
dspangenberg merged 5 commits into
mainfrom
develop

Conversation

@dspangenberg

@dspangenberg dspangenberg commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Neue Funktionen
    • Standarddaten für Zahlungsfristen, Dokumenttypen sowie Telefon-, Projekt- und Zeitkategorien ergänzt.
  • Benutzeroberfläche
    • Buchhaltungsnavigation wird nur angezeigt, wenn die Aktivierung in den Einstellungen aktiv ist.
  • Erweiterungen
    • Zusätzliche Eingabefelder für Adresskategorien sowie Steuer- und Steuersatzdaten.
  • Bug Fixes
    • PDF-Erstellung ist robuster bei fehlenden Medien/Anhängen und verarbeitet fehlende Fälligkeitsdaten korrekt.
  • Daten
    • Print-Layouts aktualisiert; Regionsdaten entfernt.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@dspangenberg, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9e1bae6b-f150-4104-8707-e5c645f833d2

📥 Commits

Reviewing files that changed from the base of the PR and between 5353cdb and 797261d.

📒 Files selected for processing (4)
  • app/Http/Controllers/App/OfferController.php
  • database/seeders/PrintLayoutSeeder.php
  • database/seeders/TaxRateSeeder.php
  • resources/views/pdf/invoice/index.blade.php

Walkthrough

Das PR erweitert ein Accounting-Setting von der Migration bis zur Navigation, ergänzt mehrere Modelle und Seeder um neue Felder und Daten, führt JSON-basierte Seeder ein und macht PDF-Erzeugung sowie Invoice-Datumsformatierung robuster.

Changes

Accounting-Flag, Stammdaten und PDF-Pfade

Layer / File(s) Summary
Accounting-Setting und Navigation
database/migrations/tenant/..., app/Settings/GeneralSettings.php, app/Http/Middleware/HandleInertiaRequests.php, resources/js/Types/index.d.ts, resources/js/Components/AppSidebar.tsx
general.is_accouting_enabled wird als Setting angelegt, im Inertia-Auth-Props bereitgestellt, als Typ ergänzt und in der Sidebar zur bedingten Anzeige der Buchhaltungsnavigation verwendet.
Modell-Fillables für neue Felder
app/Models/AddressCategory.php, app/Models/Tax.php, app/Models/TaxRate.php
type, mehrere Tax-Felder sowie name und outturn_account_id werden massenweise zuweisbar gemacht.
Seed-Daten und Seed-Konfiguration
database/json/payment_deadlines.json, database/json/print_layouts.json, database/seeders/SettingsSeeder.php, database/seeders/TaxSeeder.php, database/seeders/TaxRateSeeder.php
Neue Zahlungsfristen-Daten werden ergänzt, ein Layout-css-Wert wird auf null gesetzt, und mehrere Seeder übernehmen neue Werte ohne Zeitstempel-Updates.
Neue JSON-basierte Seeder
database/seeders/DocumentTypeSeeder.php, database/seeders/PaymentDeadlineSeeder.php, database/seeders/PhoneCategorySeeder.php, database/seeders/ProjectCategorySeeder.php, database/seeders/TimeCategorySeeder.php, database/seeders/TenantSeeder.php
Fünf Seeder laden JSON von der json-Disk, schreiben Datensätze per firstOrCreate und werden in der Tenant-Seed-Reihenfolge ergänzt.
Bestehende Seeder auf firstOrCreate
database/seeders/AddressCategorySeeder.php, database/seeders/CountrySeeder.php, database/seeders/PrintLayoutSeeder.php, database/seeders/SalutationSeeder.php, database/seeders/TitleSeeder.php
Mehrere bestehende Seeder wechseln auf firstOrCreate und entfernen frühe Abbrüche sowie JSON-Zeitstempel aus den Schreibvorgängen.
PDF- und Invoice-Prüfungen
app/Services/PdfService.php, resources/views/pdf/invoice/index.blade.php
PdfService prüft Medien über Storage::disk(...)->exists(...); das Invoice-Template formatiert due_on null-sicher.

OfferData-Status

Layer / File(s) Summary
Nullable Offer-Status
app/Data/OfferData.php
Der OfferData-Konstruktor akzeptiert $status jetzt als nullable OfferStatusEnum.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • dspangenberg/opsc#41: Ändert app/Settings/GeneralSettings.php und steht damit direkt neben dem neuen Accounting-Setting.
  • dspangenberg/opsc#473: Betrifft app/Services/PdfService, dieselbe Stelle, an der jetzt die Storage-basierte Existenzprüfung ergänzt wird.
  • dspangenberg/opsc#559: Arbeitet ebenfalls an PDF-/Invoice-bezogenen Datenflüssen und ist dadurch nah an den neuen Zahlungsfristen- und PDF-Anpassungen.

Poem

🐇 Ich hüpf durchs Repo, flink und sacht,
und hab die Buchhaltung an Bord gebracht.
JSON im Korb, die Seeds sind bereit,
PDFs gucken jetzt doppelt auf Sicherheit.
Und fehlt mal das Datum im warmen Schein,
dann bleibt der Hase ganz ruhig und fein.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive Der Titel ist sehr allgemein und nennt die konkreten Änderungen im PR nicht. Präzisieren Sie den Titel, z. B. mit den wichtigsten neuen Seedern und den zugehörigen Modell- und UI-Anpassungen.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Services/PdfService.php`:
- Line 110: Der Guard in PdfService prüft mit $media->exists() nur auf
vorhandene Eloquent-Datensätze und verhindert nicht, dass contents() auf ein
verwaistes Medium ohne Datei zugreift. Ersetze die Prüfung im betroffenen
Medien-Zweig durch eine echte Dateiverfügbarkeitsprüfung vor dem
contents()-Aufruf und übernimm denselben Guard auch im Letterhead-Zweig, damit
beide Pfade nur mit tatsächlich vorhandenen Dateien fortfahren.

In `@database/json/payment_deadlines.json`:
- Line 10: The payment deadline seed data includes invoice_text values that are
currently ignored by the seeding flow. Update PaymentDeadlineSeeder to persist
invoice_text alongside the existing fields it writes, and ensure the
corresponding PaymentDeadline model contract/fillable attributes support it; if
invoice_text is not meant to be seeded, remove it from payment_deadlines.json
and keep the seeder aligned with the remaining fields.
- Around line 4-6: The payment_deadlines entry for the unique symbol "sofort" is
internally inconsistent because is_immediately is set while days still indicates
a 7-day deadline. Update this record in payment_deadlines.json so the
immediate-payment semantics and the day-based due date match, either by setting
days to 0 or by aligning the field meaning across the dataset, and make sure the
"sofort" definition remains consistent with the rest of the payment deadline
records.

In `@database/seeders/DocumentTypeSeeder.php`:
- Around line 22-31: The DocumentTypeSeeder updateOrCreate call is relying on
mass assignment for created_at and updated_at, so the JSON timestamp values are
being overridden by Eloquent defaults. Update the seeding flow in
DocumentTypeSeeder to set the timestamps explicitly on the DocumentType model
before saving, or adjust the model’s mass-assignable fields if that is intended.
Apply the same fix pattern to the other new seeders that use updateOrCreate with
timestamp fields.

In `@database/seeders/PaymentDeadlineSeeder.php`:
- Around line 16-18: The guard in PaymentDeadlineSeeder is a no-op because the
Early-Return is commented out, so the count() check does not stop the seeder
from running. Restore the intended early-exit behavior in the
PaymentDeadlineSeeder logic by making the existing PaymentDeadline::count()
check return immediately when records already exist, matching the pattern used
by the other seeders. Keep the fix localized to the seeding flow around the
PaymentDeadline model check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2403d244-f2f7-459a-a4cd-1deeb41e45c9

📥 Commits

Reviewing files that changed from the base of the PR and between b71609b and 38a58e5.

📒 Files selected for processing (14)
  • app/Models/AddressCategory.php
  • app/Models/Tax.php
  • app/Models/TaxRate.php
  • app/Services/PdfService.php
  • database/json/payment_deadlines.json
  • database/json/print_layouts.json
  • database/json/regions.json
  • database/seeders/DocumentTypeSeeder.php
  • database/seeders/PaymentDeadlineSeeder.php
  • database/seeders/PhoneCategorySeeder.php
  • database/seeders/ProjectCategorySeeder.php
  • database/seeders/TenantSeeder.php
  • database/seeders/TimeCategorySeeder.php
  • resources/views/pdf/invoice/index.blade.php
💤 Files with no reviewable changes (1)
  • database/json/regions.json

Comment thread app/Services/PdfService.php Outdated
Comment thread database/json/payment_deadlines.json
Comment thread database/json/payment_deadlines.json
Comment thread database/seeders/DocumentTypeSeeder.php Outdated
Comment thread database/seeders/PaymentDeadlineSeeder.php Outdated
@dspangenberg

Copy link
Copy Markdown
Owner Author

@coderabbitai bitte erneuten review durchführen

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

@dspangenberg klar — ich führe eine erneute vollständige Review für PR #560 durch.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
database/migrations/tenant/2026_06_30_184733_add_accounting_enabled.php (1)

7-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

down()-Methode für sauberes Rollback ergänzen.

Die Migration definiert nur up(). Ohne down() (das den Key per $this->migrator->delete(...) entfernt) lässt sich die Setting-Migration nicht zurückrollen.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/migrations/tenant/2026_06_30_184733_add_accounting_enabled.php`
around lines 7 - 11, The migration only defines up() in the
2026_06_30_184733_add_accounting_enabled class, so add a matching down() method
for rollback support. Implement down() to undo the
add('general.is_accouting_enabled', false) change by removing the same setting
key through the migrator delete operation, keeping the migration reversible.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@database/migrations/tenant/2026_06_30_184733_add_accounting_enabled.php`:
- Line 9: The migration is storing general.is_accouting_enabled as a boolean
even though GeneralSettings::$is_accouting_enabled is treated as a string and
the middleware checks for 'true'. Update the migration to persist the setting
consistently as the string 'false', or switch the setting and related middleware
logic to use a real boolean everywhere; use the add() call in the migration and
the GeneralSettings::$is_accouting_enabled / middleware comparison as the key
places to align.

In `@database/seeders/DocumentTypeSeeder.php`:
- Around line 16-29: Remove the early DocumentType::count() guard in
DocumentTypeSeeder so the seeder always processes the JSON payload and lets
updateOrCreate() handle idempotency. Keep the existing
Storage::disk('json')->json('document_types.json') load and the foreach loop
over $documentTypes, and ensure DocumentType::updateOrCreate continues to upsert
each entry by id so newly added JSON document types are seeded on subsequent
runs.

In `@database/seeders/PaymentDeadlineSeeder.php`:
- Around line 16-30: The early return in PaymentDeadlineSeeder::run prevents any
later changes from being applied once PaymentDeadline already has rows, even
though the loop uses updateOrCreate. Remove or narrow that guard so the seeder
can upsert entries from payment_deadlines.json for existing tenants, and keep
the logic centered on the PaymentDeadline::updateOrCreate call rather than
blocking it with PaymentDeadline::count().

In `@database/seeders/PhoneCategorySeeder.php`:
- Around line 16-27: The early return in PhoneCategorySeeder::run() is blocking
repeatable seeding because it skips the JSON-driven upserts whenever
PhoneCategory::count() is greater than zero. Remove that “table not empty” guard
so the loop over Storage::disk('json')->json('phone_categories.json') always
runs, and keep using PhoneCategory::updateOrCreate() keyed by id so existing
rows are updated and new defaults are added on every seed.

In `@database/seeders/ProjectCategorySeeder.php`:
- Around line 16-28: The early return in ProjectCategorySeeder::run prevents new
default categories from being synced once the table already has rows. Remove or
relax the ProjectCategory::count() guard so the seeder always reads
project_categories.json and runs the existing updateOrCreate loop, allowing new
JSON entries to be added for existing tenants while preserving the upsert
behavior.

In `@database/seeders/TimeCategorySeeder.php`:
- Around line 16-18: The early TimeCategorySeeder guard using
TimeCategory::count() stops the seeder from backfilling or correcting existing
tenants, even though the seeder already uses updateOrCreate(). Remove the global
count-based return in TimeCategorySeeder, or replace it with a per-seed-ID
existence check inside the seeding loop so new/updated JSON entries are still
applied to tables that already contain time_categories rows.

In `@resources/views/pdf/invoice/index.blade.php`:
- Around line 269-274: The invoice PDF text rendering in the Blade view still
replaces $dueDate with an empty string when due_on is null, producing an
incomplete sentence. Update the invoice_text handling in the invoice PDF
template so the placeholder is only substituted when due_on has a value,
otherwise omit that sentence/section entirely or choose a safe fallback before
calling Str::replace in the invoice rendering path.

---

Nitpick comments:
In `@database/migrations/tenant/2026_06_30_184733_add_accounting_enabled.php`:
- Around line 7-11: The migration only defines up() in the
2026_06_30_184733_add_accounting_enabled class, so add a matching down() method
for rollback support. Implement down() to undo the
add('general.is_accouting_enabled', false) change by removing the same setting
key through the migrator delete operation, keeping the migration reversible.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8a6f95da-f991-4eb4-9526-e02815c04a9b

📥 Commits

Reviewing files that changed from the base of the PR and between b71609b and b590220.

📒 Files selected for processing (23)
  • app/Http/Middleware/HandleInertiaRequests.php
  • app/Models/AddressCategory.php
  • app/Models/Tax.php
  • app/Models/TaxRate.php
  • app/Services/PdfService.php
  • app/Settings/GeneralSettings.php
  • database/json/payment_deadlines.json
  • database/json/print_layouts.json
  • database/json/regions.json
  • database/migrations/tenant/2026_06_30_184733_add_accounting_enabled.php
  • database/seeders/DocumentTypeSeeder.php
  • database/seeders/PaymentDeadlineSeeder.php
  • database/seeders/PhoneCategorySeeder.php
  • database/seeders/PrintLayoutSeeder.php
  • database/seeders/ProjectCategorySeeder.php
  • database/seeders/SettingsSeeder.php
  • database/seeders/TaxRateSeeder.php
  • database/seeders/TaxSeeder.php
  • database/seeders/TenantSeeder.php
  • database/seeders/TimeCategorySeeder.php
  • resources/js/Components/AppSidebar.tsx
  • resources/js/Types/index.d.ts
  • resources/views/pdf/invoice/index.blade.php
💤 Files with no reviewable changes (4)
  • database/seeders/TaxSeeder.php
  • database/seeders/PrintLayoutSeeder.php
  • database/json/regions.json
  • database/seeders/TaxRateSeeder.php

Comment thread database/migrations/tenant/2026_06_30_184733_add_accounting_enabled.php Outdated
Comment thread database/seeders/DocumentTypeSeeder.php Outdated
Comment on lines +16 to +30
if (PaymentDeadline::count() > 0) {
return;
}

$paymentDeadlines = Storage::disk('json')->json('payment_deadlines.json');
foreach ($paymentDeadlines as $value) {
PaymentDeadline::updateOrCreate([
'id' => $value['id'],
], [
'name' => $value['name'],
'days' => $value['days'],
'is_immediately' => $value['is_immediately'],
'is_default' => $value['is_default'],
'invoice_text' => $value['invoice_text'],
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Der Early-Return blockiert Updates für bestehende Tenants.

Mit diesem Guard werden neue oder geänderte payment_deadlines.json-Einträge nie übernommen, sobald die Tabelle bereits irgendeinen Datensatz enthält. Das widerspricht dem darunter verwendeten updateOrCreate() und macht den Seeder für Nachpflege faktisch unbrauchbar.

🐛 Vorgeschlagene Korrektur
     public function run(): void
     {
-        if (PaymentDeadline::count() > 0) {
-            return;
-        }
-
         $paymentDeadlines = Storage::disk('json')->json('payment_deadlines.json');
         foreach ($paymentDeadlines as $value) {
             PaymentDeadline::updateOrCreate([
                 'id' => $value['id'],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (PaymentDeadline::count() > 0) {
return;
}
$paymentDeadlines = Storage::disk('json')->json('payment_deadlines.json');
foreach ($paymentDeadlines as $value) {
PaymentDeadline::updateOrCreate([
'id' => $value['id'],
], [
'name' => $value['name'],
'days' => $value['days'],
'is_immediately' => $value['is_immediately'],
'is_default' => $value['is_default'],
'invoice_text' => $value['invoice_text'],
]);
$paymentDeadlines = Storage::disk('json')->json('payment_deadlines.json');
foreach ($paymentDeadlines as $value) {
PaymentDeadline::updateOrCreate([
'id' => $value['id'],
], [
'name' => $value['name'],
'days' => $value['days'],
'is_immediately' => $value['is_immediately'],
'is_default' => $value['is_default'],
'invoice_text' => $value['invoice_text'],
]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/seeders/PaymentDeadlineSeeder.php` around lines 16 - 30, The early
return in PaymentDeadlineSeeder::run prevents any later changes from being
applied once PaymentDeadline already has rows, even though the loop uses
updateOrCreate. Remove or narrow that guard so the seeder can upsert entries
from payment_deadlines.json for existing tenants, and keep the logic centered on
the PaymentDeadline::updateOrCreate call rather than blocking it with
PaymentDeadline::count().

Comment on lines +16 to +27
if (PhoneCategory::count() > 0) {
return;
}

$phoneCategories = Storage::disk('json')->json('phone_categories.json');
foreach ($phoneCategories as $value) {
PhoneCategory::updateOrCreate([
'id' => $value['id'],
], [
'name' => $value['name'],
'type' => $value['type'],
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Nicht auf „Tabelle ist leer“ cappen, wenn darunter per ID upsertet wird.

Sobald ein Tenant bereits irgendeine phone_categories-Zeile hat, springt der Seeder hier komplett ab. Dadurch werden spätere Ergänzungen der JSON-Defaults nie übernommen, obwohl updateOrCreate() genau für ein wiederholbares Seed gedacht ist.

🐛 Vorgeschlagene Korrektur
     public function run(): void
     {
-        if (PhoneCategory::count() > 0) {
-            return;
-        }
-
         $phoneCategories = Storage::disk('json')->json('phone_categories.json');
         foreach ($phoneCategories as $value) {
             PhoneCategory::updateOrCreate([
                 'id' => $value['id'],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (PhoneCategory::count() > 0) {
return;
}
$phoneCategories = Storage::disk('json')->json('phone_categories.json');
foreach ($phoneCategories as $value) {
PhoneCategory::updateOrCreate([
'id' => $value['id'],
], [
'name' => $value['name'],
'type' => $value['type'],
]);
$phoneCategories = Storage::disk('json')->json('phone_categories.json');
foreach ($phoneCategories as $value) {
PhoneCategory::updateOrCreate([
'id' => $value['id'],
], [
'name' => $value['name'],
'type' => $value['type'],
]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/seeders/PhoneCategorySeeder.php` around lines 16 - 27, The early
return in PhoneCategorySeeder::run() is blocking repeatable seeding because it
skips the JSON-driven upserts whenever PhoneCategory::count() is greater than
zero. Remove that “table not empty” guard so the loop over
Storage::disk('json')->json('phone_categories.json') always runs, and keep using
PhoneCategory::updateOrCreate() keyed by id so existing rows are updated and new
defaults are added on every seed.

Comment on lines +16 to +28
if (ProjectCategory::count() > 0) {
return;
}

$projectCategories = Storage::disk('json')->json('project_categories.json');
foreach ($projectCategories as $value) {
ProjectCategory::updateOrCreate([
'id' => $value['id'],
], [
'name' => $value['name'],
'color' => $value['color'],
'icon' => $value['icon'],
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Der Guard verhindert das Nachziehen neuer Standardkategorien.

Auch hier wird der eigentliche Upsert-Pfad nie erreicht, sobald die Tabelle nicht mehr leer ist. Damit kommen neu eingeführte JSON-Einträge bei bestehenden Tenants nicht an, obwohl der Seeder mit updateOrCreate() bereits sicher erneut laufen könnte.

🐛 Vorgeschlagene Korrektur
     public function run(): void
     {
-        if (ProjectCategory::count() > 0) {
-            return;
-        }
-
         $projectCategories = Storage::disk('json')->json('project_categories.json');
         foreach ($projectCategories as $value) {
             ProjectCategory::updateOrCreate([
                 'id' => $value['id'],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (ProjectCategory::count() > 0) {
return;
}
$projectCategories = Storage::disk('json')->json('project_categories.json');
foreach ($projectCategories as $value) {
ProjectCategory::updateOrCreate([
'id' => $value['id'],
], [
'name' => $value['name'],
'color' => $value['color'],
'icon' => $value['icon'],
]);
$projectCategories = Storage::disk('json')->json('project_categories.json');
foreach ($projectCategories as $value) {
ProjectCategory::updateOrCreate([
'id' => $value['id'],
], [
'name' => $value['name'],
'color' => $value['color'],
'icon' => $value['icon'],
]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/seeders/ProjectCategorySeeder.php` around lines 16 - 28, The early
return in ProjectCategorySeeder::run prevents new default categories from being
synced once the table already has rows. Remove or relax the
ProjectCategory::count() guard so the seeder always reads
project_categories.json and runs the existing updateOrCreate loop, allowing new
JSON entries to be added for existing tenants while preserving the upsert
behavior.

Comment thread database/seeders/TimeCategorySeeder.php Outdated
Comment on lines +16 to +18
if (TimeCategory::count() > 0) {
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Der count()-Guard verhindert Backfills und Korrekturen.

Mit dem frühen return läuft dieser Seeder nur auf komplett leeren Tabellen. Spätere JSON-Ergänzungen oder Korrekturen kommen damit nie bei Tenants an, die bereits irgendeine time_categories-Zeile haben, obwohl darunter updateOrCreate() verwendet wird. Den Guard bitte entfernen oder auf eine gezielte Prüfung pro Seed-ID umstellen.

Vorgeschlagene Korrektur
     public function run(): void
     {
-        if (TimeCategory::count() > 0) {
-            return;
-        }
-
         $timeCategories = Storage::disk('json')->json('time_categories.json');
         foreach ($timeCategories as $value) {
             TimeCategory::updateOrCreate([
                 'id' => $value['id'],
             ], [
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (TimeCategory::count() > 0) {
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/seeders/TimeCategorySeeder.php` around lines 16 - 18, The early
TimeCategorySeeder guard using TimeCategory::count() stops the seeder from
backfilling or correcting existing tenants, even though the seeder already uses
updateOrCreate(). Remove the global count-based return in TimeCategorySeeder, or
replace it with a per-seed-ID existence check inside the seeding loop so
new/updated JSON entries are still applied to tables that already contain
time_categories rows.

Comment thread resources/views/pdf/invoice/index.blade.php
@dspangenberg

Copy link
Copy Markdown
Owner Author

@coderabbitai bitte erneuten review durchführen

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

@dspangenberg klar — ich führe erneut eine vollständige Review für PR #560 durch.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
app/Services/PdfService.php (1)

43-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplikation der Disk-Existenzprüfung.

Das Muster Storage::disk($media->disk)->exists($media->getDiskPath()) taucht zweimal identisch auf. Eine kleine private Helper-Methode (z. B. mediaFileExists(?Media $media): bool) würde Duplikation vermeiden und zukünftige Änderungen an der Prüfung an einer Stelle halten.

♻️ Vorschlag
+    private function mediaFileExists(?Media $media): bool
+    {
+        return $media && Storage::disk($media->disk)->exists($media->getDiskPath());
+    }

Dann an beiden Stellen:

-            if ($media && Storage::disk($media->disk)->exists($media->getDiskPath())) {
+            if ($this->mediaFileExists($media)) {

Also applies to: 111-111

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Services/PdfService.php` at line 43, Duplicate disk-existence checks in
PdfService should be extracted into a shared private helper to avoid repeated
logic. Add a method such as mediaFileExists(?Media $media): bool in PdfService
that encapsulates the Storage::disk(...)->exists(...) check, then replace both
current inline uses with that helper so the media existence logic stays
centralized and easier to maintain.
app/Settings/GeneralSettings.php (1)

29-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tippfehler: is_accouting_enabled statt is_accounting_enabled.

Der Property-Name (und der entsprechende Migration-Key) enthält einen Tippfehler. Funktional ist das aktuell konsistent, da Migration und Middleware denselben Namen verwenden, aber der Fehler verfestigt sich dauerhaft im DB-Settings-Key. Da der Frontend-Prop in HandleInertiaRequests.php bereits korrekt is_accounting_enabled heißt, wäre es sinnvoll, den Tippfehler jetzt (vor dem Merge) zu korrigieren, statt ihn dauerhaft mitzuschleppen.

✏️ Vorschlag
-    public string $is_accouting_enabled;
+    public string $is_accounting_enabled;

Hinweis: erfordert auch Anpassung des Migration-Keys (general.is_accouting_enabledgeneral.is_accounting_enabled) und der Stelle in HandleInertiaRequests.php, die auf $settings?->is_accouting_enabled zugreift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Settings/GeneralSettings.php` around lines 29 - 30, The setting property
name in GeneralSettings is misspelled as is_accouting_enabled and should be
renamed to is_accounting_enabled to match the frontend prop and avoid locking in
the wrong DB key. Update the corresponding migration key from
general.is_accouting_enabled to general.is_accounting_enabled, and fix the
access in HandleInertiaRequests where it reads $settings?->is_accouting_enabled
so all references use the corrected symbol consistently.
database/seeders/TaxRateSeeder.php (1)

16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Variablennamen-Konvention abweichend.

$tax_rates ist snake_case, während alle übrigen neuen JSON-Seeder ($documentTypes, $paymentDeadlines, $phoneCategories, $projectCategories, $timeCategories) camelCase verwenden.

Based on coding guidelines: "Follow the application's existing code conventions, including naming, structure, and reuse of existing components."

♻️ Vorgeschlagene Korrektur
-        $tax_rates = Storage::disk('json')->json('tax_rates.json');
-        foreach ($tax_rates as $value) {
+        $taxRates = Storage::disk('json')->json('tax_rates.json');
+        foreach ($taxRates as $value) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/seeders/TaxRateSeeder.php` at line 16, The variable naming in
TaxRateSeeder is inconsistent with the other JSON seeders: rename $tax_rates to
camelCase to match the existing convention used by documentTypes,
paymentDeadlines, phoneCategories, projectCategories, and timeCategories. Update
the variable in the seeder logic so the naming style stays aligned with the
application’s existing conventions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Data/OfferData.php`:
- Line 70: The OfferData status field is now nullable, but the frontend types
and UI still assume a non-null value, which can break lookups like
offerStatusDirectory[offer.status].name and selectedKeys={[offer.status]}.
Regenerate the generated TypeScript definitions from OfferData so
resources/js/Types/generated.d.ts matches the backend, then update the affected
UI code paths that use offer.status to handle null safely, especially where
offerStatusDirectory and selectedKeys are used.

In `@database/seeders/PrintLayoutSeeder.php`:
- Around line 17-43: `PrintLayoutSeeder` and the related `Letterhead` seeding
now use `firstOrCreate`, so existing rows are no longer synchronized with JSON
fixes; update the seeding flow to explicitly apply changes to existing records
when needed. In `PrintLayoutSeeder`, adjust the `Letterhead` and `PrintLayout`
blocks so the seeded attributes from `letterheads.json` and `print_layouts.json`
can overwrite stale values for matching ids, or route this through a dedicated
sync/update path instead of relying only on `firstOrCreate`. Keep the same
lookup keys (`id`, `Letterhead`, `PrintLayout`) but ensure corrected fields like
`css` are propagated to already-seeded tenants.

In `@database/seeders/TaxRateSeeder.php`:
- Around line 16-25: The TaxRate seeder currently uses TaxRate::firstOrCreate
with only the default scoped lookup, so soft-deleted records with the same id
are missed and a duplicate primary key insert is attempted. Update the seeding
logic in TaxRateSeeder to account for SoftDeletes by checking for trashed
TaxRate records when matching on id, and restore/update an existing soft-deleted
record instead of inserting a new one; keep the fix centered around the
TaxRate::firstOrCreate flow.

---

Nitpick comments:
In `@app/Services/PdfService.php`:
- Line 43: Duplicate disk-existence checks in PdfService should be extracted
into a shared private helper to avoid repeated logic. Add a method such as
mediaFileExists(?Media $media): bool in PdfService that encapsulates the
Storage::disk(...)->exists(...) check, then replace both current inline uses
with that helper so the media existence logic stays centralized and easier to
maintain.

In `@app/Settings/GeneralSettings.php`:
- Around line 29-30: The setting property name in GeneralSettings is misspelled
as is_accouting_enabled and should be renamed to is_accounting_enabled to match
the frontend prop and avoid locking in the wrong DB key. Update the
corresponding migration key from general.is_accouting_enabled to
general.is_accounting_enabled, and fix the access in HandleInertiaRequests where
it reads $settings?->is_accouting_enabled so all references use the corrected
symbol consistently.

In `@database/seeders/TaxRateSeeder.php`:
- Line 16: The variable naming in TaxRateSeeder is inconsistent with the other
JSON seeders: rename $tax_rates to camelCase to match the existing convention
used by documentTypes, paymentDeadlines, phoneCategories, projectCategories, and
timeCategories. Update the variable in the seeder logic so the naming style
stays aligned with the application’s existing conventions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6686f0d9-3798-46f7-ac55-18162fc18d8a

📥 Commits

Reviewing files that changed from the base of the PR and between b71609b and 5353cdb.

📒 Files selected for processing (28)
  • app/Data/OfferData.php
  • app/Http/Middleware/HandleInertiaRequests.php
  • app/Models/AddressCategory.php
  • app/Models/Tax.php
  • app/Models/TaxRate.php
  • app/Services/PdfService.php
  • app/Settings/GeneralSettings.php
  • database/json/payment_deadlines.json
  • database/json/print_layouts.json
  • database/json/regions.json
  • database/migrations/tenant/2026_06_30_184733_add_accounting_enabled.php
  • database/seeders/AddressCategorySeeder.php
  • database/seeders/CountrySeeder.php
  • database/seeders/DocumentTypeSeeder.php
  • database/seeders/PaymentDeadlineSeeder.php
  • database/seeders/PhoneCategorySeeder.php
  • database/seeders/PrintLayoutSeeder.php
  • database/seeders/ProjectCategorySeeder.php
  • database/seeders/SalutationSeeder.php
  • database/seeders/SettingsSeeder.php
  • database/seeders/TaxRateSeeder.php
  • database/seeders/TaxSeeder.php
  • database/seeders/TenantSeeder.php
  • database/seeders/TimeCategorySeeder.php
  • database/seeders/TitleSeeder.php
  • resources/js/Components/AppSidebar.tsx
  • resources/js/Types/index.d.ts
  • resources/views/pdf/invoice/index.blade.php
💤 Files with no reviewable changes (1)
  • database/json/regions.json

Comment thread app/Data/OfferData.php Outdated
public readonly ?bool $is_template,
public readonly ?string $template_name,
public readonly OfferStatusEnum $status,
public readonly ?OfferStatusEnum $status,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Prüfen, ob generated.d.ts bereits als nullable aktualisiert wurde
rg -n "status" resources/js/Types/generated.d.ts | rg -i "OfferStatusEnum"

# Frontend-Stellen ohne Null-Check für offer.status finden
rg -nP 'offer\.status\b' resources/js -C2

Repository: dspangenberg/opsc

Length of output: 1463


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== OfferData.php ==\n'
sed -n '1,140p' app/Data/OfferData.php

printf '\n== OfferUpdateStatusRequest.php ==\n'
sed -n '1,120p' app/Http/Requests/OfferUpdateStatusRequest.php

printf '\n== OfferDetailsSide.tsx ==\n'
sed -n '1,120p' resources/js/Pages/App/Offer/OfferDetailsSide.tsx

printf '\n== OfferDetailsLayout.tsx ==\n'
sed -n '220,270p' resources/js/Pages/App/Offer/OfferDetailsLayout.tsx

Repository: dspangenberg/opsc

Length of output: 7894


status nicht nullable ins Frontend durchreichen.

app/Data/OfferData.php erlaubt jetzt ?OfferStatusEnum, aber resources/js/Types/generated.d.ts und die UI behandeln offer.status weiter als nicht-null. offerStatusDirectory[offer.status].name und selectedKeys={[offer.status]} können bei null zur Laufzeit brechen. TS-Typen neu generieren und die betroffenen Stellen mit Null-Handling absichern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Data/OfferData.php` at line 70, The OfferData status field is now
nullable, but the frontend types and UI still assume a non-null value, which can
break lookups like offerStatusDirectory[offer.status].name and
selectedKeys={[offer.status]}. Regenerate the generated TypeScript definitions
from OfferData so resources/js/Types/generated.d.ts matches the backend, then
update the affected UI code paths that use offer.status to handle null safely,
especially where offerStatusDirectory and selectedKeys are used.

Comment thread database/seeders/PrintLayoutSeeder.php Outdated
Comment thread database/seeders/TaxRateSeeder.php
@dspangenberg dspangenberg merged commit c3db369 into main Jun 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant