Merge: t-paliad-345 — surface proceeding_type id so Builder add-proceeding works (m/paliad#153)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled

This commit is contained in:
mAi
2026-05-28 09:50:57 +02:00
4 changed files with 72 additions and 2 deletions

View File

@@ -855,6 +855,16 @@ function openAddProceedingPicker(anchor: HTMLElement): void {
if (!state.active) return;
mountAddProceedingPicker(anchor, state.procTypes, async (meta) => {
if (!state.active) return;
// Guard against a wire-shape regression: if the proceeding-types
// endpoint stops returning `id`, `meta.id` is undefined and
// JSON.stringify silently drops the field, the server rejects the
// POST with a 400, and fetchJSON swallows the error — the user
// sees "nothing happens" (t-paliad-345). Fail loud instead.
if (typeof meta.id !== "number" || meta.id <= 0) {
console.error("builder: missing proceeding_type id in picker meta", meta);
setSaveState("error");
return;
}
const proc = await addProceeding(state.active.id, {
proceeding_type_id: meta.id,
});

View File

@@ -145,7 +145,7 @@ func (s *FristenrechnerService) ListProceedings(ctx context.Context, opts Procee
AND pe.event_kind = $%d
)`, opts.EventKind)
}
query := `SELECT code, name, name_en, jurisdiction
query := `SELECT id, code, name, name_en, jurisdiction
FROM paliad.proceeding_types
WHERE ` + strings.Join(where, " AND ") + `
ORDER BY sort_order`
@@ -160,7 +160,7 @@ func (s *FristenrechnerService) ListProceedings(ctx context.Context, opts Procee
for rows.Next() {
var t lp.FristenrechnerType
var juris sql.NullString
if err := rows.Scan(&t.Code, &t.Name, &t.NameEN, &juris); err != nil {
if err := rows.Scan(&t.ID, &t.Code, &t.Name, &t.NameEN, &juris); err != nil {
return nil, err
}
if juris.Valid {

View File

@@ -531,7 +531,17 @@ type RuleCalculationProceeding struct {
// FristenrechnerType mirrors the /api/tools/proceeding-types response
// metadata.
//
// ID is the paliad.proceeding_types primary key. Surfaces so frontend
// pickers (Litigation Builder add-proceeding, fristenrechner-wizard
// project prefill) can POST the FK directly without a code→id round
// trip. Historically code-keyed; the Litigation Builder POSTing
// proceeding_type_id (int) to /api/builder/scenarios/{id}/proceedings
// forced surfacing the id (t-paliad-345 — the missing id meant the
// POST silently sent body={} and the "+ Verfahren hinzufügen" button
// did nothing).
type FristenrechnerType struct {
ID int `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
NameEN string `json:"nameEN"`

View File

@@ -0,0 +1,50 @@
package litigationplanner
import (
"encoding/json"
"strings"
"testing"
)
// TestFristenrechnerType_WireShapeIncludesID is the regression test for
// t-paliad-345: the /api/tools/proceeding-types JSON response must
// include `id` so frontend pickers (Litigation Builder add-proceeding,
// fristenrechner-wizard project prefill) can POST proceeding_type_id
// directly without a code→id round trip. When the id was missing the
// Litigation Builder "+ Verfahren hinzufügen" button silently dropped
// the proceeding_type_id from the POST body (JSON.stringify omits
// undefined keys), the server rejected with 400, and the client
// swallowed the error — user-visible symptom was "nothing happens".
func TestFristenrechnerType_WireShapeIncludesID(t *testing.T) {
in := FristenrechnerType{
ID: 42,
Code: "upc.inf.cfi",
Name: "UPC Verletzungsverfahren",
NameEN: "UPC Infringement Action",
Group: "UPC",
}
b, err := json.Marshal(in)
if err != nil {
t.Fatalf("marshal: %v", err)
}
got := string(b)
if !strings.Contains(got, `"id":42`) {
t.Errorf("missing id in wire shape: %s", got)
}
for _, want := range []string{`"code":"upc.inf.cfi"`, `"nameEN":"UPC Infringement Action"`} {
if !strings.Contains(got, want) {
t.Errorf("missing %q in wire shape: %s", want, got)
}
}
// Round-trip — a client that posts the id back to /api/builder/
// scenarios/{id}/proceedings should see it preserved as an integer
// (paliad.scenario_proceedings.proceeding_type_id is INT, not UUID).
var out FristenrechnerType
if err := json.Unmarshal(b, &out); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if out.ID != 42 {
t.Errorf("id lost on round-trip: got %d want 42", out.ID)
}
}