Commit Graph

4 Commits

Author SHA1 Message Date
m
9919e04657 feat(t-paliad-128): /events 'Nur persönliche' = items I created
Redefines the "Nur persönliche" filter on /events from "appointment with
NULL project_id" to "items where created_by = me", applied uniformly to
deadlines and appointments.

Before: client-side filter dropped every deadline row because the type
guard was `x.type === "appointment"`. m saw zero deadlines under "Nur
persönliche" even though he created plenty.

After:
- /api/events?personal_only=true (and /api/events/summary?personal_only=true)
  narrow BOTH rails to f.created_by / t.created_by = current user.
  ProjectID is ignored when personal_only is set (the two are
  contradictory).
- DeadlineService.ListFilter and AppointmentService.AppointmentListFilter
  gain CreatedBy *uuid.UUID — composes with existing visibility (AND), so
  a row created on a team the user has since left still won't leak.
- Frontend drops the client-side filter; sends personal_only=true when
  projectFilter === PERSONAL. URL ?personal_only=true also accepted on
  initial load (bookmark-friendly alias for ?project_id=__personal__).
  Personal option now shows for type=Fristen too — applies uniformly.
- 3 new live subtests covering personal_only across type=deadline /
  appointment / all, with mixed-creator + multi-project + null-project
  fixtures.
2026-05-04 19:49:37 +02:00
m
1bba9cb3ce feat(t-paliad-123): apply date-bucket Status filter to appointments
Until now, /events hid the Status dropdown when Type=Termine. The
date-bucket filters (Heute, Diese Woche, Nächste Woche, Später) only
worked on the deadline rail — m wanted them on appointments too, even
without a "completed" dimension.

Frontend (events.ts):
- New populateStatusFilter() rebuilds the Status <select> options based
  on currentType: deadlines get the full 8-option set, appointments
  narrow to 5 (Alle + 4 buckets). The "completed/pending/overdue"
  options drop because they have no appointment analogue.
- applyTypeVisibility() no longer hides the Status filter for
  appointments; it calls the populator instead. The populator runs on
  type-chip click and on language change so labels translate live.
- When switching type while a now-invalid status is selected (e.g.
  Termine + status=completed via URL), the populator falls back to the
  per-type default (deadline → pending, appointment → all) and updates
  URL params.
- syncURLParams + isFilterPristine + initFilters use a per-type default
  so the appointment view treats `all` as pristine and stays out of the
  URL until the user picks a bucket.
- loadList always sends `status` to /api/events; backend already
  applies bucket-aware appointment filtering via
  bucketAppointmentWindow().

events.tsx:
- The static <option> list collapses to a single placeholder; the
  populator owns the option set at hydration.

