fix(builder): surface proceeding_type id so add-proceeding POST works (t-paliad-345)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / build (pull_request) Has been cancelled
Paliad CI gate / test-go (pull_request) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
Paliad CI gate / deploy (pull_request) Has been cancelled
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / build (pull_request) Has been cancelled
Paliad CI gate / test-go (pull_request) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
Paliad CI gate / deploy (pull_request) Has been cancelled
The Litigation Builder's "+ Verfahren hinzufügen" silently failed in
prod after t-paliad-343 B2 shipped — clicking a Verfahren chip in the
picker did nothing, no visible error.
Root cause: the wire shape FristenrechnerType (the response of
/api/tools/proceeding-types) carried code+name+nameEN+group but not
id. Builder.ts mountAddProceedingPicker's callback POSTed
`{proceeding_type_id: meta.id}` to
/api/builder/scenarios/{id}/proceedings — meta.id was undefined,
JSON.stringify dropped the key, the server returned 400 ("invalid
input: proceeding_type_id is required"), and fetchJSON swallowed the
error to console. The user saw "nothing happens".
Fix:
- Add `ID int json:"id"` to lp.FristenrechnerType.
- SELECT id in FristenrechnerService.ListProceedings + Scan into the
new field.
- Defensive guard in builder.ts openAddProceedingPicker — refuse to
POST without a positive integer id and log a clear error, so a
future wire-shape regression cannot recreate the silent-fail.
- Regression test in pkg/litigationplanner/types_wire_test.go pins the
contract (id present in JSON, round-trips as integer).
Side-benefit: fristenrechner-wizard.ts:599-628 documented this exact
gap as a known limitation ("S5/follow-up can extend the wire shape to
include id"). That workaround can now be retired in a follow-up.
Refs m/paliad#153 (Litigation Builder)
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
50
pkg/litigationplanner/types_wire_test.go
Normal file
50
pkg/litigationplanner/types_wire_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user