Release/4.5.0 - #324
Open
lukas-staab wants to merge 42 commits into
Open
Conversation
The settings-import migration still invoked the old settings:import-from-legacy-config name; update it to match the command namespace reorg. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the @alpinejs/sort plugin and its registration; sortable budget items now use Livewire's native wire:sort instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… and cloning Graduate the budget-plan feature from the dev-only feature branch to a full release (4.5.0): - Replace the draft/final BudgetPlanState enum with a spatie model-states workflow (Draft → Resolved → Approved → Published → Completed) and gate transitions through the policy. - Add a derived BudgetItemKind (group/budget/mount) and "mount" items that stand in for the income/expense side of another plan, with cycle-safe effective-value rollups and reachability checks. - Add Support\Budget helpers: TitleNumberer for auto Titelnummern and BudgetPlanCloner for copy-from-existing with per-mount copy/drop. - New plan-create and plan-view Livewire pages; rework plan-edit and the index; drop the old controller-rendered view. - Rework fiscal-year create/edit: overlap validation, authorization, next-year suggestion and a compact range label. - Move routes from web-dev.php into web.php, add the nav entry, schema (referenced_plan_id, nullable description) and German strings. - Fix BudgetPlanFactory missing Published state import. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update ConvertLegacyBudgetPlans for the new model/state structure and add coverage for the legacy conversion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add legacy:verify-booking-migration, a read-only reconciliation that cross-references the booking table against both the old (haushaltstitel) and new (budget_item) title tables. For every booked title it shows the old/new identity side by side and flags titles that are missing on the new side or resolve to a non-bookable group/mount. The footer prints grand totals and a fingerprint over the per-title booking sums: capture it before and after the upgrade to confirm the booking table was preserved (or trigger a rollback if it changed). Only ground truth while both real tables exist, i.e. before the table->view swap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e guards Make the new BudgetItem the canonical title for accounting, leaving the legacy haushaltstitel tables and the booking FK in place (the table->view swap follows separately). - Wire BudgetItem::bookings() (was a stub) to the booking table by titel_id, which equals budget_item.id for converted leaf items, and add isBookable()/hasBookings() helpers. Bookability is derived (a plain budget leaf, never a group or mount), no stored column. - Re-point Booking::budgetItem, ProjectPost::budgetItem and TaxBudget::budgetTitle (renamed from legacyBudgetTitle) at BudgetItem; update consumers (DatevExport, the legacy transaction + show-project views, edit-project tax posts) to short_name/name/budget_type. - Guard plan-edit delete/convertToGroup/convertToMount against items that already have bookings, so a booked leaf can't become non-bookable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the legacy-plan conversion logic out of the ConvertLegacyBudgetPlans console command into a reusable App\Support\Budget\BudgetPlanConverter, so it can also be invoked outside the console (the upcoming table->view swap migration runs it). The command becomes a thin wrapper that handles the dry-run transaction and console output via a logger callback. Behaviour is unchanged (same preserved ids, same fingerprint); the conversion-helper test now exercises the service directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Turn haushaltsplan/haushaltsgruppen/haushaltstitel into read-only VIEWS over the new budget_plan/budget_item structure, so the legacy PHP app keeps reading live data while the new budget plan becomes the single source of truth. Migration (self-contained): converts any existing legacy data into the new tables (idempotent, preserves leaf ids), re-points the booking/projektposten/ tax_budget foreign keys from haushaltstitel/haushaltsplan to budget_item/ budget_plan (widening the int columns to match), drops the legacy tables and recreates them as views. FK constraint names are discovered from information_schema (they vary by environment), and all raw SQL is table-prefix aware. Plans without a fiscal year have no legacy von/bis, so the haushaltsplan view excludes them. NOT data-reversible — roll back from a backup, using legacy:verify-booking-migration's fingerprint to confirm the booking table. Writers are disabled, since the legacy tables can no longer be written: - legacy HHP import (HHPHandler::saveNewHHP) now refuses with a clear message. - TaxBudget::addToPlan is reworked to create budget_items (and its relations point at the new models); its test moves to the new structure. Demo seeding moves off the SQL dump for the budget data: the legacy budget inserts are removed from stufis-demo-data.sql and replaced by DemoBudgetSeeder, which builds the plans/groups/titles in the new structure (preserving leaf and plan ids so the dump's bookings/posts still resolve) and runs before the dump. The year-shift is shared via DemoDataSeeder::yearShiftDelta. Project tests that built legacy fixtures now build the new structure. composer prepare-test gains --drop-views so the persistent test DB can be refreshed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mark legacy:budget-group-shift, legacy:budget-id-shift, legacy:budget-id-batch-shift and legacy:delete-hhp as deprecated / slated for deletion: their targets (haushaltsplan/gruppen/titel) are now read-only views, so they can no longer write. Left runnable for now, but each carries a @deprecated docblock, a [DEPRECATED] description and a runtime warning. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tax_budget foreign keys now point at the new tables, so give them clear English names: titel_id -> budget_id (budget_item), hhp_id -> plan_id (budget_plan). The rename happens in the swap migration (and reverses in down()); cascade it through the TaxBudget model (fillable, relations, addToPlan) and its callers (edit-project tax posts, AddToPlanTest). booking.titel_id and projektposten.titel_id are left as-is: the legacy PHP app reads those column names directly via raw SQL in several places, so renaming them would mean churn in legacy code that is slated for deletion anyway. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bring the legacy HHP "Umsatzsteuer-Titel hinzufügen" feature into the new
budget plan editor as an addTaxTitles() action, and improve it:
- Authorized via the plan-update policy and gated by the global tax.active
setting (button only shown when active), mirroring the legacy gate.
- Tax rates are now configurable via a new tax.rates setting (default 7/19)
instead of being hard-coded; rates are normalised (deduped, positives only,
sorted) and a title is generated per rate.
- addToPlan() returns how many titles were newly added, so the action gives
real feedback ("n added" vs "already present") via a toast, and refreshes
the tree. Still fully idempotent.
The legacy button keeps working too — it calls the same reworked addToPlan,
which now writes the new budget_item structure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eeding; update dependencies
…tion Preload entire item tree with relations and compute effective values bottom-up to reduce redundant database queries and improve rendering efficiency.
…e tax title deletion handling Introduce a function to add plain budget items at the root level and update tests accordingly. Fix foreign key constraint issues during tax title deletion by ensuring tax-budget rows are removed in a transaction. Adjust UI and translations to accommodate the new budget item functionality.
…a new budget plan
…translations Wrap the "legacy budget plan" navigation item with an admin-only permission check. Adjust translations to distinguish between legacy and current budget plans in menus.
Add tests to validate the behavior of read-only legacy budget views (`haushaltsplan`, `haushaltsgruppen`, `haushaltstitel`). Confirm correct mappings for nested and root-level items, proper handling of mount items, and visibility logic based on fiscal year and state.
Fiscal years are meant to tile time without holes. The edit form now detects (live) whether the entered range leaves a gap to the nearest neighbouring fiscal year on either side and shows a non-blocking warning listing the missing date range(s). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…index Fiscal years with no budget plans were skipped entirely on the index. They now always render their headline with a "no budget plans" placeholder row, so the full set of fiscal years is visible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d spreadsheet export Compute the "Ist" (booked) and "Beschlossen" (committed) figures per budget item via BudgetPlanMeasures, mirroring the legacy getMoneyByTitle: booked sums non-canceled bookings, committed reserves open projects' postings plus terminated projects' receipt postings. Figures roll up through groups and mounts and are fetched with a constant number of queries. Wire these into the plan view (replacing the placeholder columns, with column hints), a new per-item detail page listing the item's bookings, and an xlsx/ods spreadsheet export of the whole plan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… and exports Integrate "planned" (Soll) figures alongside booked and committed amounts. Update BudgetPlanMeasures to compute and roll up planned values through groups and mounts, ensuring alignment with effectiveValue logic. Replace placeholders with planned values in views, item detail, and spreadsheet export. Expand test coverage for planned computations.
…oid column overflow Ensure generated `value` and `saldo` fields stay within column limits by bounding randomFloat() magnitudes.
Cap the budget item tree at MAX_DEPTH (3): leaves may sit at the deepest level, groups one level above so their auto-added child still fits. The editor already hid the add-subgroup / to-group actions past the limit; this also guards it server-side in addItem() and convertToGroup() so the invariant holds regardless of the UI, and adds nestingDepth()/ canBecomeGroup() helpers on the model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Group rows in the plan view are now click-to-collapse: an Alpine scope on the tbody tracks collapsed group ids (persisted per plan and per in/out tab), and each row hides itself when any of its ancestor groups is collapsed. Ancestors come from the new BudgetItem::ancestorIds() helper, read off the adjacency-list path. Also replaces the hard-coded per-level padding (capped at 4 levels) with a single --indent CSS variable that scales to any depth, adds a chevron gutter so leaf/mount siblings line up under group names, and moves the kind icon into its own column. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… rows Introduce toolbar buttons to collapse or expand all groups in the read-only plan view. Controls are wired to the shared Alpine scope, enabling batch operations. Update translations for localization support and improve test coverage for the new functionality.
…_id` Substitute date-range associations with direct `budget_plan_id` links across projects and related modules. Update schema, queries, tests, and logic for robust association with budget plans.
…onfig for some time now
Bump laravel/framework ^12.42 -> ^13.0 (13.20) and pull the L13-compatible releases of the staudenmeir packages and translations-checker via a full composer update. Symfony jumps 7.4 -> 8.1. Adapt application code to L13 breaking changes: - Rename CSRF middleware VerifyCsrfToken -> PreventRequestForgery in bootstrap/app.php (the old class is only a deprecated alias now). - LocalSeeder: set the non-nullable, no-default `version` column on the raw user inserts, which strict SQL mode now rejects when omitted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the non-legacy project code's references to the legacy budget plan (LegacyBudgetPlan/LegacyBudgetItem) with the new BudgetPlan/BudgetItem, part of the 4.5.0 goal to retire the legacy Haushaltsplan. Safe because the legacy tables are now id-preserving SQL views over the new structure. - Add BudgetItem::scopeBookable() (is_group=0 AND referenced_plan_id IS NULL), the query-level counterpart of isBookable() and exactly the set the legacy haushaltstitel view exposed. - ProjectState + subclasses: titel_id exists rule now targets BudgetItem via a shared budgetItemExistsRule() constrained to bookable leaves. - edit-project: plan/title lookups use BudgetPlan/BudgetItem; remap and title options key on short_name (Titelnummer) via bookable(); blade shows name/short_name. - Legacy\Project::relatedBudgetPlan() returns the new BudgetPlan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a two-tab detail view for a budget item — "Buchungen (Gebucht)" and "Projekte (Beschlossen)" — with sortable columns and the active tab kept in the URL. The bookings tab eager-loads the receipt chain (Belegposten → Beleg → Abrechnung → Projekt); sort fields are whitelisted so a tampered value can never reach arbitrary data. - BudgetPlanMeasures::committedBreakdown() returns per-project rows behind the "Beschlossen" card (open projects contribute planned, terminated contribute billed), reconciling exactly with the card total; extract billedByPost() and bySide() from committedMap(). - Add a reusable consumption-meter component and the German labels. - budget-plan.item.view breadcrumb now shows ancestor groups as unlinked crumbs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Point the plan card and per-post title links at budget-plan.view / budget-plan.item.view instead of the legacy legacy.hhp.* routes, and add wire:navigate. Drop the now-unused $budgetPlan view var (the blade reads $budgetItem->budget_plan_id directly). Part of the 4.5.0 legacy-budget retirement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Booking (B), Project (P) and Abrechnung (A) own those id prefixes, so block
them as bank-account short codes (not_in:B,P,A) with a matching hint and error
message. Show bookings as B{id} in the legacy transaction view for consistency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # composer.json # composer.lock # docs/feature-changelog.md
Switch CSP from report-only to enforcing (4.5.0): - Strict policy for the modern app: script-src 'self' 'nonce', no unsafe-*. Align Vite/Livewire/Flux nonces via Vite::useCspNonce so the inline @livewireScriptConfig isn't blocked and Livewire boots. - Bundle the CSP-safe Alpine build (livewire.csp.esm) and move the budget-plan collapse logic into a bundled Alpine.data module (budget-collapse.js) registered before Livewire.start(); reduce the blade attributes to the CSP-safe subset, pass arrays via @JSON data-*. - Permissive LegacyCspPreset on the legacy.* route group: the legacy system is embedded via <iframe srcdoc> (inherits the parent CSP) and predates CSP, so it needs unsafe-inline/eval. - style-src-attr 'unsafe-inline' for data-driven inline widths; img-src scoped to the stumv provider origin for external avatars. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…de file and remove standalone JS module
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist for @DieMichii
Changelog:
FIXME
Deployment (production) — env vars to remove after this update
Sessions are now database-backed with a pinned
config/session.php, and OIDC Back-Channel (Single) Logout was added. Delete these entries from the production.envso the new config defaults take effect:SESSION_DRIVER— must be removed (or set todatabase). Sessions now live in the DB; OIDC Back-Channel Logout finds the session to terminate via the newsessions.oidc_sidcolumn and does not work with thefiledriver.SESSION_ENCRYPT— remove so the new default (true) applies; the session payload is encrypted at rest.Also required on deploy:
php artisan migrate— creates thesessionstable (incl. theoidc_sidcolumn).php artisan config:cache.<APP_URL>/auth/backchannel-logoutas the OIDC client'sback_channel_logout_uri.end_session_endpoint(ending the SSO session) and back. NoOIDC_LOGOUT_URLanymore. New optional env:OIDC_POST_LOGOUT_REDIRECT— where the browser lands after logout (default: StuFis login route). If set, register it as the client'spost_logout_redirect_uriin StuMV.OIDC_LOGOUT_CONFIRM— defaulttrue: the IdP shows its "really log out?" prompt (id_token_hint withheld), so the user confirms before their whole SSO session ends. Setfalsefor a seamless logout without a prompt.