i18n:
- New `events.filter.status.all` ("Alle"/"All") for the appointment-only
  case — `deadlines.filter.all` says "Alle (offen & erledigt)" which is
  wrong for appointments (they don't have a completed/pending state).

Backend (event_service_test.go):
- Three new live subtests confirming type=appointment + status=today
  narrows to today's appointments, status=later narrows to far-future,
  and status=completed collapses the appointment rail (defensive vs.
  URL-hacking — the dropdown excludes that value for appointments).
2026-05-04 18:56:25 +02:00
m
0be2dfb5a0 fix(t-paliad-111): bug bundle (correctness) — UPC GESAMTKOSTEN, court-set dates, REGEL save flow
Three correctness bugs from the t-paliad-101 QA sweep, fixed together since
they all change displayed/saved numbers users rely on.

B1 — Kostenrechner UPC GESAMTKOSTEN double-count
  ComputeUPCInstance was setting InstanceTotal = effectiveCourtFee +
  recoverableCeiling. The R.152 recoverable-cost cap is the OPPOSING
  side's worst-case loss-of-suit liability, not the user's own cost —
  folding it into GESAMTKOSTEN inflated the UPC total under a label
  that means "your outlay," and the DE LG/OLG/BGH branches don't add
  any opponent estimate. Drop it from InstanceTotal; the ceiling
  still surfaces as its own RecoverableCeiling line item.

  Live pre-fix on paliad.de (Streitwert 100k, UPC 1. Instanz only):
    instanceTotal = 52600 = 14600 court fee + 38000 R.152 ceiling
  Post-fix:
    instanceTotal = 14600 (court fee only); RecoverableCeiling stays 38000

B3 — Court-determined Termine emit trigger date as a real-looking date
  Zwischenverfahren / Mündliche Verhandlung / Entscheidung all live in
  paliad.deadline_rules with duration_value=0 and parent_id=NULL, so
  Calculate() classified them as IsRootEvent and emitted the trigger
  date as their own DueDate. Worse, RoP.151 "Antrag auf Kostenentscheidung"
  parents off inf.decision and chained 1 month off the placeholder ->
  bogus deadline that the UI rendered as real.

  Fix: classify a zero-duration rule as IsCourtSet (not IsRootEvent)
  when primary_party = 'court' or event_type ∈ {hearing, decision,
  order}. Track court-set rule IDs and propagate IsCourtSet downstream
  to any rule whose parent is court-set, so RoP.151 also surfaces as
  court-set rather than a fabricated date. Save-modal already greys
  out IsCourtSet rows so the "Gerichtsbestimmte Termine ohne Datum
  werden übersprungen" footnote becomes truthful again.

  Live pre-fix on paliad.de (UPC_INF, trigger 2026-04-29):
    Zwischenverfahren / Oral / Entscheidung -> dueDate 2026-04-29
    Antrag auf Kostenentscheidung -> 2026-05-29 (bogus, +1mo from trigger)

B6 — Fristenrechner save flow stored rule code in TITLE
  Frontend was concatenating "RoP.023 — Klageerwiderung" into the
  title because deadlines had nowhere else to put the citation, and
  the /deadlines REGEL column ended up showing "—". Add migration 032
  with a paliad.deadlines.rule_code text column, plumb it through
  CreateDeadlineInput / insertTx, drop the now-redundant r.code AS
  rule_code JOIN alias on the list query (the deadline owns its
  citation), and render f.rule_code on the project-detail deadlines
  table + /deadlines events list + deadline-detail page.

Build, vet, and tests all clean. New unit test
TestIsCourtDeterminedRule pins the B3 discriminator across the
event_type / primary_party combinations seen in migrations 012 + 031.

Repro creds: tester@hlc.de
2026-05-04 14:42:29 +02:00
m
2102dfd07d feat(t-paliad-110): add EventService + /api/events + /api/events/summary
PR-1 of the Fristen+Termine unification (t-paliad-110). Backend layer
only — no frontend changes; the existing /deadlines and /appointments
pages still render the type-specific UIs.

EventService delegates to DeadlineService + AppointmentService for the
actual reads (no duplicate visibility logic, no duplicate event_type
hydration), then projects both into the discriminated EventListItem
union and merges/sorts by event_date asc. The handler exposes:

  GET /api/events?type=deadline|appointment|all&status=…&project_id=…
                  &event_type=…&type_filter=…&from=…&to=…
  GET /api/events/summary?type=…&project_id=…

Bucket model (per t-paliad-110 spec, supersedes t-106):
- four universal cards: Heute · Diese Woche · Nächste Woche · Später
- Überfällig is deadline-only, conditional, alarm-styled when > 0
- Erledigt drops from the card row; stays available as a filter option
- appointments have no completed_at — past appointments aren't bucketed

The deadline-side cutoffs reuse computeDeadlineBucketBounds so
/api/events/summary and /api/deadlines/summary can never disagree.

Existing /api/deadlines and /api/appointments stay untouched —
calendars, project-detail panes, and CalDAV consumers still call them
directly.
2026-05-04 13:37:20 +02:00