Skip to content

Release/4.5.0 - #324

Open
lukas-staab wants to merge 42 commits into
mainfrom
release/4.5.0
Open

Release/4.5.0#324
lukas-staab wants to merge 42 commits into
mainfrom
release/4.5.0

Conversation

@lukas-staab

@lukas-staab lukas-staab commented Jun 30, 2026

Copy link
Copy Markdown
Member

Checklist for @DieMichii

  • Languaging
  • automated Testing
  • Documentation
  • Version bump (composer) and Version Tag
  • Changelog written

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 .env so the new config defaults take effect:

  • SESSION_DRIVERmust be removed (or set to database). Sessions now live in the DB; OIDC Back-Channel Logout finds the session to terminate via the new sessions.oidc_sid column and does not work with the file driver.
  • SESSION_ENCRYPT — remove so the new default (true) applies; the session payload is encrypted at rest.

Also required on deploy:

  • php artisan migrate — creates the sessions table (incl. the oidc_sid column).
  • Rebuild the config cache if used: php artisan config:cache.
  • In StuMV, register <APP_URL>/auth/backchannel-logout as the OIDC client's back_channel_logout_uri.
  • Logout now performs RP-Initiated Logout automatically: it redirects to the IdP's auto-discovered end_session_endpoint (ending the SSO session) and back. No OIDC_LOGOUT_URL anymore. New optional env:
    • OIDC_POST_LOGOUT_REDIRECT — where the browser lands after logout (default: StuFis login route). If set, register it as the client's post_logout_redirect_uri in StuMV.
    • OIDC_LOGOUT_CONFIRMdefault true: the IdP shows its "really log out?" prompt (id_token_hint withheld), so the user confirms before their whole SSO session ends. Set false for a seamless logout without a prompt.

Ordering dependency: Back-Channel (Single) Logout only fires once StuMV v2 (the new-architecture rewrite, which mints and POSTs the logout_token) is the live IdP in production. Only StuMV v2 sends these notifications; the previous StuMV/Keycloak setup does not. If this StuFis release ships before StuMV v2 is deployed, the endpoint is harmless but dormant — single-logout starts working automatically once StuMV v2 goes live and the client's back_channel_logout_uri is registered.

lukas-staab and others added 30 commits June 27, 2026 09:23
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>
…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.
…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.
lukas-staab and others added 12 commits July 10, 2026 14:33
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>
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