feat(t-paliad-088): Event Types for deadlines — schema + service + handlers (PR-1)

Migration 030 adds paliad.event_types and paliad.deadline_event_types
junction. ~43 firm-wide seeds biased toward submissions (25 UPC
submissions + 8 UPC decisions/orders/hearings + 5 EPO + 4 DPMA/DE + 1
cross-jurisdiction). UPC-seeded rows carry a loose trigger_event_id
column (no FK constraint per Q2: event_types leads, trigger_events
follows). RLS policies are defense-in-depth — primary enforcement is
in the Go service layer. Per Q6, any authenticated user can create
firm-wide types; admins moderate via the soft-delete archive lever.

EventTypeService: List (firm-wide ∪ own-private), GetByID, Create
(slug auto-derived, supports diacritics → ASCII), Update (author OR
admin-on-firm-wide), SuggestSimilar (powers the duplicate-warning in
the add modal), AttachToDeadlineTx + ValidateForUser + ListForDeadlines
for the junction.

DeadlineService gains an EventTypeService dependency and now:
- accepts event_type_ids on Create / Update / CreateBulk
- attaches them in the same transaction as the deadline insert
- hydrates EventTypeIDs on every Get / List / ListForProject
- supports the multi-select Typ filter via ListFilter.EventTypeIDs +
  IncludeUntyped (UNION semantics within types, AND-intersected with
  Status/Project)

AgendaService gets the same Typ filter on its deadline side;
appointments are unaffected.

API:
- GET /api/event-types?category=&jurisdiction=
- GET /api/event-types/suggest?q=
- POST /api/event-types
- PATCH /api/event-types/{id}        (set archive=true to hide)
- GET /api/deadlines?event_type=<uuid>,<uuid>,none
- GET /api/agenda?event_type=<uuid>,<uuid>,none
- POST/PATCH /api/deadlines accept event_type_ids: [uuid]

go build / go vet / go test ./... clean.

Frontend (picker + custom-add modal + multi-select filter) follows in
PR-2. Admin moderation panel deferred to t-paliad-089 follow-up.
This commit is contained in:
m
2026-04-30 12:49:04 +02:00
parent c74f6b494c
commit 04ce6a8bfa
14 changed files with 1151 additions and 43 deletions

View File

@@ -181,6 +181,11 @@ type Deadline struct {
CreatedBy *uuid.UUID `db:"created_by" json:"created_by,omitempty"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
// EventTypeIDs lists the paliad.event_types attached to this deadline
// via the paliad.deadline_event_types junction. Always present (never
// nil) once the row has been hydrated by DeadlineService.
EventTypeIDs []uuid.UUID `db:"-" json:"event_type_ids"`
}
// DeadlineWithProject enriches a Deadline with parent-Project display fields
@@ -403,3 +408,51 @@ type EventDeadlineRuleCode struct {
RuleCode string `db:"rule_code" json:"rule_code"`
SortOrder int `db:"sort_order" json:"sort_order"`
}
// EventType is a user-facing categorization tag for a Deadline (Statement
// of Defence, Reply, Decision on the merits, EPO opposition, …). Distinct
// from TriggerEvent: TriggerEvents are calc-engine state (UPC-only,
// verbatim youpc imports), EventTypes are the broader taxonomy users
// pick from when creating a Deadline.
//
// CreatedBy NULL on system seeds; set on user-created rows. IsFirmWide
// true for seeds and any firm-wide row a user explicitly publishes;
// false for personal taxonomy. TriggerEventID is a loose linkage column
// (no FK constraint) populated only for seeded UPC rows.
type EventType struct {
ID uuid.UUID `db:"id" json:"id"`
Slug string `db:"slug" json:"slug"`
LabelDE string `db:"label_de" json:"label_de"`
LabelEN string `db:"label_en" json:"label_en"`
Category string `db:"category" json:"category"`
Jurisdiction *string `db:"jurisdiction" json:"jurisdiction,omitempty"`
Description string `db:"description" json:"description"`
TriggerEventID *int64 `db:"trigger_event_id" json:"trigger_event_id,omitempty"`
CreatedBy *uuid.UUID `db:"created_by" json:"created_by,omitempty"`
IsFirmWide bool `db:"is_firm_wide" json:"is_firm_wide"`
ArchivedAt *time.Time `db:"archived_at" json:"archived_at,omitempty"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
// EventTypeCategory enumerates the values allowed on event_types.category.
// Mirrors the CHECK constraint in migration 030.
const (
EventTypeCategorySubmission = "submission"
EventTypeCategoryDecision = "decision"
EventTypeCategoryOrder = "order"
EventTypeCategoryService = "service"
EventTypeCategoryFee = "fee"
EventTypeCategoryHearing = "hearing"
EventTypeCategoryOther = "other"
)
// EventTypeJurisdiction enumerates the values allowed on
// event_types.jurisdiction (NULL is also valid).
const (
EventTypeJurisdictionUPC = "UPC"
EventTypeJurisdictionEPO = "EPO"
EventTypeJurisdictionDPMA = "DPMA"
EventTypeJurisdictionDE = "DE"
EventTypeJurisdictionAny = "any"
)